Merge "Add vibrator service test mapping to source folder" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 13903ac..f429966 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -56,6 +56,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.expresslog.Counter;
import com.android.server.LocalServices;
import com.android.server.job.GrantedUriPermissions;
import com.android.server.job.JobSchedulerInternal;
@@ -161,6 +162,9 @@
/** If the job is going to be passed an unmetered network. */
private boolean mHasAccessToUnmetered;
+ /** If the effective bucket has been downgraded once due to being buggy. */
+ private boolean mIsDowngradedDueToBuggyApp;
+
/**
* The additional set of dynamic constraints that must be met if this is an expedited job that
* had a long enough run while the device was Dozing or in battery saver.
@@ -1173,18 +1177,32 @@
// like other ACTIVE apps.
return ACTIVE_INDEX;
}
+
+ final int bucketWithMediaExemption;
+ if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
+ && mHasMediaBackupExemption) {
+ // Treat it as if it's at most WORKING_INDEX (lower index grants higher quota) since
+ // media backup jobs are important to the user, and the source package may not have
+ // been used directly in a while.
+ bucketWithMediaExemption = Math.min(WORKING_INDEX, actualBucket);
+ } else {
+ bucketWithMediaExemption = actualBucket;
+ }
+
// If the app is considered buggy, but hasn't yet been put in the RESTRICTED bucket
// (potentially because it's used frequently by the user), limit its effective bucket
// so that it doesn't get to run as much as a normal ACTIVE app.
- final int highestBucket = isBuggy ? WORKING_INDEX : ACTIVE_INDEX;
- if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
- && mHasMediaBackupExemption) {
- // Treat it as if it's at least WORKING_INDEX since media backup jobs are important
- // to the user, and the
- // source package may not have been used directly in a while.
- return Math.max(highestBucket, Math.min(WORKING_INDEX, actualBucket));
+ if (isBuggy && bucketWithMediaExemption < WORKING_INDEX) {
+ if (!mIsDowngradedDueToBuggyApp) {
+ // Safety check to avoid logging multiple times for the same job.
+ Counter.logIncrementWithUid(
+ "job_scheduler.value_job_quota_reduced_due_to_buggy_uid",
+ getTimeoutBlameUid());
+ mIsDowngradedDueToBuggyApp = true;
+ }
+ return WORKING_INDEX;
}
- return Math.max(highestBucket, actualBucket);
+ return bucketWithMediaExemption;
}
/** Returns the real standby bucket of the job. */
diff --git a/api/Android.bp b/api/Android.bp
index f5bafe8..c16bce5 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -60,14 +60,14 @@
metalava_cmd = "$(location metalava)"
// Silence reflection warnings. See b/168689341
metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
-metalava_cmd += " --quiet --no-banner --format=v2 "
+metalava_cmd += " --quiet "
genrule {
name: "current-api-xml",
tools: ["metalava"],
srcs: [":frameworks-base-api-current.txt"],
out: ["current.api"],
- cmd: metalava_cmd + "-convert2xmlnostrip $(in) $(out)",
+ cmd: metalava_cmd + "signature-to-jdiff $(in) $(out)",
visibility: ["//visibility:public"],
}
diff --git a/cmds/appops/OWNERS b/cmds/appops/OWNERS
index 999ea0e..2fe78c5 100644
--- a/cmds/appops/OWNERS
+++ b/cmds/appops/OWNERS
@@ -1 +1,2 @@
+#Bug component: 137825
include /core/java/android/permission/OWNERS
diff --git a/core/api/current.txt b/core/api/current.txt
index e811eca..eaefe84 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9694,6 +9694,7 @@
method public int describeContents();
method public void enforceCallingUid();
method @Nullable public String getAttributionTag();
+ method public int getDeviceId();
method @Nullable public android.content.AttributionSource getNext();
method @Nullable public String getPackageName();
method public int getPid();
@@ -9709,6 +9710,7 @@
ctor public AttributionSource.Builder(@NonNull android.content.AttributionSource);
method @NonNull public android.content.AttributionSource build();
method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
+ method @NonNull public android.content.AttributionSource.Builder setDeviceId(int);
method @Deprecated @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
method @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);
method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
@@ -46018,6 +46020,7 @@
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT = 9; // 0x9
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED = 6; // 0x6
+ field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16; // 0x10
field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2
field public static final int SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE = 3; // 0x3
field public static final int SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION = 4; // 0x4
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index cf26e12..083c445 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -850,6 +850,7 @@
ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder);
ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource);
ctor public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource);
+ ctor public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource);
method public void enforceCallingPid();
}
@@ -4235,6 +4236,7 @@
field public final boolean isTrustedOverlay;
field public final boolean isVisible;
field @NonNull public final String name;
+ field @NonNull public final android.graphics.Matrix transform;
field @NonNull public final android.os.IBinder windowToken;
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ab567ac..59b0dac 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3463,7 +3463,7 @@
AttributionSource attributionSource = new AttributionSource(Process.myUid(),
Process.myPid(), mOpPackageName, attributionTag,
(renouncedPermissions != null) ? renouncedPermissions.toArray(new String[0]) : null,
- nextAttributionSource);
+ getDeviceId(), nextAttributionSource);
// If we want to access protected data on behalf of another app we need to
// tell the OS that we opt in to participate in the attribution chain.
if (nextAttributionSource != null) {
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 3dddbc0..d0bb2b9 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -115,14 +115,14 @@
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag, @NonNull IBinder token) {
this(uid, Process.INVALID_PID, packageName, attributionTag, token,
- /*renouncedPermissions*/ null, /*next*/ null);
+ /*renouncedPermissions*/ null, Context.DEVICE_ID_DEFAULT, /*next*/ null);
}
/** @hide */
public AttributionSource(int uid, int pid, @Nullable String packageName,
@Nullable String attributionTag, @NonNull IBinder token) {
this(uid, pid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
- /*next*/ null);
+ Context.DEVICE_ID_DEFAULT, /*next*/ null);
}
/** @hide */
@@ -132,21 +132,23 @@
@Nullable AttributionSource next) {
this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken,
(renouncedPermissions != null)
- ? renouncedPermissions.toArray(new String[0]) : null, /*next*/ next);
+ ? renouncedPermissions.toArray(new String[0]) : null, Context.DEVICE_ID_DEFAULT,
+ /*next*/ next);
}
/** @hide */
public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) {
this(current.getUid(), current.getPid(), current.getPackageName(),
current.getAttributionTag(), current.getToken(),
- current.mAttributionSourceState.renouncedPermissions, next);
+ current.mAttributionSourceState.renouncedPermissions, current.getDeviceId(), next);
}
/** @hide */
public AttributionSource(int uid, int pid, @Nullable String packageName,
- @Nullable String attributionTag, @Nullable String[] renouncedPermissions,
+ @Nullable String attributionTag, @Nullable String[] renouncedPermissions, int deviceId,
@Nullable AttributionSource next) {
- this(uid, pid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
+ this(uid, pid, packageName, attributionTag, sDefaultToken, renouncedPermissions, deviceId,
+ next);
}
/** @hide */
@@ -155,6 +157,16 @@
@Nullable String attributionTag, @NonNull IBinder token,
@Nullable String[] renouncedPermissions,
@Nullable AttributionSource next) {
+ this(uid, pid, packageName, attributionTag, token, renouncedPermissions,
+ Context.DEVICE_ID_DEFAULT, next);
+ }
+
+ /** @hide */
+ @TestApi
+ public AttributionSource(int uid, int pid, @Nullable String packageName,
+ @Nullable String attributionTag, @NonNull IBinder token,
+ @Nullable String[] renouncedPermissions,
+ int deviceId, @Nullable AttributionSource next) {
mAttributionSourceState = new AttributionSourceState();
mAttributionSourceState.uid = uid;
mAttributionSourceState.pid = pid;
@@ -162,6 +174,7 @@
mAttributionSourceState.packageName = packageName;
mAttributionSourceState.attributionTag = attributionTag;
mAttributionSourceState.renouncedPermissions = renouncedPermissions;
+ mAttributionSourceState.deviceId = deviceId;
mAttributionSourceState.next = (next != null) ? new AttributionSourceState[]
{next.mAttributionSourceState} : new AttributionSourceState[0];
}
@@ -197,25 +210,31 @@
/** @hide */
public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
- getToken(), mAttributionSourceState.renouncedPermissions, next);
+ getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), next);
}
/** @hide */
public AttributionSource withPackageName(@Nullable String packageName) {
return new AttributionSource(getUid(), getPid(), packageName, getAttributionTag(),
- getToken(), mAttributionSourceState.renouncedPermissions, getNext());
+ getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());
}
/** @hide */
public AttributionSource withToken(@NonNull Binder token) {
return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
- token, mAttributionSourceState.renouncedPermissions, getNext());
+ token, mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());
}
/** @hide */
public AttributionSource withPid(int pid) {
return new AttributionSource(getUid(), pid, getPackageName(), getAttributionTag(),
- getToken(), mAttributionSourceState.renouncedPermissions, getNext());
+ getToken(), mAttributionSourceState.renouncedPermissions, getDeviceId(), getNext());
+ }
+
+ /** @hide */
+ public AttributionSource withDeviceId(int deviceId) {
+ return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
+ getToken(), mAttributionSourceState.renouncedPermissions, deviceId, getNext());
}
/** @hide */
@@ -259,6 +278,7 @@
try {
return new AttributionSource.Builder(uid)
.setPid(Process.myPid())
+ .setDeviceId(Context.DEVICE_ID_DEFAULT)
.setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
.build();
} catch (Exception ignored) {
@@ -497,6 +517,13 @@
}
/**
+ * The device ID for which permissions are checked.
+ */
+ public int getDeviceId() {
+ return mAttributionSourceState.deviceId;
+ }
+
+ /**
* Unique token for that source.
*
* @hide
@@ -662,6 +689,19 @@
}
/**
+ * Set the device ID for this attribution source, permission check would happen
+ * against this device ID.
+ *
+ * @return the builder
+ */
+ public @NonNull Builder setDeviceId(int deviceId) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x12;
+ mAttributionSourceState.deviceId = deviceId;
+ return this;
+ }
+
+ /**
* The next app to receive the permission protected data.
*
* @deprecated Use {@link setNextAttributionSource} instead.
@@ -703,6 +743,9 @@
if ((mBuilderFieldsSet & 0x10) == 0) {
mAttributionSourceState.renouncedPermissions = null;
}
+ if ((mBuilderFieldsSet & 0x12) == 0) {
+ mAttributionSourceState.deviceId = Context.DEVICE_ID_DEFAULT;
+ }
if ((mBuilderFieldsSet & 0x20) == 0) {
mAttributionSourceState.next = null;
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6d82922..2a6d84b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5298,6 +5298,13 @@
public static final String APP_PREDICTION_SERVICE = "app_prediction";
/**
+ * Used for reading system-wide, overridable flags.
+ *
+ * @hide
+ */
+ public static final String FEATURE_FLAGS_SERVICE = "feature_flags";
+
+ /**
* Official published name of the search ui service.
*
* <p><b>NOTE: </b> this service is optional; callers of
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c10062c..fe108a5 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6546,6 +6546,14 @@
public static final String EXTRA_REASON = "android.intent.extra.REASON";
/**
+ * Intent extra: Whether to show the wipe progress UI or to skip it.
+ *
+ * <p>Type: boolean
+ * @hide
+ */
+ public static final String EXTRA_SHOW_WIPE_PROGRESS = "android.intent.extra.SHOW_WIPE_PROGRESS";
+
+ /**
* {@hide}
* This extra will be send together with {@link #ACTION_FACTORY_RESET}
*/
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 2948bd9..bef023e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1432,7 +1432,6 @@
INSTALL_ALLOW_DOWNGRADE,
INSTALL_STAGED,
INSTALL_REQUEST_UPDATE_OWNERSHIP,
- INSTALL_DONT_EXTRACT_BASELINE_PROFILES,
})
@Retention(RetentionPolicy.SOURCE)
public @interface InstallFlags {}
@@ -1647,13 +1646,6 @@
*/
public static final int INSTALL_FROM_MANAGED_USER_OR_PROFILE = 1 << 26;
- /**
- * Flag parameter for {@link PackageInstaller.SessionParams} to indicate that do not extract
- * the baseline profiles when parsing the apk
- * @hide
- */
- public static final int INSTALL_DONT_EXTRACT_BASELINE_PROFILES = 1 << 27;
-
/** @hide */
@IntDef(flag = true, value = {
DONT_KILL_APP,
diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java
index 3b53c25..e75aa06 100644
--- a/core/java/android/content/pm/dex/DexMetadataHelper.java
+++ b/core/java/android/content/pm/dex/DexMetadataHelper.java
@@ -23,9 +23,6 @@
import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.AssetManager;
-import android.os.ParcelFileDescriptor.AutoCloseInputStream;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.JsonReader;
@@ -36,7 +33,6 @@
import com.android.internal.security.VerityUtils;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -48,7 +44,6 @@
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
/**
* Helper class used to compute and validate the location of dex metadata files.
@@ -67,11 +62,6 @@
private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
- private static final String PROFILE_FILE_NAME = "primary.prof";
- private static final String PROFILE_METADATA_FILE_NAME = "primary.profm";
- private static final String BASELINE_PROFILE_SOURCE_RELATIVE_PATH = "dexopt/baseline.prof";
- private static final String BASELINE_PROFILE_METADATA_RELATIVE_PATH = "dexopt/baseline.profm";
-
private DexMetadataHelper() {}
/** Return true if the given file is a dex metadata file. */
@@ -323,76 +313,4 @@
}
}
- /**
- * Extract the baseline profiles from the assets directory in the apk. Then create a
- * ZIP archive with the (.dm) file extension (e.g. foo.dm from foo.apk) to include the
- * baseline profiles and put the DexMetadata file in the same directory with the apk.
- *
- * @param assetManager The {@link AssetManager} to use.
- * @param apkPath The path of the apk
- * @return {@code true} if the extraction is successful. Otherwise, return {@code false}.
- *
- * @see #buildDexMetadataPathForApk(String)
- */
- public static boolean extractBaselineProfilesToDexMetadataFileFromApk(AssetManager assetManager,
- String apkPath) {
- if (!ApkLiteParseUtils.isApkPath(apkPath)) {
- if (DEBUG) {
- Log.d(TAG, "It is not an apk file: " + apkPath);
- }
- return false;
- }
-
- // get the name of the DexMetadata file from the path of the apk
- final File dmFile = new File(buildDexMetadataPathForApk(apkPath));
- boolean success = false;
-
- // load profile and profile metadata from assets directory in the apk
- try (InputStream profileIs = openStreamFromAssets(assetManager,
- BASELINE_PROFILE_SOURCE_RELATIVE_PATH);
- InputStream profileMetadataIs = openStreamFromAssets(assetManager,
- BASELINE_PROFILE_METADATA_RELATIVE_PATH)) {
- // Create the zip archive file and write the baseline profiles into it.
- try (FileOutputStream fos = new FileOutputStream(dmFile)) {
- try (ZipOutputStream zipOs = new ZipOutputStream(fos)) {
- zipOs.putNextEntry(new ZipEntry(PROFILE_FILE_NAME));
- zipOs.write(profileIs.readAllBytes());
- zipOs.closeEntry();
-
- zipOs.putNextEntry(new ZipEntry(PROFILE_METADATA_FILE_NAME));
- zipOs.write(profileMetadataIs.readAllBytes());
- zipOs.closeEntry();
- success = true;
- }
- }
- } catch (IOException e) {
- if (DEBUG) {
- Log.e(TAG, "Extract baseline profiles from apk failed: " + e.getMessage());
- }
- } finally {
- if (!success) {
- if (dmFile.exists()) {
- dmFile.delete();
- }
- }
- }
- return success;
- }
-
- /**
- * Loads an {@link AutoCloseInputStream} from assets with the path.
- *
- * @param assetManager The {@link AssetManager} to use.
- * @param path The source file's relative path.
- * @return An AutoCloseInputStream in case the file was successfully read.
- * @throws IOException If anything goes wrong while opening or reading the file.
- */
- private static AutoCloseInputStream openStreamFromAssets(AssetManager assetManager, String path)
- throws IOException {
- AssetFileDescriptor descriptor = assetManager.openFd(path);
- // Based on the java doc of AssetFileDescriptor#createInputStream, it will always return
- // an AutoCloseInputStream. It should be fine we cast it from FileInputStream to
- // AutoCloseInputStream here.
- return (AutoCloseInputStream) descriptor.createInputStream();
- }
}
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 382e4f8..b7489229 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -176,6 +176,8 @@
private static native void nativeCancel(long connectionPtr);
private static native void nativeResetCancel(long connectionPtr, boolean cancelable);
private static native int nativeLastInsertRowId(long connectionPtr);
+ private static native long nativeChanges(long connectionPtr);
+ private static native long nativeTotalChanges(long connectionPtr);
private SQLiteConnection(SQLiteConnectionPool pool,
SQLiteDatabaseConfiguration configuration,
@@ -1823,11 +1825,36 @@
* @return The ROWID of the last row to be inserted under this connection.
* @hide
*/
- long lastInsertRowId() {
+ long getLastInsertRowId() {
try {
return nativeLastInsertRowId(mConnectionPtr);
} finally {
Reference.reachabilityFence(this);
}
}
+
+ /**
+ * Return the number of database changes on the current connection made by the last SQL
+ * statement
+ * @hide
+ */
+ long getLastChangedRowsCount() {
+ try {
+ return nativeChanges(mConnectionPtr);
+ } finally {
+ Reference.reachabilityFence(this);
+ }
+ }
+
+ /**
+ * Return the total number of database changes made on the current connection.
+ * @hide
+ */
+ long getTotalChangedRowsCount() {
+ try {
+ return nativeTotalChanges(mConnectionPtr);
+ } finally {
+ Reference.reachabilityFence(this);
+ }
+ }
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index eceec34..a3f8383 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -2197,7 +2197,49 @@
* @throws IllegalStateException if there is no current transaction.
*/
public long getLastInsertRowId() {
- return getThreadSession().lastInsertRowId();
+ return getThreadSession().getLastInsertRowId();
+ }
+
+ /**
+ * Return the number of database rows that were inserted, updated, or deleted by the most recent
+ * SQL statement within the current transaction.
+ *
+ * @see <a href="https://sqlite.org/c3ref/changes.html">sqlite3_changes64</a>
+ *
+ * @return The number of rows changed by the most recent sql statement
+ * @throws IllegalStateException if there is no current transaction.
+ * @hide
+ */
+ public long getLastChangedRowsCount() {
+ return getThreadSession().getLastChangedRowsCount();
+ }
+
+ /**
+ * Return the total number of database rows that have been inserted, updated, or deleted on
+ * the current connection since it was created. Due to Android's internal management of
+ * SQLite connections, the value may, or may not, include changes made in earlier
+ * transactions. Best practice is to compare values returned within a single transaction.
+ *
+ * <code><pre>
+ * database.beginTransaction();
+ * try {
+ * long initialValue = database.getTotalChangedRowsCount();
+ * // Execute SQL statements
+ * long changedRows = database.getTotalChangedRowsCount() - initialValue;
+ * // changedRows counts the total number of rows updated in the transaction.
+ * } finally {
+ * database.endTransaction();
+ * }
+ * </pre></code>
+ *
+ * @see <a href="https://sqlite.org/c3ref/changes.html">sqlite3_total_changes64</a>
+ *
+ * @return The number of rows changed on the current connection.
+ * @throws IllegalStateException if there is no current transaction.
+ * @hide
+ */
+ public long getTotalChangedRowsCount() {
+ return getThreadSession().getTotalChangedRowsCount();
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index 2379c84..ef1a9cb 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -988,9 +988,29 @@
* necessary to acquire and release the connection: the connection has already been acquired.
* @hide
*/
- long lastInsertRowId() {
+ long getLastInsertRowId() {
throwIfNoTransaction();
- return mConnection.lastInsertRowId();
+ return mConnection.getLastInsertRowId();
+ }
+
+ /**
+ * Return the number of database rows that were changed by the most recent SQL statement on
+ * this connection.
+ * @hide
+ */
+ long getLastChangedRowsCount() {
+ throwIfNoTransaction();
+ return mConnection.getLastChangedRowsCount();
+ }
+
+ /**
+ * Return the total number of database rows that were changed on the current connection, since
+ * it was created.
+ * @hide
+ */
+ long getTotalChangedRowsCount() {
+ throwIfNoTransaction();
+ return mConnection.getTotalChangedRowsCount();
}
/**
diff --git a/core/java/android/flags/BooleanFlag.java b/core/java/android/flags/BooleanFlag.java
new file mode 100644
index 0000000..d4a35b2
--- /dev/null
+++ b/core/java/android/flags/BooleanFlag.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+
+/**
+ * A flag representing a true or false value.
+ *
+ * The value will always be the same during the lifetime of the process it is read in.
+ *
+ * @hide
+ */
+public class BooleanFlag extends BooleanFlagBase {
+ private final boolean mDefault;
+
+ /**
+ * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+ * @param name A name for this flag.
+ * @param defaultValue The value of this flag if no other override is present.
+ */
+ BooleanFlag(String namespace, String name, boolean defaultValue) {
+ super(namespace, name);
+ mDefault = defaultValue;
+ }
+
+ @Override
+ @NonNull
+ public Boolean getDefault() {
+ return mDefault;
+ }
+
+ @Override
+ public BooleanFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/core/java/android/flags/BooleanFlagBase.java b/core/java/android/flags/BooleanFlagBase.java
new file mode 100644
index 0000000..985dbe3
--- /dev/null
+++ b/core/java/android/flags/BooleanFlagBase.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+
+abstract class BooleanFlagBase implements Flag<Boolean> {
+
+ private final String mNamespace;
+ private final String mName;
+ private String mLabel;
+ private String mDescription;
+ private String mCategoryName;
+
+ /**
+ * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+ * @param name A name for this flag.
+ */
+ BooleanFlagBase(String namespace, String name) {
+ mNamespace = namespace;
+ mName = name;
+ mLabel = name;
+ }
+
+ public abstract Boolean getDefault();
+
+ @Override
+ @NonNull
+ public String getNamespace() {
+ return mNamespace;
+ }
+
+ @Override
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public BooleanFlagBase defineMetaData(String label, String description, String categoryName) {
+ mLabel = label;
+ mDescription = description;
+ mCategoryName = categoryName;
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public String getLabel() {
+ return mLabel;
+ }
+
+ @Override
+ public String getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public String getCategoryName() {
+ return mCategoryName;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return getNamespace() + "." + getName() + "[" + getDefault() + "]";
+ }
+}
diff --git a/core/java/android/flags/DynamicBooleanFlag.java b/core/java/android/flags/DynamicBooleanFlag.java
new file mode 100644
index 0000000..271a8c5f4
--- /dev/null
+++ b/core/java/android/flags/DynamicBooleanFlag.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+/**
+ * A flag representing a true or false value.
+ *
+ * The value may be different from one read to the next.
+ *
+ * @hide
+ */
+public class DynamicBooleanFlag extends BooleanFlagBase implements DynamicFlag<Boolean> {
+
+ private final boolean mDefault;
+
+ /**
+ * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
+ * @param name A name for this flag.
+ * @param defaultValue The value of this flag if no other override is present.
+ */
+ DynamicBooleanFlag(String namespace, String name, boolean defaultValue) {
+ super(namespace, name);
+ mDefault = defaultValue;
+ }
+
+ @Override
+ public Boolean getDefault() {
+ return mDefault;
+ }
+
+ @Override
+ public DynamicBooleanFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/core/java/android/flags/DynamicFlag.java
similarity index 66%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to core/java/android/flags/DynamicFlag.java
index 6727fbc..68819c5 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/core/java/android/flags/DynamicFlag.java
@@ -14,12 +14,18 @@
* limitations under the License.
*/
-package com.android.server.biometrics;
+package android.flags;
/**
- * Interface for biometric operations to get camera privacy state.
+ * A flag for which the value may be different from one read to the next.
+ *
+ * @param <T> The type of value that this flag stores. E.g. Boolean or String.
+ *
+ * @hide
*/
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
+public interface DynamicFlag<T> extends Flag<T> {
+ @Override
+ default boolean isDynamic() {
+ return true;
+ }
}
diff --git a/core/java/android/flags/FeatureFlags.java b/core/java/android/flags/FeatureFlags.java
new file mode 100644
index 0000000..8d3112c
--- /dev/null
+++ b/core/java/android/flags/FeatureFlags.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class for querying constants from the system - primarily booleans.
+ *
+ * Clients using this class can define their flags and their default values in one place,
+ * can override those values on running devices for debugging and testing purposes, and can control
+ * what flags are available to be used on release builds.
+ *
+ * TODO(b/279054964): A lot. This is skeleton code right now.
+ * @hide
+ */
+public class FeatureFlags {
+ private static final String TAG = "FeatureFlags";
+ private static FeatureFlags sInstance;
+ private static final Object sInstanceLock = new Object();
+
+ private final Set<Flag<?>> mKnownFlags = new ArraySet<>();
+ private final Set<Flag<?>> mDirtyFlags = new ArraySet<>();
+
+ private IFeatureFlags mIFeatureFlags;
+ private final Map<String, Map<String, Boolean>> mBooleanOverrides = new HashMap<>();
+ private final Set<ChangeListener> mListeners = new HashSet<>();
+
+ /**
+ * Obtain a per-process instance of FeatureFlags.
+ * @return A singleton instance of {@link FeatureFlags}.
+ */
+ @NonNull
+ public static FeatureFlags getInstance() {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ sInstance = new FeatureFlags();
+ }
+ }
+
+ return sInstance;
+ }
+
+ /** See {@link FeatureFlagsFake}. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public static void setInstance(FeatureFlags instance) {
+ synchronized (sInstanceLock) {
+ sInstance = instance;
+ }
+ }
+
+ private final IFeatureFlagsCallback mIFeatureFlagsCallback = new IFeatureFlagsCallback.Stub() {
+ @Override
+ public void onFlagChange(SyncableFlag flag) {
+ for (Flag<?> f : mKnownFlags) {
+ if (flagEqualsSyncableFlag(f, flag)) {
+ if (f instanceof DynamicFlag<?>) {
+ if (f instanceof DynamicBooleanFlag) {
+ String value = flag.getValue();
+ if (value == null) { // Null means any existing overrides were erased.
+ value = ((DynamicBooleanFlag) f).getDefault().toString();
+ }
+ addBooleanOverride(flag.getNamespace(), flag.getName(), value);
+ }
+ FeatureFlags.this.onFlagChange((DynamicFlag<?>) f);
+ }
+ break;
+ }
+ }
+ }
+ };
+
+ private FeatureFlags() {
+ this(null);
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public FeatureFlags(IFeatureFlags iFeatureFlags) {
+ mIFeatureFlags = iFeatureFlags;
+
+ if (mIFeatureFlags != null) {
+ try {
+ mIFeatureFlags.registerCallback(mIFeatureFlagsCallback);
+ } catch (RemoteException e) {
+ // Shouldn't happen with things passed into tests.
+ Log.e(TAG, "Could not register callbacks!", e);
+ }
+ }
+ }
+
+ /**
+ * Construct a new {@link BooleanFlag}.
+ *
+ * Use this instead of constructing a {@link BooleanFlag} directly, as it registers the flag
+ * with the internals of the flagging system.
+ */
+ @NonNull
+ public static BooleanFlag booleanFlag(
+ @NonNull String namespace, @NonNull String name, boolean def) {
+ return getInstance().addFlag(new BooleanFlag(namespace, name, def));
+ }
+
+ /**
+ * Construct a new {@link FusedOffFlag}.
+ *
+ * Use this instead of constructing a {@link FusedOffFlag} directly, as it registers the
+ * flag with the internals of the flagging system.
+ */
+ @NonNull
+ public static FusedOffFlag fusedOffFlag(@NonNull String namespace, @NonNull String name) {
+ return getInstance().addFlag(new FusedOffFlag(namespace, name));
+ }
+
+ /**
+ * Construct a new {@link FusedOnFlag}.
+ *
+ * Use this instead of constructing a {@link FusedOnFlag} directly, as it registers the flag
+ * with the internals of the flagging system.
+ */
+ @NonNull
+ public static FusedOnFlag fusedOnFlag(@NonNull String namespace, @NonNull String name) {
+ return getInstance().addFlag(new FusedOnFlag(namespace, name));
+ }
+
+ /**
+ * Construct a new {@link DynamicBooleanFlag}.
+ *
+ * Use this instead of constructing a {@link DynamicBooleanFlag} directly, as it registers
+ * the flag with the internals of the flagging system.
+ */
+ @NonNull
+ public static DynamicBooleanFlag dynamicBooleanFlag(
+ @NonNull String namespace, @NonNull String name, boolean def) {
+ return getInstance().addFlag(new DynamicBooleanFlag(namespace, name, def));
+ }
+
+ /**
+ * Add a listener to be alerted when a {@link DynamicFlag} changes.
+ *
+ * See also {@link #removeChangeListener(ChangeListener)}.
+ *
+ * @param listener The listener to add.
+ */
+ public void addChangeListener(@NonNull ChangeListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Remove a listener that was added earlier.
+ *
+ * See also {@link #addChangeListener(ChangeListener)}.
+ *
+ * @param listener The listener to remove.
+ */
+ public void removeChangeListener(@NonNull ChangeListener listener) {
+ mListeners.remove(listener);
+ }
+
+ protected void onFlagChange(@NonNull DynamicFlag<?> flag) {
+ for (ChangeListener l : mListeners) {
+ l.onFlagChanged(flag);
+ }
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * {@link BooleanFlag} should only be used in debug builds. They do not get optimized out.
+ *
+ * The first time a flag is read, its value is cached for the lifetime of the process.
+ */
+ public boolean isEnabled(@NonNull BooleanFlag flag) {
+ return getBooleanInternal(flag);
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * Always returns false.
+ */
+ public boolean isEnabled(@NonNull FusedOffFlag flag) {
+ return false;
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * Always returns true;
+ */
+ public boolean isEnabled(@NonNull FusedOnFlag flag) {
+ return true;
+ }
+
+ /**
+ * Returns whether the supplied flag is true or not.
+ *
+ * Can return a different value for the flag each time it is called if an override comes in.
+ */
+ public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) {
+ return getBooleanInternal(flag);
+ }
+
+ private boolean getBooleanInternal(Flag<Boolean> flag) {
+ sync();
+ Map<String, Boolean> ns = mBooleanOverrides.get(flag.getNamespace());
+ Boolean value = null;
+ if (ns != null) {
+ value = ns.get(flag.getName());
+ }
+ if (value == null) {
+ throw new IllegalStateException("Boolean flag being read but was not synced: " + flag);
+ }
+
+ return value;
+ }
+
+ private <T extends Flag<?>> T addFlag(T flag) {
+ synchronized (FeatureFlags.class) {
+ mDirtyFlags.add(flag);
+ mKnownFlags.add(flag);
+ }
+ return flag;
+ }
+
+ /**
+ * Sync any known flags that have not yet been synced.
+ *
+ * This is called implicitly when any flag is read, and is not generally needed except in
+ * exceptional circumstances.
+ */
+ public void sync() {
+ synchronized (FeatureFlags.class) {
+ if (mDirtyFlags.isEmpty()) {
+ return;
+ }
+ syncInternal(mDirtyFlags);
+ mDirtyFlags.clear();
+ }
+ }
+
+ /**
+ * Called when new flags have been declared. Gives the implementation a chance to act on them.
+ *
+ * Guaranteed to be called from a synchronized, thread-safe context.
+ */
+ protected void syncInternal(Set<Flag<?>> dirtyFlags) {
+ IFeatureFlags iFeatureFlags = bind();
+ List<SyncableFlag> syncableFlags = new ArrayList<>();
+ for (Flag<?> f : dirtyFlags) {
+ syncableFlags.add(flagToSyncableFlag(f));
+ }
+
+ List<SyncableFlag> serverFlags = List.of(); // Need to initialize the list with something.
+ try {
+ // New values come back from the service.
+ serverFlags = iFeatureFlags.syncFlags(syncableFlags);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ for (Flag<?> f : dirtyFlags) {
+ boolean found = false;
+ for (SyncableFlag sf : serverFlags) {
+ if (flagEqualsSyncableFlag(f, sf)) {
+ if (f instanceof BooleanFlag || f instanceof DynamicBooleanFlag) {
+ addBooleanOverride(sf.getNamespace(), sf.getName(), sf.getValue());
+ }
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ if (f instanceof BooleanFlag) {
+ addBooleanOverride(
+ f.getNamespace(),
+ f.getName(),
+ ((BooleanFlag) f).getDefault() ? "true" : "false");
+ }
+ }
+ }
+ }
+
+ private void addBooleanOverride(String namespace, String name, String override) {
+ Map<String, Boolean> nsOverrides = mBooleanOverrides.get(namespace);
+ if (nsOverrides == null) {
+ nsOverrides = new HashMap<>();
+ mBooleanOverrides.put(namespace, nsOverrides);
+ }
+ nsOverrides.put(name, parseBoolean(override));
+ }
+
+ private SyncableFlag flagToSyncableFlag(Flag<?> f) {
+ return new SyncableFlag(
+ f.getNamespace(),
+ f.getName(),
+ f.getDefault().toString(),
+ f instanceof DynamicFlag<?>);
+ }
+
+ private IFeatureFlags bind() {
+ if (mIFeatureFlags == null) {
+ mIFeatureFlags = IFeatureFlags.Stub.asInterface(
+ ServiceManager.getService(Context.FEATURE_FLAGS_SERVICE));
+ try {
+ mIFeatureFlags.registerCallback(mIFeatureFlagsCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to listen for flag changes!");
+ }
+ }
+
+ return mIFeatureFlags;
+ }
+
+ static boolean parseBoolean(String value) {
+ // Check for a truish string.
+ boolean result = value.equalsIgnoreCase("true")
+ || value.equals("1")
+ || value.equalsIgnoreCase("t")
+ || value.equalsIgnoreCase("on");
+ if (!result) { // Expect a falsish string, else log an error.
+ if (!(value.equalsIgnoreCase("false")
+ || value.equals("0")
+ || value.equalsIgnoreCase("f")
+ || value.equalsIgnoreCase("off"))) {
+ Log.e(TAG,
+ "Tried parsing " + value + " as boolean but it doesn't look like one. "
+ + "Value expected to be one of true|false, 1|0, t|f, on|off.");
+ }
+ }
+ return result;
+ }
+
+ private static boolean flagEqualsSyncableFlag(Flag<?> f, SyncableFlag sf) {
+ return f.getName().equals(sf.getName()) && f.getNamespace().equals(sf.getNamespace());
+ }
+
+
+ /**
+ * A simpler listener that is alerted when a {@link DynamicFlag} changes.
+ *
+ * See {@link #addChangeListener(ChangeListener)}
+ */
+ public interface ChangeListener {
+ /**
+ * Called when a {@link DynamicFlag} changes.
+ *
+ * @param flag The flag that has changed.
+ */
+ void onFlagChanged(DynamicFlag<?> flag);
+ }
+}
diff --git a/core/java/android/flags/FeatureFlagsFake.java b/core/java/android/flags/FeatureFlagsFake.java
new file mode 100644
index 0000000..daedcda
--- /dev/null
+++ b/core/java/android/flags/FeatureFlagsFake.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An implementation of {@link FeatureFlags} for testing.
+ *
+ * Before you read a flag from using this Fake, you must set that flag using
+ * {@link #setFlagValue(BooleanFlagBase, boolean)}. This ensures that your tests are deterministic.
+ *
+ * If you are relying on {@link FeatureFlags#getInstance()} to access FeatureFlags in your code
+ * under test, (instead of dependency injection), you can pass an instance of this fake to
+ * {@link FeatureFlags#setInstance(FeatureFlags)}. Be sure to call that method again, passing null,
+ * to ensure hermetic testing - you don't want static state persisting between your test methods.
+ *
+ * @hide
+ */
+public class FeatureFlagsFake extends FeatureFlags {
+ private final Map<BooleanFlagBase, Boolean> mFlagValues = new HashMap<>();
+ private final Set<BooleanFlagBase> mReadFlags = new HashSet<>();
+
+ public FeatureFlagsFake(IFeatureFlags iFeatureFlags) {
+ super(iFeatureFlags);
+ }
+
+ @Override
+ public boolean isEnabled(@NonNull BooleanFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ public boolean isEnabled(@NonNull FusedOffFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ public boolean isEnabled(@NonNull FusedOnFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) {
+ return requireFlag(flag);
+ }
+
+ @Override
+ protected void syncInternal(Set<Flag<?>> dirtyFlags) {
+ }
+
+ /**
+ * Explicitly set a flag's value for reading in tests.
+ *
+ * You _must_ call this for every flag your code-under-test will read. Otherwise, an
+ * {@link IllegalStateException} will be thrown.
+ *
+ * You are able to set values for {@link FusedOffFlag} and {@link FusedOnFlag}, despite those
+ * flags having a fixed value at compile time, since unit tests should still test the state of
+ * those flags as both true and false. I.e. a flag that is off might be turned on in a future
+ * build or vice versa.
+ *
+ * You can not call this method _after_ a non-dynamic flag has been read. Non-dynamic flags
+ * are held stable in the system, so changing a value after reading would not match
+ * real-implementation behavior.
+ *
+ * Calling this method will trigger any {@link android.flags.FeatureFlags.ChangeListener}s that
+ * are registered for the supplied flag if the flag is a {@link DynamicFlag}.
+ *
+ * @param flag The BooleanFlag that you want to set a value for.
+ * @param value The value that the flag should return when accessed.
+ */
+ public void setFlagValue(@NonNull BooleanFlagBase flag, boolean value) {
+ if (!(flag instanceof DynamicBooleanFlag) && mReadFlags.contains(flag)) {
+ throw new RuntimeException(
+ "You can not set the value of a flag after it has been read. Tried to set "
+ + flag + " to " + value + " but it already " + mFlagValues.get(flag));
+ }
+ mFlagValues.put(flag, value);
+ if (flag instanceof DynamicBooleanFlag) {
+ onFlagChange((DynamicFlag<?>) flag);
+ }
+ }
+
+ private boolean requireFlag(BooleanFlagBase flag) {
+ if (!mFlagValues.containsKey(flag)) {
+ throw new IllegalStateException(
+ "Tried to access " + flag + " in test but no overrided specified. You must "
+ + "call #setFlagValue for each flag read in a test.");
+ }
+ mReadFlags.add(flag);
+
+ return mFlagValues.get(flag);
+ }
+
+}
diff --git a/core/java/android/flags/Flag.java b/core/java/android/flags/Flag.java
new file mode 100644
index 0000000..b97a4c8
--- /dev/null
+++ b/core/java/android/flags/Flag.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+
+/**
+ * Base class for constants read via {@link android.flags.FeatureFlags}.
+ *
+ * @param <T> The type of value that this flag stores. E.g. Boolean or String.
+ *
+ * @hide
+ */
+public interface Flag<T> {
+ /** The namespace for a flag. Should combine uniquely with its name. */
+ @NonNull
+ String getNamespace();
+
+ /** The name of the flag. Should combine uniquely with its namespace. */
+ @NonNull
+ String getName();
+
+ /** The value of this flag if no override has been set. Null values are not supported. */
+ @NonNull
+ T getDefault();
+
+ /** Returns true if the value of this flag can change at runtime. */
+ default boolean isDynamic() {
+ return false;
+ }
+
+ /**
+ * Add human-readable details to the flag. Flag client's are not required to set this.
+ *
+ * See {@link #getLabel()}, {@link #getDescription()}, and {@link #getCategoryName()}.
+ *
+ * @return Returns `this`, to make a fluent api.
+ */
+ Flag<T> defineMetaData(String label, String description, String categoryName);
+
+ /**
+ * A human-readable name for the flag. Defaults to {@link #getName()}
+ *
+ * See {@link #defineMetaData(String, String, String)}
+ */
+ @NonNull
+ default String getLabel() {
+ return getName();
+ }
+
+ /**
+ * A human-readable description for the flag. Defaults to null if unset.
+ *
+ * See {@link #defineMetaData(String, String, String)}
+ */
+ default String getDescription() {
+ return null;
+ }
+
+ /**
+ * A human-readable category name for the flag. Defaults to null if unset.
+ *
+ * See {@link #defineMetaData(String, String, String)}
+ */
+ default String getCategoryName() {
+ return null;
+ }
+}
diff --git a/core/java/android/flags/FusedOffFlag.java b/core/java/android/flags/FusedOffFlag.java
new file mode 100644
index 0000000..6844b8f
--- /dev/null
+++ b/core/java/android/flags/FusedOffFlag.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+/**
+ * A flag representing a false value.
+ *
+ * The flag can never be changed or overridden. It is false at compile time.
+ *
+ * @hide
+ */
+public final class FusedOffFlag extends BooleanFlagBase {
+ /**
+ * @param namespace A namespace for this flag. See {@link DeviceConfig}.
+ * @param name A name for this flag.
+ */
+ FusedOffFlag(String namespace, String name) {
+ super(namespace, name);
+ }
+
+ @Override
+ @NonNull
+ public Boolean getDefault() {
+ return false;
+ }
+
+ @Override
+ public FusedOffFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/core/java/android/flags/FusedOnFlag.java b/core/java/android/flags/FusedOnFlag.java
new file mode 100644
index 0000000..e9adba7
--- /dev/null
+++ b/core/java/android/flags/FusedOnFlag.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+import android.provider.DeviceConfig;
+
+/**
+ * A flag representing a true value.
+ *
+ * The flag can never be changed or overridden. It is true at compile time.
+ *
+ * @hide
+ */
+public final class FusedOnFlag extends BooleanFlagBase {
+ /**
+ * @param namespace A namespace for this flag. See {@link DeviceConfig}.
+ * @param name A name for this flag.
+ */
+ FusedOnFlag(String namespace, String name) {
+ super(namespace, name);
+ }
+
+ @Override
+ @NonNull
+ public Boolean getDefault() {
+ return true;
+ }
+
+ @Override
+ public FusedOnFlag defineMetaData(String label, String description, String categoryName) {
+ super.defineMetaData(label, description, categoryName);
+ return this;
+ }
+}
diff --git a/core/java/android/flags/IFeatureFlags.aidl b/core/java/android/flags/IFeatureFlags.aidl
new file mode 100644
index 0000000..3efcec9
--- /dev/null
+++ b/core/java/android/flags/IFeatureFlags.aidl
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.flags;
+
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+
+/**
+ * Binder interface for communicating with {@link com.android.server.flags.FeatureFlagsService}.
+ *
+ * This interface is used by {@link android.flags.FeatureFlags} and developers should use that to
+ * interface with the service. FeatureFlags is the "client" in this documentation.
+ *
+ * The methods allow client apps to communicate what flags they care about, and receive back
+ * current values for those flags. For stable flags, this is the finalized value until the device
+ * restarts. For {@link DynamicFlag}s, this is the last known value, though it may change in the
+ * future. Clients can listen for changes to flag values so that it can react accordingly.
+ * @hide
+ */
+interface IFeatureFlags {
+ /**
+ * Synchronize with the {@link com.android.server.flags.FeatureFlagsService} about flags of
+ * interest.
+ *
+ * The client should pass in a list of flags that it is using as {@link SyncableFlag}s, which
+ * includes what it thinks the default values of the flags are.
+ *
+ * The response will contain a list of matching SyncableFlags, whose values are set to what the
+ * value of the flags actually are. The client should update its internal state flag data to
+ * match.
+ *
+ * Generally speaking, if a flag that is passed in is new to the FeatureFlagsService, the
+ * service will cache the passed-in value, and return it back out. If, however, a different
+ * client has synced that flag with the service previously, FeatureFlagsService will return the
+ * existing cached value, which may or may not be what the current client passed in. This allows
+ * FeatureFlagsService to keep clients in agreement with one another.
+ */
+ List<SyncableFlag> syncFlags(in List<SyncableFlag> flagList);
+
+ /**
+ * Pass in an {@link IFeatureFlagsCallback} that will be called whenever a {@link DymamicFlag}
+ * changes.
+ */
+ void registerCallback(IFeatureFlagsCallback callback);
+
+ /**
+ * Remove a {@link IFeatureFlagsCallback} that was previously registered with
+ * {@link #registerCallback}.
+ */
+ void unregisterCallback(IFeatureFlagsCallback callback);
+
+ /**
+ * Query the {@link com.android.server.flags.FeatureFlagsService} for flags, but don't
+ * cache them. See {@link #syncFlags}.
+ *
+ * You almost certainly don't want this method. This is intended for the Flag Flipper
+ * application that needs to query the state of system but doesn't want to affect it by
+ * doing so. All other clients should use {@link syncFlags}.
+ */
+ List<SyncableFlag> queryFlags(in List<SyncableFlag> flagList);
+
+ /**
+ * Change a flags value in the system.
+ *
+ * This is intended for use by the Flag Flipper application.
+ */
+ void overrideFlag(in SyncableFlag flag);
+
+ /**
+ * Restore a flag to its default value.
+ *
+ * This is intended for use by the Flag Flipper application.
+ */
+ void resetFlag(in SyncableFlag flag);
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/core/java/android/flags/IFeatureFlagsCallback.aidl
similarity index 62%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to core/java/android/flags/IFeatureFlagsCallback.aidl
index 6727fbc..f708667 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/core/java/android/flags/IFeatureFlagsCallback.aidl
@@ -14,12 +14,18 @@
* limitations under the License.
*/
-package com.android.server.biometrics;
+ package android.flags;
+
+import android.flags.SyncableFlag;
/**
- * Interface for biometric operations to get camera privacy state.
+ * Callback for {@link IFeatureFlags#registerCallback} to get alerts when a {@link DynamicFlag}
+ * changes.
+ *
+ * DynamicFlags can change at run time. Stable flags will never result in a call to this method.
+ *
+ * @hide
*/
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
-}
+oneway interface IFeatureFlagsCallback {
+ void onFlagChange(in SyncableFlag flag);
+}
\ No newline at end of file
diff --git a/core/java/android/flags/OWNERS b/core/java/android/flags/OWNERS
new file mode 100644
index 0000000..fa125c4
--- /dev/null
+++ b/core/java/android/flags/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 1306523
+
+mankoff@google.com
+pixel@google.com
+
+dsandler@android.com
+
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/core/java/android/flags/SyncableFlag.aidl
similarity index 69%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to core/java/android/flags/SyncableFlag.aidl
index 6727fbc..1526ec1 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/core/java/android/flags/SyncableFlag.aidl
@@ -14,12 +14,9 @@
* limitations under the License.
*/
-package com.android.server.biometrics;
+package android.flags;
/**
- * Interface for biometric operations to get camera privacy state.
+ * A parcelable data class for serializing {@link Flag} across a Binder.
*/
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
-}
+parcelable SyncableFlag;
\ No newline at end of file
diff --git a/core/java/android/flags/SyncableFlag.java b/core/java/android/flags/SyncableFlag.java
new file mode 100644
index 0000000..449bcc3c
--- /dev/null
+++ b/core/java/android/flags/SyncableFlag.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.flags;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public final class SyncableFlag implements Parcelable {
+ private final String mNamespace;
+ private final String mName;
+ private final String mValue;
+ private final boolean mDynamic;
+ private final boolean mOverridden;
+
+ public SyncableFlag(
+ @NonNull String namespace,
+ @NonNull String name,
+ @NonNull String value,
+ boolean dynamic) {
+ this(namespace, name, value, dynamic, false);
+ }
+
+ public SyncableFlag(
+ @NonNull String namespace,
+ @NonNull String name,
+ @NonNull String value,
+ boolean dynamic,
+ boolean overridden
+ ) {
+ mNamespace = namespace;
+ mName = name;
+ mValue = value;
+ mDynamic = dynamic;
+ mOverridden = overridden;
+ }
+
+ @NonNull
+ public String getNamespace() {
+ return mNamespace;
+ }
+
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ @NonNull
+ public String getValue() {
+ return mValue;
+ }
+
+ public boolean isDynamic() {
+ return mDynamic;
+ }
+
+ public boolean isOverridden() {
+ return mOverridden;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<SyncableFlag> CREATOR = new Parcelable.Creator<>() {
+ public SyncableFlag createFromParcel(Parcel in) {
+ return new SyncableFlag(
+ in.readString(),
+ in.readString(),
+ in.readString(),
+ in.readBoolean(),
+ in.readBoolean());
+ }
+
+ public SyncableFlag[] newArray(int size) {
+ return new SyncableFlag[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mNamespace);
+ dest.writeString(mName);
+ dest.writeString(mValue);
+ dest.writeBoolean(mDynamic);
+ dest.writeBoolean(mOverridden);
+ }
+
+ @Override
+ public String toString() {
+ return getNamespace() + "." + getName() + "[" + getValue() + "]";
+ }
+}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 2e40f60..912e8df 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -144,6 +144,7 @@
private Context mContext;
private IAuthService mService;
+ // LINT.IfChange
/**
* Creates a builder for a {@link BiometricPrompt} dialog.
* @param context The {@link Context} that will be used to build the prompt.
@@ -417,6 +418,7 @@
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL})
public Builder setDisallowBiometricsIfPolicyExists(boolean checkDevicePolicyManager) {
mPromptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicyManager);
return this;
@@ -429,6 +431,7 @@
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL})
public Builder setReceiveSystemEvents(boolean set) {
mPromptInfo.setReceiveSystemEvents(set);
return this;
@@ -442,6 +445,7 @@
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
public Builder setIgnoreEnrollmentState(boolean ignoreEnrollmentState) {
mPromptInfo.setIgnoreEnrollmentState(ignoreEnrollmentState);
return this;
@@ -454,10 +458,12 @@
* @hide
*/
@NonNull
+ @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
public Builder setIsForLegacyFingerprintManager(int sensorId) {
mPromptInfo.setIsForLegacyFingerprintManager(sensorId);
return this;
}
+ // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java)
/**
* Creates a {@link BiometricPrompt}.
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 02aad1d..e275078 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -113,6 +113,7 @@
dest.writeBoolean(mIsForLegacyFingerprintManager);
}
+ // LINT.IfChange
public boolean containsTestConfigurations() {
if (mIsForLegacyFingerprintManager
&& mAllowedSensorIds.size() == 1
@@ -122,6 +123,10 @@
return true;
} else if (mAllowBackgroundAuthentication) {
return true;
+ } else if (mIsForLegacyFingerprintManager) {
+ return true;
+ } else if (mIgnoreEnrollmentState) {
+ return true;
}
return false;
}
@@ -144,6 +149,7 @@
}
return false;
}
+ // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
// Setters
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
index d41a428..e91e04e 100644
--- a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java
@@ -28,7 +28,6 @@
import com.android.internal.vibrator.persistence.XmlConstants;
import com.android.internal.vibrator.persistence.XmlParserException;
import com.android.internal.vibrator.persistence.XmlReader;
-import com.android.internal.vibrator.persistence.XmlSerializedVibration;
import com.android.modules.utils.TypedXmlPullParser;
import org.xmlpull.v1.XmlPullParser;
@@ -178,25 +177,62 @@
// Ensure XML starts with expected root tag.
XmlReader.readDocumentStartTag(parser, XmlConstants.TAG_VIBRATION);
- int parserFlags = 0;
- if ((flags & FLAG_ALLOW_HIDDEN_APIS) != 0) {
- parserFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS;
- }
-
// Parse root tag as a vibration effect.
- XmlSerializedVibration<VibrationEffect> serializedVibration =
- VibrationEffectXmlParser.parseTag(parser, parserFlags);
+ VibrationEffect effect = parseTag(parser, flags);
// Ensure XML ends after root tag is consumed.
XmlReader.readDocumentEndTag(parser);
- return serializedVibration.deserialize();
- } catch (XmlParserException e) {
+ return effect;
+ } catch (XmlParserException | VibrationXmlParserException e) {
Slog.w(TAG, "Error parsing vibration XML", e);
return null;
}
}
+ /**
+ * Parses XML content from given open {@link TypedXmlPullParser} into a {@link VibrationEffect}.
+ *
+ * <p>The provided parser should be pointing to a start of a valid vibration XML (i.e. to a
+ * start <vibration> tag). No other parser position, including start of document, is considered
+ * valid.
+ *
+ * <p>This method parses as long as it reads a valid vibration XML, and until an end vibration
+ * tag. After a successful parsing, the parser will point to the end vibration tag (i.e. to a
+ * </vibration> tag).
+ *
+ * @throws IOException error parsing from given {@link TypedXmlPullParser}.
+ * @throws VibrationXmlParserException if the XML tag cannot be parsed into a
+ * {@link VibrationEffect}. The given {@code parser} might be pointing to a child XML tag
+ * that caused the parser failure.
+ *
+ * @hide
+ */
+ @NonNull
+ public static VibrationEffect parseTag(@NonNull TypedXmlPullParser parser, @Flags int flags)
+ throws IOException, VibrationXmlParserException {
+ int parserFlags = 0;
+ if ((flags & VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS) != 0) {
+ parserFlags |= XmlConstants.FLAG_ALLOW_HIDDEN_APIS;
+ }
+ try {
+ return VibrationEffectXmlParser.parseTag(parser, parserFlags).deserialize();
+ } catch (XmlParserException e) {
+ throw new VibrationXmlParserException("Error parsing vibration effect.", e);
+ }
+ }
+
+ /**
+ * Represents an error while parsing a vibration XML input.
+ *
+ * @hide
+ */
+ public static final class VibrationXmlParserException extends Exception {
+ private VibrationXmlParserException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
private VibrationXmlParser() {
}
}
diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS
index 28a24203..0809de2 100644
--- a/core/java/android/print/OWNERS
+++ b/core/java/android/print/OWNERS
@@ -1,4 +1,4 @@
# Bug component: 47273
-svetoslavganov@android.com
-svetoslavganov@google.com
+anothermark@google.com
+kumarashishg@google.com
diff --git a/core/java/android/print/pdf/OWNERS b/core/java/android/print/pdf/OWNERS
deleted file mode 100644
index 28a24203..0000000
--- a/core/java/android/print/pdf/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# Bug component: 47273
-
-svetoslavganov@android.com
-svetoslavganov@google.com
diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS
index 28a24203..0809de2 100644
--- a/core/java/android/printservice/OWNERS
+++ b/core/java/android/printservice/OWNERS
@@ -1,4 +1,4 @@
# Bug component: 47273
-svetoslavganov@android.com
-svetoslavganov@google.com
+anothermark@google.com
+kumarashishg@google.com
diff --git a/core/java/android/printservice/recommendation/OWNERS b/core/java/android/printservice/recommendation/OWNERS
deleted file mode 100644
index 28a24203..0000000
--- a/core/java/android/printservice/recommendation/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# Bug component: 47273
-
-svetoslavganov@android.com
-svetoslavganov@google.com
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 06cce11..cb46674 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4847,6 +4847,15 @@
public static final String PEAK_REFRESH_RATE = "peak_refresh_rate";
/**
+ * Control whether to stay awake on fold
+ *
+ * If this isn't set, the system falls back to a device specific default.
+ * @hide
+ */
+ @Readable
+ public static final String STAY_AWAKE_ON_FOLD = "stay_awake_on_fold";
+
+ /**
* The amount of time in milliseconds before the device goes to sleep or begins
* to dream after a period of inactivity. This value is also known as the
* user activity timeout period since the screen isn't necessarily turned off
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index cc3a662..e47fab4 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -868,6 +868,11 @@
* This will trigger a {@link #onComputeColors()} call.
*/
public void notifyColorsChanged() {
+ if (mDestroyed) {
+ Log.i(TAG, "Ignoring notifyColorsChanged(), Engine has already been destroyed.");
+ return;
+ }
+
final long now = mClockFunction.get();
if (now - mLastColorInvalidation < NOTIFY_COLORS_RATE_LIMIT_MS) {
Log.w(TAG, "This call has been deferred. You should only call "
@@ -2231,7 +2236,11 @@
}
}
- void detach() {
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public void detach() {
if (mDestroyed) {
return;
}
@@ -2449,6 +2458,14 @@
}
public void reportShown() {
+ if (mEngine == null) {
+ Log.i(TAG, "Can't report null engine as shown.");
+ return;
+ }
+ if (mEngine.mDestroyed) {
+ Log.i(TAG, "Engine was destroyed before we could draw.");
+ return;
+ }
if (!mShownReported) {
mShownReported = true;
Trace.beginSection("WPMS.mConnection.engineShown");
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a626775..6d9f156 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -21908,6 +21908,8 @@
mCurrentAnimation = null;
if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+ removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+ removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
hideTooltip();
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 52710bf..f1537ca 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -880,22 +880,23 @@
int LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP = 600;
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app can be opted-in or opted-out
- * from the compatibility treatment that avoids {@link
- * android.app.Activity#setRequestedOrientation} loops. The loop can be trigerred by
- * ignoreRequestedOrientation display setting enabled on the device or by the landscape natural
- * orientation of the device.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app can be opted-in or opted-out from the
+ * compatibility treatment that avoids {@link android.app.Activity#setRequestedOrientation
+ * Activity#setRequestedOrientation()} loops. Loops can be triggered by the OEM-configured
+ * ignore requested orientation display setting (on Android 12 (API level 31) and higher) or by
+ * the landscape natural orientation of the device.
*
* <p>The treatment is disabled by default but device manufacturers can enable the treatment
* using their discretion to improve display compatibility.
*
- * <p>With this property set to {@code true}, the system could ignore {@link
- * android.app.Activity#setRequestedOrientation} call from an app if one of the following
- * conditions are true:
+ * <p>With this property set to {@code true}, the system could ignore
+ * {@link android.app.Activity#setRequestedOrientation Activity#setRequestedOrientation()} call
+ * from an app if one of the following conditions are true:
* <ul>
- * <li>Activity is relaunching due to the previous {@link
- * android.app.Activity#setRequestedOrientation} call.
+ * <li>Activity is relaunching due to the previous
+ * {@link android.app.Activity#setRequestedOrientation Activity#setRequestedOrientation()}
+ * call.
* <li>Camera compatibility force rotation treatment is active for the package.
* </ul>
*
@@ -919,14 +920,16 @@
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
* for an app to inform the system that the app can be opted-out from the compatibility
- * treatment that avoids {@link android.app.Activity#setRequestedOrientation} loops. The loop
- * can be trigerred by ignoreRequestedOrientation display setting enabled on the device or
- * by the landscape natural orientation of the device.
+ * treatment that avoids {@link android.app.Activity#setRequestedOrientation
+ * Activity#setRequestedOrientation()} loops. Loops can be triggered by the OEM-configured
+ * ignore requested orientation display setting (on Android 12 (API level 31) and higher) or by
+ * the landscape natural orientation of the device.
*
- * <p>The system could ignore {@link android.app.Activity#setRequestedOrientation}
- * call from an app if both of the following conditions are true:
+ * <p>The system could ignore {@link android.app.Activity#setRequestedOrientation
+ * Activity#setRequestedOrientation()} call from an app if both of the following conditions are
+ * true:
* <ul>
- * <li>Activity has requested orientation more than 2 times within 1-second timer
+ * <li>Activity has requested orientation more than two times within one-second timer
* <li>Activity is not letterboxed for fixed orientation
* </ul>
*
@@ -953,23 +956,21 @@
"android.window.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that it needs to be opted-out from the
- * compatibility treatment that sandboxes {@link android.view.View} API.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that it needs to be opted-out from the compatibility
+ * treatment that sandboxes the {@link android.view.View View} API.
*
* <p>The treatment can be enabled by device manufacturers for applications which misuse
- * {@link android.view.View} APIs by expecting that
- * {@link android.view.View#getLocationOnScreen},
- * {@link android.view.View#getBoundsOnScreen},
- * {@link android.view.View#getWindowVisibleDisplayFrame},
- * {@link android.view.View#getWindowDisplayFrame}
+ * {@link android.view.View View} APIs by expecting that
+ * {@link android.view.View#getLocationOnScreen View#getLocationOnScreen()} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame View#getWindowVisibleDisplayFrame()}
* return coordinates as if an activity is positioned in the top-left corner of the screen, with
- * left coordinate equal to 0. This may not be the case for applications in multi-window and in
+ * left coordinate equal to 0. This may not be the case for applications in multi-window and
* letterbox modes.
*
* <p>Setting this property to {@code false} informs the system that the application must be
- * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
- * if the device manufacturer has opted the app into the treatment.
+ * opted-out from the "Sandbox View API to Activity bounds" treatment even if the device
+ * manufacturer has opted the app into the treatment.
*
* <p>Not setting this property at all, or setting this property to {@code true} has no effect.
*
@@ -987,12 +988,11 @@
"android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the application can be opted-in or opted-out
- * from the compatibility treatment that enables sending a fake focus event for unfocused
- * resumed split screen activities. This is needed because some game engines wait to get
- * focus before drawing the content of the app which isn't guaranteed by default in multi-window
- * modes.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the application can be opted-in or opted-out from the
+ * compatibility treatment that enables sending a fake focus event for unfocused resumed
+ * split-screen activities. This is needed because some game engines wait to get focus before
+ * drawing the content of the app which isn't guaranteed by default in multi-window mode.
*
* <p>Device manufacturers can enable this treatment using their discretion on a per-device
* basis to improve display compatibility. The treatment also needs to be specifically enabled
@@ -1022,9 +1022,9 @@
String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be excluded from the
- * camera compatibility force rotation treatment.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be excluded from the camera compatibility
+ * force rotation treatment.
*
* <p>The camera compatibility treatment aligns orientations of portrait app window and natural
* orientation of the device and set opposite to natural orientation for a landscape app
@@ -1034,10 +1034,11 @@
* rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to
* camera and is removed once camera is closed.
*
- * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
- * for more details).
+ * <p>The camera compatibility can be enabled by device manufacturers on displays that have the
+ * ignore requested orientation display setting enabled (enables compatibility mode for fixed
+ * orientation on Android 12 (API level 31) or higher; see
+ * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
+ * letterboxing</a> for more details).
*
* <p>With this property set to {@code true} or unset, the system may apply the force rotation
* treatment to fixed orientation activities. Device manufacturers can exclude packages from the
@@ -1060,9 +1061,9 @@
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be excluded
- * from the activity "refresh" after the camera compatibility force rotation treatment.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be excluded from the activity "refresh"
+ * after the camera compatibility force rotation treatment.
*
* <p>The camera compatibility treatment aligns orientations of portrait app window and natural
* orientation of the device and set opposite to natural orientation for a landscape app
@@ -1079,10 +1080,11 @@
* camera preview and can lead to sideways or stretching issues persisting even after force
* rotation.
*
- * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
- * for more details).
+ * <p>The camera compatibility can be enabled by device manufacturers on displays that have the
+ * ignore requested orientation display setting enabled (enables compatibility mode for fixed
+ * orientation on Android 12 (API level 31) or higher; see
+ * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
+ * letterboxing</a> for more details).
*
* <p>With this property set to {@code true} or unset, the system may "refresh" activity after
* the force rotation treatment. Device manufacturers can exclude packages from the "refresh"
@@ -1105,10 +1107,10 @@
"android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the activity should be or shouldn't be
- * "refreshed" after the camera compatibility force rotation treatment using "paused ->
- * resumed" cycle rather than "stopped -> resumed".
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the activity should be or shouldn't be "refreshed" after
+ * the camera compatibility force rotation treatment using "paused -> resumed" cycle rather than
+ * "stopped -> resumed".
*
* <p>The camera compatibility treatment aligns orientations of portrait app window and natural
* orientation of the device and set opposite to natural orientation for a landscape app
@@ -1124,10 +1126,11 @@
* values in apps (e.g., display or camera rotation) that influence camera preview and can lead
* to sideways or stretching issues persisting even after force rotation.
*
- * <p>The camera compatibility can be enabled by device manufacturers on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
- * for more details).
+ * <p>The camera compatibility can be enabled by device manufacturers on displays that have the
+ * ignore requested orientation display setting enabled (enables compatibility mode for fixed
+ * orientation on Android 12 (API level 31) or higher; see
+ * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
+ * letterboxing</a> for more details).
*
* <p>Device manufacturers can override packages to "refresh" via "resumed -> paused -> resumed"
* cycle using their discretion to improve display compatibility.
@@ -1153,22 +1156,23 @@
"android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be excluded from the
- * compatibility override for orientation set by the device manufacturer. When the orientation
- * override is applied it can:
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be excluded from the compatibility
+ * override for orientation set by the device manufacturer. When the orientation override is
+ * applied it can:
* <ul>
* <li>Replace the specific orientation requested by the app with another selected by the
- device manufacturer, e.g. replace undefined requested by the app with portrait.
+ device manufacturer; for example, replace undefined requested by the app with portrait.
* <li>Always use an orientation selected by the device manufacturer.
* <li>Do one of the above but only when camera connection is open.
* </ul>
*
- * <p>This property is different from {@link PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION}
+ * <p>This property is different from {@link #PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION}
* (which is used to avoid orientation loops caused by the incorrect use of {@link
- * android.app.Activity#setRequestedOrientation}) because this property overrides the app to an
- * orientation selected by the device manufacturer rather than ignoring one of orientation
- * requests coming from the app while respecting the previous one.
+ * android.app.Activity#setRequestedOrientation Activity#setRequestedOrientation()}) because
+ * this property overrides the app to an orientation selected by the device manufacturer rather
+ * than ignoring one of orientation requests coming from the app while respecting the previous
+ * one.
*
* <p>With this property set to {@code true} or unset, device manufacturers can override
* orientation for the app using their discretion to improve display compatibility.
@@ -1190,10 +1194,10 @@
"android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be opted-out from the
- * compatibility override that fixes display orientation to landscape natural orientation when
- * an activity is fullscreen.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be opted-out from the compatibility
+ * override that fixes display orientation to landscape natural orientation when an activity is
+ * fullscreen.
*
* <p>When this compat override is enabled and while display is fixed to the landscape natural
* orientation, the orientation requested by the activity will be still respected by bounds
@@ -1202,16 +1206,17 @@
* lanscape natural orientation.
*
* <p>The treatment is disabled by default but device manufacturers can enable the treatment
- * using their discretion to improve display compatibility on the displays that have
- * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed
- * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a>
- * for more details).
+ * using their discretion to improve display compatibility on displays that have the ignore
+ * orientation request display setting enabled by OEMs on the device (enables compatibility mode
+ * for fixed orientation on Android 12 (API level 31) or higher; see
+ * <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced
+ * letterboxing</a> for more details).
*
* <p>With this property set to {@code true} or unset, the system wiil use landscape display
* orientation when the following conditions are met:
* <ul>
* <li>Natural orientation of the display is landscape
- * <li>ignoreOrientationRequest display setting is enabled
+ * <li>ignore requested orientation display setting is enabled
* <li>Activity is fullscreen.
* <li>Device manufacturer enabled the treatment.
* </ul>
@@ -1233,9 +1238,9 @@
"android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be opted-out from the
- * compatibility override that changes the min aspect ratio.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be opted-out from the compatibility
+ * override that changes the min aspect ratio.
*
* <p>When this compat override is enabled the min aspect ratio given in the app's manifest can
* be overridden by the device manufacturer using their discretion to improve display
@@ -1264,14 +1269,14 @@
"android.window.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that the app should be opted-out from the
- * compatibility overrides that change the resizability of the app.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * for an app to inform the system that the app should be opted-out from the compatibility
+ * overrides that change the resizability of the app.
*
* <p>When these compat overrides are enabled they force the packages they are applied to to be
- * resizable / unresizable. If the app is forced to be resizable this won't change whether
- * the app can be put into multi-windowing mode, but allow the app to resize without going into
- * size-compat mode when the window container resizes, such as display size change or screen
+ * resizable/unresizable. If the app is forced to be resizable this won't change whether the app
+ * can be put into multi-windowing mode, but allow the app to resize without going into size
+ * compatibility mode when the window container resizes, such as display size change or screen
* rotation.
*
* <p>Setting this property to {@code false} informs the system that the app must be
@@ -1320,34 +1325,29 @@
}
/**
- * Application-level
- * {@link android.content.pm.PackageManager.Property PackageManager.Property}
- * tag that specifies whether OEMs are permitted to provide activity
- * embedding split-rule configurations on behalf of the app.
+ * Application-level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * tag that specifies whether OEMs are permitted to provide activity embedding split-rule
+ * configurations on behalf of the app.
*
- * <p>If {@code true}, the system is permitted to override the app's
- * windowing behavior and implement activity embedding split rules, such as
- * displaying activities side by side. A system override informs the app
- * that the activity embedding APIs are disabled so the app will not provide
- * its own activity embedding rules, which would conflict with the system's
+ * <p>If {@code true}, the system is permitted to override the app's windowing behavior and
+ * implement activity embedding split rules, such as displaying activities side by side. A
+ * system override informs the app that the activity embedding APIs are disabled so the app
+ * doesn't provide its own activity embedding rules, which would conflict with the system's
* rules.
*
- * <p>If {@code false}, the system is not permitted to override the
- * windowing behavior of the app. Set the property to {@code false} if the
- * app provides its own activity embedding split rules, or if you want to
- * prevent the system override for any other reason.
+ * <p>If {@code false}, the system is not permitted to override the windowing behavior of the
+ * app. Set the property to {@code false} if the app provides its own activity embedding split
+ * rules, or if you want to prevent the system override for any other reason.
*
* <p>The default value is {@code false}.
*
- * <p class="note"><b>Note:</b> Refusal to permit the system override is not
- * enforceable. OEMs can override the app's activity embedding
- * implementation whether or not this property is specified and set to
- * <code>false</code>. The property is, in effect, a hint to OEMs.
+ * <p class="note"><b>Note:</b> Refusal to permit the system override is not enforceable. OEMs
+ * can override the app's activity embedding implementation whether or not this property is
+ * specified and set to {@code false}. The property is, in effect, a hint to OEMs.
*
- * <p>OEMs can implement activity embedding on any API level. The best
- * practice for apps is to always explicitly set this property in the app
- * manifest file regardless of targeted API level rather than rely on the
- * default value.
+ * <p>OEMs can implement activity embedding on any API level. The best practice for apps is to
+ * always explicitly set this property in the app manifest file regardless of targeted API level
+ * rather than rely on the default value.
*
* <p><b>Syntax:</b>
* <pre>
@@ -1362,14 +1362,15 @@
"android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
/**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} that an app can specify to inform the system that the app is ActivityEmbedding
- * split feature enabled.
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * that an app can specify to inform the system that the app is activity embedding split feature
+ * enabled.
*
* <p>With this property, the system could provide custom behaviors for the apps that are
- * ActivityEmbedding split feature enabled. For example, the fixed-portrait orientation
+ * activity embedding split feature enabled. For example, the fixed-portrait orientation
* requests of the activities could be ignored by the system in order to provide seamless
- * ActivityEmbedding split experiences while holding the large-screen devices in landscape mode.
+ * activity embedding split experiences while holding large screen devices in landscape
+ * orientation.
*
* <p><b>Syntax:</b>
* <pre>
diff --git a/core/java/android/widget/RemoteViews.aidl b/core/java/android/widget/RemoteViews.aidl
index ec86410..6a5fc03 100644
--- a/core/java/android/widget/RemoteViews.aidl
+++ b/core/java/android/widget/RemoteViews.aidl
@@ -17,3 +17,4 @@
package android.widget;
parcelable RemoteViews;
+parcelable RemoteViews.RemoteCollectionItems;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index a12656d..66aa66c 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -33,16 +33,19 @@
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.ActivityThread;
+import android.app.AppGlobals;
import android.app.Application;
import android.app.LoadedApk;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.appwidget.AppWidgetHostView;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.ColorStateList;
@@ -65,10 +68,12 @@
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.Process;
+import android.os.RemoteException;
import android.os.StrictMode;
import android.os.UserHandle;
import android.system.Os;
@@ -98,8 +103,10 @@
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.android.internal.R;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.util.ContrastColorUtil;
import com.android.internal.util.Preconditions;
+import com.android.internal.widget.IRemoteViewsFactory;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
@@ -124,7 +131,9 @@
import java.util.Map;
import java.util.Objects;
import java.util.Stack;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -324,6 +333,13 @@
(clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
/**
+ * The maximum waiting time for remote adapter conversion in milliseconds
+ *
+ * @hide
+ */
+ private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 2000;
+
+ /**
* Application that hosts the remote views.
*
* @hide
@@ -1042,28 +1058,96 @@
}
private class SetRemoteCollectionItemListAdapterAction extends Action {
- private final RemoteCollectionItems mItems;
+ private @NonNull CompletableFuture<RemoteCollectionItems> mItemsFuture;
- SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) {
+ SetRemoteCollectionItemListAdapterAction(@IdRes int id,
+ @NonNull RemoteCollectionItems items) {
viewId = id;
- mItems = items;
- mItems.setHierarchyRootData(getHierarchyRootData());
+ items.setHierarchyRootData(getHierarchyRootData());
+ mItemsFuture = CompletableFuture.completedFuture(items);
+ }
+
+ SetRemoteCollectionItemListAdapterAction(@IdRes int id, Intent intent) {
+ viewId = id;
+ mItemsFuture = getItemsFutureFromIntentWithTimeout(intent);
+ setHierarchyRootData(getHierarchyRootData());
+ }
+
+ private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout(
+ Intent intent) {
+ if (intent == null) {
+ Log.e(LOG_TAG, "Null intent received when generating adapter future");
+ return CompletableFuture.completedFuture(new RemoteCollectionItems
+ .Builder().build());
+ }
+
+ final Context context = ActivityThread.currentApplication();
+ final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
+
+ context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
+ result.defaultExecutor(), new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName componentName,
+ IBinder iBinder) {
+ RemoteCollectionItems items;
+ try {
+ items = IRemoteViewsFactory.Stub.asInterface(iBinder)
+ .getRemoteCollectionItems();
+ } catch (RemoteException re) {
+ items = new RemoteCollectionItems.Builder().build();
+ Log.e(LOG_TAG, "Error getting collection items from the factory",
+ re);
+ } finally {
+ context.unbindService(this);
+ }
+
+ result.complete(items);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName componentName) { }
+ });
+
+ result.completeOnTimeout(
+ new RemoteCollectionItems.Builder().build(),
+ MAX_ADAPTER_CONVERSION_WAITING_TIME_MS, TimeUnit.MILLISECONDS);
+
+ return result;
}
SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
viewId = parcel.readInt();
- mItems = new RemoteCollectionItems(parcel, getHierarchyRootData());
+ mItemsFuture = CompletableFuture.completedFuture(
+ new RemoteCollectionItems(parcel, getHierarchyRootData()));
}
@Override
public void setHierarchyRootData(HierarchyRootData rootData) {
- mItems.setHierarchyRootData(rootData);
+ mItemsFuture = mItemsFuture
+ .thenApply(rc -> {
+ rc.setHierarchyRootData(rootData);
+ return rc;
+ });
+ }
+
+ private static RemoteCollectionItems getCollectionItemsFromFuture(
+ CompletableFuture<RemoteCollectionItems> itemsFuture) {
+ RemoteCollectionItems items;
+ try {
+ items = itemsFuture.get();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Error getting collection items from future", e);
+ items = new RemoteCollectionItems.Builder().build();
+ }
+
+ return items;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(viewId);
- mItems.writeToParcel(dest, flags, /* attached= */ true);
+ RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+ items.writeToParcel(dest, flags, /* attached= */ true);
}
@Override
@@ -1072,6 +1156,8 @@
View target = root.findViewById(viewId);
if (target == null) return;
+ RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+
// Ensure that we are applying to an AppWidget root
if (!(rootParent instanceof AppWidgetHostView)) {
Log.e(LOG_TAG, "setRemoteAdapter can only be used for "
@@ -1092,10 +1178,10 @@
// recycling in setAdapter, so we must call setAdapter again if the number of view types
// increases.
if (adapter instanceof RemoteCollectionItemsAdapter
- && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) {
+ && adapter.getViewTypeCount() >= items.getViewTypeCount()) {
try {
((RemoteCollectionItemsAdapter) adapter).setData(
- mItems, params.handler, params.colorResources);
+ items, params.handler, params.colorResources);
} catch (Throwable throwable) {
// setData should never failed with the validation in the items builder, but if
// it does, catch and rethrow.
@@ -1105,7 +1191,7 @@
}
try {
- adapterView.setAdapter(new RemoteCollectionItemsAdapter(mItems,
+ adapterView.setAdapter(new RemoteCollectionItemsAdapter(items,
params.handler, params.colorResources));
} catch (Throwable throwable) {
// This could throw if the AdapterView somehow doesn't accept BaseAdapter due to
@@ -4682,6 +4768,12 @@
*/
@Deprecated
public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
+ if (AppGlobals.getIntCoreSetting(
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
+ SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) == 1) {
+ addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
+ return;
+ }
addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
}
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index 214e5cc..d4f4d19 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -43,6 +43,13 @@
private static final Object sLock = new Object();
/**
+ * Used for determining the maximum number of entries to retrieve from RemoteViewsFactory
+ *
+ * @hide
+ */
+ private static final int MAX_NUM_ENTRY = 25;
+
+ /**
* An interface for an adapter between a remote collection view (ListView, GridView, etc) and
* the underlying data for that view. The implementor is responsible for making a RemoteView
* for each item in the data set. This interface is a thin wrapper around {@link Adapter}.
@@ -227,6 +234,30 @@
}
}
+ @Override
+ public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+ RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems
+ .Builder().build();
+
+ try {
+ RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
+ new RemoteViews.RemoteCollectionItems.Builder();
+ mFactory.onDataSetChanged();
+
+ itemsBuilder.setHasStableIds(mFactory.hasStableIds());
+ final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY);
+ for (int i = 0; i < numOfEntries; i++) {
+ itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i));
+ }
+
+ items = itemsBuilder.build();
+ } catch (Exception ex) {
+ Thread t = Thread.currentThread();
+ Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ }
+ return items;
+ }
+
private RemoteViewsFactory mFactory;
private boolean mIsCreated;
}
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
index bdbce13..be88e53 100644
--- a/core/java/android/window/WindowInfosListenerForTest.java
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.InputConfig;
@@ -78,14 +79,21 @@
*/
public final boolean isVisible;
+ /**
+ * Return the transform to get the bounds from display space into window space.
+ */
+ @NonNull
+ public final Matrix transform;
+
WindowInfo(@NonNull IBinder windowToken, @NonNull String name, int displayId,
- @NonNull Rect bounds, int inputConfig) {
+ @NonNull Rect bounds, int inputConfig, @NonNull Matrix transform) {
this.windowToken = windowToken;
this.name = name;
this.displayId = displayId;
this.bounds = bounds;
this.isTrustedOverlay = (inputConfig & InputConfig.TRUSTED_OVERLAY) != 0;
this.isVisible = (inputConfig & InputConfig.NOT_VISIBLE) == 0;
+ this.transform = transform;
}
@Override
@@ -94,7 +102,8 @@
+ ", frame=" + bounds
+ ", isVisible=" + isVisible
+ ", isTrustedOverlay=" + isTrustedOverlay
- + ", token=" + windowToken;
+ + ", token=" + windowToken
+ + ", transform=" + transform;
}
}
@@ -155,7 +164,7 @@
var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight,
handle.frameBottom);
windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, handle.displayId,
- bounds, handle.inputConfig));
+ bounds, handle.inputConfig, handle.transform));
}
return windowInfos;
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 632208c..849e0b3 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -23,6 +23,7 @@
import android.content.ContextWrapper;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.res.TypedArray;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -33,6 +34,7 @@
import androidx.annotation.VisibleForTesting;
+
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -62,6 +64,9 @@
.getInt("persist.wm.debug.predictive_back", 1) != 0;
private static final boolean ALWAYS_ENFORCE_PREDICTIVE_BACK = SystemProperties
.getInt("persist.wm.debug.predictive_back_always_enforce", 0) != 0;
+ private static final boolean PREDICTIVE_BACK_FALLBACK_WINDOW_ATTRIBUTE =
+ SystemProperties.getInt("persist.wm.debug.predictive_back_fallback_window_attribute", 0)
+ != 0;
@Nullable
private ImeOnBackInvokedDispatcher mImeDispatcher;
@@ -500,6 +505,31 @@
applicationInfo.packageName,
requestsPredictiveBack));
}
+
+ if (PREDICTIVE_BACK_FALLBACK_WINDOW_ATTRIBUTE && !requestsPredictiveBack) {
+ // Compatibility check for legacy window style flag used by Wear OS.
+ // Note on compatibility behavior:
+ // 1. windowSwipeToDismiss should be respected for all apps not opted in.
+ // 2. windowSwipeToDismiss should be true for all apps not opted in, which
+ // enables the PB animation for them.
+ // 3. windowSwipeToDismiss=false should be respected for apps not opted in,
+ // which disables PB & onBackPressed caused by BackAnimController's
+ // setTrigger(true)
+ TypedArray windowAttr =
+ context.obtainStyledAttributes(
+ new int[] {android.R.attr.windowSwipeToDismiss});
+ boolean windowSwipeToDismiss = true;
+ if (windowAttr.getIndexCount() > 0) {
+ windowSwipeToDismiss = windowAttr.getBoolean(0, true);
+ }
+ windowAttr.recycle();
+
+ if (DEBUG) {
+ Log.i(TAG, "falling back to windowSwipeToDismiss: " + windowSwipeToDismiss);
+ }
+
+ requestsPredictiveBack = windowSwipeToDismiss;
+ }
}
return requestsPredictiveBack;
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 385612d..a2c4b23 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -532,6 +532,17 @@
public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot";
/**
+ * (boolean) Whether to enable the adapter conversion in RemoteViews
+ */
+ public static final String REMOTEVIEWS_ADAPTER_CONVERSION = "remoteviews_adapter_conversion";
+
+ /**
+ * Default value for whether the adapter conversion is enabled or not. This is set for
+ * RemoteViews and should not be a common practice.
+ */
+ public static final boolean REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT = false;
+
+ /**
* (boolean) Whether the task manager should show a stop button if the app is allowlisted
* by the user.
*/
diff --git a/core/java/com/android/internal/flags/CoreFlags.java b/core/java/com/android/internal/flags/CoreFlags.java
new file mode 100644
index 0000000..f177ef8
--- /dev/null
+++ b/core/java/com/android/internal/flags/CoreFlags.java
@@ -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.internal.flags;
+
+import android.flags.BooleanFlag;
+import android.flags.DynamicBooleanFlag;
+import android.flags.FeatureFlags;
+import android.flags.FusedOffFlag;
+import android.flags.FusedOnFlag;
+import android.flags.SyncableFlag;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Flags defined here are can be read by code in core.
+ *
+ * Flags not defined here will throw a security exception if third-party processes attempts to read
+ * them.
+ *
+ * DO NOT define a flag here unless you explicitly intend for that flag to be readable by code that
+ * runs inside a third party process.
+ */
+public abstract class CoreFlags {
+ private static final List<SyncableFlag> sKnownFlags = new ArrayList<>();
+
+ public static BooleanFlag BOOL_FLAG = booleanFlag("core", "bool_flag", false);
+ public static FusedOffFlag OFF_FLAG = fusedOffFlag("core", "off_flag");
+ public static FusedOnFlag ON_FLAG = fusedOnFlag("core", "on_flag");
+ public static DynamicBooleanFlag DYN_FLAG = dynamicBooleanFlag("core", "dyn_flag", true);
+
+ /** Returns true if the passed in flag matches a flag in this class. */
+ public static boolean isCoreFlag(SyncableFlag flag) {
+ for (SyncableFlag knownFlag : sKnownFlags) {
+ if (knownFlag.getName().equals(flag.getName())
+ && knownFlag.getNamespace().equals(flag.getNamespace())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static List<SyncableFlag> getCoreFlags() {
+ return sKnownFlags;
+ }
+
+ private static BooleanFlag booleanFlag(String namespace, String name, boolean defaultValue) {
+ BooleanFlag f = FeatureFlags.booleanFlag(namespace, name, defaultValue);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, Boolean.toString(defaultValue), false));
+
+ return f;
+ }
+
+ private static FusedOffFlag fusedOffFlag(String namespace, String name) {
+ FusedOffFlag f = FeatureFlags.fusedOffFlag(namespace, name);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, "false", false));
+
+ return f;
+ }
+
+ private static FusedOnFlag fusedOnFlag(String namespace, String name) {
+ FusedOnFlag f = FeatureFlags.fusedOnFlag(namespace, name);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, "true", false));
+
+ return f;
+ }
+
+ private static DynamicBooleanFlag dynamicBooleanFlag(
+ String namespace, String name, boolean defaultValue) {
+ DynamicBooleanFlag f = FeatureFlags.dynamicBooleanFlag(namespace, name, defaultValue);
+
+ sKnownFlags.add(new SyncableFlag(namespace, name, Boolean.toString(defaultValue), true));
+
+ return f;
+ }
+}
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index 1d0ba3f..e9a8d4b 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -58,6 +58,7 @@
int APP_REGISTERED = 7;
int SHORT_FGS_TIMEOUT = 8;
int JOB_SERVICE = 9;
+ int APP_START = 10;
}
/** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
@@ -190,4 +191,10 @@
public static TimeoutRecord forJobService(String reason) {
return TimeoutRecord.endingNow(TimeoutKind.JOB_SERVICE, reason);
}
+
+ /** Record for app startup timeout. */
+ @NonNull
+ public static TimeoutRecord forAppStart(String reason) {
+ return TimeoutRecord.endingNow(TimeoutKind.APP_START, reason);
+ }
}
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
index c06dab9..918d9c0 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -39,5 +39,6 @@
boolean hasStableIds();
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
boolean isCreated();
+ RemoteViews.RemoteCollectionItems getRemoteCollectionItems();
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index fe5850a..d5b8f62 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1015,7 +1015,7 @@
CREDENTIAL_TYPE_API, CREDENTIAL_TYPE_API, mCredentialTypeQuery);
/**
- * Invalidate the credential cache
+ * Invalidate the credential type cache
* @hide
*/
public final static void invalidateCredentialTypeCache() {
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index 7e827a8..29520c2 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -885,6 +885,16 @@
return sqlite3_last_insert_rowid(connection->db);
}
+static jlong nativeChanges(JNIEnv* env, jclass, jlong connectionPtr) {
+ SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+ return sqlite3_changes64(connection->db);
+}
+
+static jlong nativeTotalChanges(JNIEnv* env, jclass, jlong connectionPtr) {
+ SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+ return sqlite3_total_changes64(connection->db);
+}
+
static const JNINativeMethod sMethods[] =
{
/* name, signature, funcPtr */
@@ -943,7 +953,9 @@
{ "nativeResetCancel", "(JZ)V",
(void*)nativeResetCancel },
- { "nativeLastInsertRowId", "(J)I", (void*) nativeLastInsertRowId }
+ { "nativeLastInsertRowId", "(J)I", (void*) nativeLastInsertRowId },
+ { "nativeChanges", "(J)J", (void*) nativeChanges },
+ { "nativeTotalChanges", "(J)J", (void*) nativeTotalChanges },
};
int register_android_database_SQLiteConnection(JNIEnv *env)
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 5c71f69..8e4addd 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -17,6 +17,7 @@
#include "fd_utils.h"
#include <algorithm>
+#include <utility>
#include <fcntl.h>
#include <grp.h>
@@ -174,7 +175,7 @@
class FileDescriptorInfo {
public:
// Create a FileDescriptorInfo for a given file descriptor.
- static FileDescriptorInfo* CreateFromFd(int fd, fail_fn_t fail_fn);
+ static std::unique_ptr<FileDescriptorInfo> CreateFromFd(int fd, fail_fn_t fail_fn);
// Checks whether the file descriptor associated with this object refers to
// the same description.
@@ -213,7 +214,7 @@
DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo);
};
-FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) {
+std::unique_ptr<FileDescriptorInfo> FileDescriptorInfo::CreateFromFd(int fd, fail_fn_t fail_fn) {
struct stat f_stat;
// This should never happen; the zygote should always have the right set
// of permissions required to stat all its open files.
@@ -234,7 +235,7 @@
socket_name.c_str(), fd));
}
- return new FileDescriptorInfo(fd);
+ return std::unique_ptr<FileDescriptorInfo>(new FileDescriptorInfo(fd));
}
// We only handle allowlisted regular files and character devices. Allowlisted
@@ -315,7 +316,8 @@
int open_flags = fs_flags & (kOpenFlags);
fs_flags = fs_flags & (~(kOpenFlags));
- return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset);
+ return std::unique_ptr<FileDescriptorInfo>(
+ new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset));
}
bool FileDescriptorInfo::RefersToSameFile() const {
@@ -482,11 +484,11 @@
FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore,
fail_fn_t fail_fn) {
std::unique_ptr<std::set<int>> open_fds = GetOpenFdsIgnoring(fds_to_ignore, fail_fn);
- std::unordered_map<int, FileDescriptorInfo*> open_fd_map;
+ std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>> open_fd_map;
for (auto fd : *open_fds) {
open_fd_map[fd] = FileDescriptorInfo::CreateFromFd(fd, fail_fn);
}
- return new FileDescriptorTable(open_fd_map);
+ return new FileDescriptorTable(std::move(open_fd_map));
}
static std::unique_ptr<std::set<int>> GetOpenFdsIgnoring(const std::vector<int>& fds_to_ignore,
@@ -535,9 +537,9 @@
// Reopens all file descriptors that are contained in the table.
void FileDescriptorTable::ReopenOrDetach(fail_fn_t fail_fn) {
- std::unordered_map<int, FileDescriptorInfo*>::const_iterator it;
+ std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>>::const_iterator it;
for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) {
- const FileDescriptorInfo* info = it->second;
+ const FileDescriptorInfo* info = it->second.get();
if (info == nullptr) {
return;
} else {
@@ -547,15 +549,11 @@
}
FileDescriptorTable::FileDescriptorTable(
- const std::unordered_map<int, FileDescriptorInfo*>& map)
- : open_fd_map_(map) {
+ std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>> map)
+ : open_fd_map_(std::move(map)) {
}
-FileDescriptorTable::~FileDescriptorTable() {
- for (auto& it : open_fd_map_) {
- delete it.second;
- }
-}
+FileDescriptorTable::~FileDescriptorTable() {}
void FileDescriptorTable::RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn) {
// ART creates a file through memfd for optimization purposes. We make sure
@@ -569,7 +567,7 @@
// (b) they refer to the same file.
//
// We'll only store the last error message.
- std::unordered_map<int, FileDescriptorInfo*>::iterator it = open_fd_map_.begin();
+ std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>>::iterator it = open_fd_map_.begin();
while (it != open_fd_map_.end()) {
std::set<int>::const_iterator element = open_fds.find(it->first);
if (element == open_fds.end()) {
@@ -580,7 +578,6 @@
// TODO(narayan): This will be an error in a future android release.
// error = true;
// ALOGW("Zygote closed file descriptor %d.", it->first);
- delete it->second;
it = open_fd_map_.erase(it);
} else {
// The entry from the file descriptor table is still open. Restat
@@ -588,7 +585,6 @@
if (!it->second->RefersToSameFile()) {
// The file descriptor refers to a different description. We must
// update our entry in the table.
- delete it->second;
it->second = FileDescriptorInfo::CreateFromFd(*element, fail_fn);
} else {
// It's the same file. Nothing to do here. Move on to the next open
diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h
index a28ebf1..ac2d2a4 100644
--- a/core/jni/fd_utils.h
+++ b/core/jni/fd_utils.h
@@ -17,6 +17,7 @@
#ifndef FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_
#define FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_
+#include <memory>
#include <set>
#include <string>
#include <unordered_map>
@@ -98,12 +99,12 @@
void ReopenOrDetach(fail_fn_t fail_fn);
private:
- explicit FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map);
+ explicit FileDescriptorTable(std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>> map);
void RestatInternal(std::set<int>& open_fds, fail_fn_t fail_fn);
// Invariant: All values in this unordered_map are non-NULL.
- std::unordered_map<int, FileDescriptorInfo*> open_fd_map_;
+ std::unordered_map<int, std::unique_ptr<FileDescriptorInfo>> open_fd_map_;
DISALLOW_COPY_AND_ASSIGN(FileDescriptorTable);
};
diff --git a/core/proto/android/input/keyboard_configured.proto b/core/proto/android/input/keyboard_configured.proto
index 1699008..0b4bf8e 100644
--- a/core/proto/android/input/keyboard_configured.proto
+++ b/core/proto/android/input/keyboard_configured.proto
@@ -47,4 +47,8 @@
// IntDef annotation at:
// services/core/java/com/android/server/input/KeyboardMetricsCollector.java
optional int32 layout_selection_criteria = 4;
+ // Keyboard layout type provided by IME
+ optional int32 ime_layout_type = 5;
+ // Language tag provided by IME (e.g. en-US, ru-Cyrl etc.)
+ optional string ime_language_tag = 6;
}
diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto
index 3cedba0..5a3539a 100644
--- a/core/proto/android/os/system_properties.proto
+++ b/core/proto/android/os/system_properties.proto
@@ -172,7 +172,7 @@
optional Status tombstoned = 29;
optional Status ueventd = 30;
optional Status update_engine = 31;
- optional Status update_verifier_nonencrypted = 32;
+ optional Status update_verifier = 32;
optional Status virtual_touchpad = 33;
optional Status vndservicemanager = 34;
optional Status vold = 35;
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index 0fe2a6b..eb14db0 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -101,6 +101,30 @@
COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4;
}
+ // Information about the state of an archived app.
+ // All fields are gathered at the time of archival.
+ message ArchiveState {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ message ArchiveActivityInfo {
+ // Corresponds to android:label in the app's locale at the time of archival.
+ optional string title = 1;
+
+ // Icon of the archived app.
+ optional string icon_bitmap_path = 2;
+
+ // Only set if the app defined a monochrome icon.
+ optional string monochrome_icon_bitmap_path = 3;
+ }
+
+ /** Information about main activities. */
+ repeated ArchiveActivityInfo activity_infos = 1;
+
+ // Corresponds to android:label of the installer responsible for the unarchival of the
+ // app. Stored in the installer's locale at the time of archival.
+ optional string installer_title = 2;
+ }
+
optional int32 id = 1;
optional InstallType install_type = 2;
// Is the app restricted by owner / admin
@@ -114,6 +138,7 @@
optional int32 distraction_flags = 10;
// UTC timestamp of first install for the user
optional int32 first_install_time_ms = 11;
+ optional ArchiveState archive_state = 12;
}
message InstallSourceProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0c8707d..baa47da 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7663,6 +7663,24 @@
<permission android:name="android.permission.GET_ANY_PROVIDER_TYPE"
android:protectionLevel="signature" />
+
+ <!-- @hide Allows internal applications to read and synchronize non-core flags.
+ Apps without this permission can only read a subset of flags specifically intended
+ for use in "core", (i.e. third party apps). Apps with this permission can define their
+ own flags, and federate those values with other system-level apps.
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.SYNC_FLAGS"
+ android:protectionLevel="signature" />
+
+ <!-- @hide Allows internal applications to override flags in the FeatureFlags service.
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.WRITE_FLAGS"
+ android:protectionLevel="signature" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 936f08a..1c64f5f 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Kan nie toegang tot die foon se kamera op jou <xliff:g id="DEVICE">%1$s</xliff:g> kry nie"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Kan nie toegang tot die tablet se kamera op jou <xliff:g id="DEVICE">%1$s</xliff:g> kry nie"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Jy kan nie toegang hiertoe kry terwyl daar gestroom word nie. Probeer eerder op jou foon."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Kan nie prent-in-prent sien terwyl jy stroom nie"</string>
<string name="system_locale_title" msgid="711882686834677268">"Stelselverstek"</string>
<string name="default_card_name" msgid="9198284935962911468">"KAART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Metgeselhorlosieprofiel se toestemming om horlosies te bestuur"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 8e2f1a3..1a57480 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"የስልኩን ካሜራ ከእርስዎ <xliff:g id="DEVICE">%1$s</xliff:g> መድረስ አይቻልም"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ጡባዊውን ካሜራ ከእርስዎ <xliff:g id="DEVICE">%1$s</xliff:g> መድረስ አይቻልም"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ዥረት በመልቀቅ ላይ ሳለ ይህ ሊደረስበት አይችልም። በምትኩ በስልክዎ ላይ ይሞክሩ።"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"በዥረት በመልቀቅ ወቅት በሥዕል-ላይ-ሥዕል ማየት አይችሉም"</string>
<string name="system_locale_title" msgid="711882686834677268">"የሥርዓት ነባሪ"</string>
<string name="default_card_name" msgid="9198284935962911468">"ካርድ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"የእጅ ሰዓቶችን ለማስተዳደር የአጃቢ የእጅ ሰዓት መገለጫ ፍቃድ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index e457782..4138b0a 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -2320,6 +2320,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"يتعذّر الوصول إلى كاميرا الهاتف من على جهاز <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"يتعذّر الوصول إلى كاميرا الجهاز اللوحي من على جهاز <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
<string name="vdm_secure_window" msgid="161700398158812314">"لا يمكن الوصول إلى هذا المحتوى أثناء البث. بدلاً من ذلك، جرِّب استخدام هاتفك."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"لا يمكن عرض نافذة ضمن النافذة أثناء البث."</string>
<string name="system_locale_title" msgid="711882686834677268">"الإعداد التلقائي للنظام"</string>
<string name="default_card_name" msgid="9198284935962911468">"رقم البطاقة <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"إذن الملف الشخصي في Companion Watch لإدارة الساعات"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 7c11dfc..4d08974 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ৰ পৰা ফ’নটোৰ কেমেৰা এক্সেছ কৰিব নোৱাৰি"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"আপোনাৰ <xliff:g id="DEVICE">%1$s</xliff:g>ৰ পৰা টেবলেটটোৰ কেমেৰা এক্সেছ কৰিব নোৱাৰি"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ষ্ট্ৰীম কৰি থকাৰ সময়ত এইটো এক্সেছ কৰিব নোৱাৰি। তাৰ পৰিৱৰ্তে আপোনাৰ ফ’নত চেষ্টা কৰি চাওক।"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"ষ্ট্ৰীম কৰি থকাৰ সময়ত picture-in-picture চাব নোৱাৰি"</string>
<string name="system_locale_title" msgid="711882686834677268">"ছিষ্টেম ডিফ’ল্ট"</string>
<string name="default_card_name" msgid="9198284935962911468">"কাৰ্ড <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"ঘড়ী পৰিচালনা কৰিবলৈ সহযোগী ঘড়ীৰ প্ৰ’ফাইলৰ অনুমতি"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index dbfdd3c..e2a2808 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan telefonun kamerasına giriş etmək olmur"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan planşetin kamerasına giriş etmək olmur"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Yayım zamanı buna giriş mümkün deyil. Telefonunuzda sınayın."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Yayım zamanı şəkildə şəkilə baxmaq mümkün deyil"</string>
<string name="system_locale_title" msgid="711882686834677268">"Sistem defoltu"</string>
<string name="default_card_name" msgid="9198284935962911468">"KART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Saatları idarə etmək üçün Kompanyon Saat profili icazəsi"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 5c6187e..4a1101b8 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ne može da se pristupi kameri telefona sa <xliff:g id="DEVICE">%1$s</xliff:g> uređaja"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ne može da se pristupi kameri tableta sa <xliff:g id="DEVICE">%1$s</xliff:g> uređaja"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Ovom ne možete da pristupate tokom strimovanja. Probajte na telefonu."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Ne možete da gledate sliku u slici pri strimovanju"</string>
<string name="system_locale_title" msgid="711882686834677268">"Podrazumevani sistemski"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTICA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Dozvola za profil pratećeg sata za upravljanje satovima"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 84e68dc..217f2c8 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -2318,6 +2318,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не ўдалося атрымаць доступ да камеры тэлефона з прылады \"<xliff:g id="DEVICE">%1$s</xliff:g>\""</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Не ўдалося атрымаць доступ да камеры планшэта з прылады \"<xliff:g id="DEVICE">%1$s</xliff:g>\""</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Не ўдаецца атрымаць доступ у час перадачы плынню. Паспрабуйце скарыстаць тэлефон."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Падчас перадачы плынню прагляд у рэжыме \"Відарыс у відарысе\" немагчымы"</string>
<string name="system_locale_title" msgid="711882686834677268">"Стандартная сістэмная налада"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Дазвол для спадарожнай праграмы кіраваць гадзіннікамі"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 159f8df..ad166e4 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Няма достъп до камерата на телефона от вашия <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Няма достъп до камерата на таблета от вашия <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"До това съдържание не може да се осъществи достъп при поточно предаване. Вместо това опитайте от телефона си."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Функцията „Картина в картината“ не е налице при поточно предаване"</string>
<string name="system_locale_title" msgid="711882686834677268">"Стандартно за системата"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Разрешение на придружаващото приложение за достъп до потребителския профил на часовника с цел управление на часовници"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index f448078..4e038b5 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"আপনার <xliff:g id="DEVICE">%1$s</xliff:g> থেকে ফোনের ক্যামেরা অ্যাক্সেস করা যাচ্ছে না"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"আপনার <xliff:g id="DEVICE">%1$s</xliff:g> থেকে ট্যাবলেটের ক্যামেরা অ্যাক্সেস করা যাচ্ছে না"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"স্ট্রিমিংয়ের সময় এটি অ্যাক্সেস করা যাবে না। পরিবর্তে আপনার ফোনে ব্যবহার করে দেখুন।"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"স্ট্রিম করার সময় \'ছবির-মধ্যে-ছবি\' দেখা যাবে না"</string>
<string name="system_locale_title" msgid="711882686834677268">"সিস্টেম ডিফল্ট"</string>
<string name="default_card_name" msgid="9198284935962911468">"কার্ড <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"ওয়াচ ম্যানেজ করতে, কম্প্যানিয়ন ওয়াচ প্রোফাইল সংক্রান্ত অনুমতি"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 78daa1c..029d6f4 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nije moguće pristupiti kameri telefona s uređaja <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nije moguće pristupiti kameri tableta s uređaja <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Ovom ne možete pristupiti tokom prijenosa. Umjesto toga pokušajte na telefonu."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Tokom prijenosa nije moguće gledati sliku u slici"</string>
<string name="system_locale_title" msgid="711882686834677268">"Sistemski zadano"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTICA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Odobrenje za profil pratećeg sata da upravlja satovima"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index a1bbc01..4417083 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"No es pot accedir a la càmera del telèfon des del teu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"No es pot accedir a la càmera de la tauleta des del teu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"No s\'hi pot accedir mentre s\'està reproduint en continu. Prova-ho al telèfon."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"No es pot veure el mode d\'imatge sobre imatge durant la reproducció en continu"</string>
<string name="system_locale_title" msgid="711882686834677268">"Valor predeterminat del sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"TARGETA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Permís del perfil del rellotge perquè l\'aplicació complementària gestioni els rellotges"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 069b3bc..df6cc64 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -2318,6 +2318,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ze zařízení <xliff:g id="DEVICE">%1$s</xliff:g> nelze získat přístup k fotoaparátu telefonu"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ze zařízení <xliff:g id="DEVICE">%1$s</xliff:g> nelze získat přístup k fotoaparátu tabletu"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Tento obsah při streamování nelze zobrazit. Zkuste to na telefonu."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Během streamování nelze zobrazit obraz v obraze"</string>
<string name="system_locale_title" msgid="711882686834677268">"Výchozí nastavení systému"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Oprávnění profilu doprovodných hodinek ke správě hodinek"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index f698b34..1a74897 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Kameraet på din telefon kan ikke tilgås via din <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Kameraet på din tablet kan ikke tilgås via din <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Der er ikke adgang til dette indhold under streaming. Prøv på din telefon i stedet."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Funktionen Integreret billede er ikke tilgængelig, når der streames"</string>
<string name="system_locale_title" msgid="711882686834677268">"Systemstandard"</string>
<string name="default_card_name" msgid="9198284935962911468">"KORT <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Tilladelse til at administrere ure for urprofilens medfølgende app"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index de50e04..b8275c5 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Zugriff auf die Kamera des Smartphones über dein Gerät (<xliff:g id="DEVICE">%1$s</xliff:g>) nicht möglich"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Zugriff auf die Kamera des Tablets über dein Gerät (<xliff:g id="DEVICE">%1$s</xliff:g>) nicht möglich"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Während des Streamings ist kein Zugriff möglich. Versuch es stattdessen auf deinem Smartphone."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Funktion „Bild im Bild“ kann beim Streamen nicht verwendet werden"</string>
<string name="system_locale_title" msgid="711882686834677268">"Standardeinstellung des Systems"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTE <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Berechtigung für Companion-Smartwatch-Profil zum Verwalten von Smartwatches"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index b9e447f..f2e5c4d 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Δεν είναι δυνατή η πρόσβαση στην κάμερα τηλεφώνου από το <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Δεν είναι δυνατή η πρόσβαση στην κάμερα του tablet από τη συσκευή <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Δεν είναι δυνατή η πρόσβαση σε αυτό το στοιχείο κατά τη ροή. Δοκιμάστε στο τηλέφωνό σας."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Δεν είναι δυνατή η προβολή picture-in-picture κατά τη ροή"</string>
<string name="system_locale_title" msgid="711882686834677268">"Προεπιλογή συστήματος"</string>
<string name="default_card_name" msgid="9198284935962911468">"ΚΑΡΤΑ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Άδεια προφίλ συνοδευτικής εφαρμογής ρολογιού για τη διαχείριση ρολογιών"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index b49c053..a60b6cc 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Can’t access the phone’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Can’t access the tablet’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"This can’t be accessed while streaming. Try on your phone instead."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Can’t view picture-in-picture while streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"System default"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Companion watch profile permission to manage watches"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 84c1492..2c6f6a2 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Can’t access the phone’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Can’t access the tablet’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"This can’t be accessed while streaming. Try on your phone instead."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Can’t view picture-in-picture while streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"System default"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Companion Watch profile permission to manage watches"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 318324a..1918fc7 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Can’t access the phone’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Can’t access the tablet’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"This can’t be accessed while streaming. Try on your phone instead."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Can’t view picture-in-picture while streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"System default"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Companion watch profile permission to manage watches"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 2d75eae..95b37b0 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Can’t access the phone’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Can’t access the tablet’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"This can’t be accessed while streaming. Try on your phone instead."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Can’t view picture-in-picture while streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"System default"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Companion watch profile permission to manage watches"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index e544c1c..fc534cd 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Can’t access the phone’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Can’t access the tablet’s camera from your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"This can’t be accessed while streaming. Try on your phone instead."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Can’t view picture-in-picture while streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"System default"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Companion Watch profile permission to manage watches"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index c6ab2f1..4caae15 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"No se puede acceder a la cámara del dispositivo desde tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"No se puede acceder a la cámara de la tablet desde tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"No se puede acceder a este contenido durante una transmisión. Inténtalo en tu teléfono."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"No puedes ver pantalla en pantalla mientras transmites"</string>
<string name="system_locale_title" msgid="711882686834677268">"Predeterminado del sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"TARJETA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Permiso de perfil del reloj complementario para administrar relojes"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 3803005..b7547f8 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"No se puede acceder a la cámara del teléfono desde tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"No se puede acceder a la cámara del tablet desde tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"No se puede acceder a este contenido durante una emisión. Prueba en tu teléfono."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"No se puede usar imagen en imagen mientras se emite contenido"</string>
<string name="system_locale_title" msgid="711882686834677268">"Predeterminado del sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"TARJETA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Permiso del perfil del reloj complementario para gestionar relojes"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 8bb1f16..eb8c8106 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Teie seadmest <xliff:g id="DEVICE">%1$s</xliff:g> ei pääse telefoni kaamerale juurde."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Teie seadmest <xliff:g id="DEVICE">%1$s</xliff:g> ei pääse tahvelarvuti kaamerale juurde"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Sellele ei pääse voogesituse ajal juurde. Proovige juurde pääseda oma telefonis."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Voogesitamise ajal ei saa pilt pildis funktsiooni kasutada"</string>
<string name="system_locale_title" msgid="711882686834677268">"Süsteemi vaikeseade"</string>
<string name="default_card_name" msgid="9198284935962911468">"KAART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Kaasrakenduse profiili luba kellade haldamiseks"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index a5a3a2c..31ad27b2 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -673,7 +673,7 @@
<string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Sakatu hau aurpegi-eredua ezabatzeko eta, gero, gehitu aurpegia berriro"</string>
<string name="face_setup_notification_title" msgid="8843461561970741790">"Konfiguratu Aurpegi bidez desblokeatzea"</string>
<string name="face_setup_notification_content" msgid="5463999831057751676">"Telefonoa desblokeatzeko, begira iezaiozu"</string>
- <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Aurpegi bidez desblokeatzeko aukera erabiltzeko, aktibatu "<b>"kamera erabiltzeko baimena"</b>" Ezarpenak > Pribatutasuna atalean"</string>
+ <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Aurpegi bidez desblokeatzeko eginbidea erabiltzeko, aktibatu "<b>"kamera erabiltzeko baimena"</b>" Ezarpenak > Pribatutasuna atalean"</string>
<string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Konfiguratu telefonoa desblokeatzeko modu gehiago"</string>
<string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Sakatu hau hatz-marka bat gehitzeko"</string>
<string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Hatz-marka bidez desblokeatzea"</string>
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ezin da atzitu telefonoaren kamera <xliff:g id="DEVICE">%1$s</xliff:g> gailutik"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ezin da atzitu tabletaren kamera <xliff:g id="DEVICE">%1$s</xliff:g> gailutik"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Ezin da atzitu edukia hura igorri bitartean. Oraingo gailuaren ordez, erabili telefonoa."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Edukia zuzenean erreproduzitu bitartean ezin da pantaila txiki gainjarrian ikusi"</string>
<string name="system_locale_title" msgid="711882686834677268">"Sistemaren balio lehenetsia"</string>
<string name="default_card_name" msgid="9198284935962911468">"<xliff:g id="CARDNUMBER">%d</xliff:g> TXARTELA"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Aplikazio osagarrien erloju-profilaren baimena erlojuak kudeatzeko"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 439226d..0878b76 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"از <xliff:g id="DEVICE">%1$s</xliff:g> به دوربین تلفن دسترسی ندارید"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"نمیتوان از <xliff:g id="DEVICE">%1$s</xliff:g> شما به دوربین رایانه لوحی دسترسی داشت"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"درحین جاریسازی، نمیتوانید به آن دسترسی داشته باشید. دسترسی به آن را در تلفنتان امتحان کنید."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"هنگام جاریسازی نمیتوان تصویر در تصویر را مشاهده کرد"</string>
<string name="system_locale_title" msgid="711882686834677268">"پیشفرض سیستم"</string>
<string name="default_card_name" msgid="9198284935962911468">"کارت <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"اجازه نمایه «ساعت همراه» برای مدیریت ساعتها"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 1c425af..5f1b048 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> ei pääse puhelimen kameraan"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> ei pääse tabletin kameraan"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Sisältöön ei saa pääsyä striimauksen aikana. Kokeile striimausta puhelimella."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Pikkuruutua ei voi nähdä striimauksen aikana"</string>
<string name="system_locale_title" msgid="711882686834677268">"Järjestelmän oletusarvo"</string>
<string name="default_card_name" msgid="9198284935962911468">"Kortti: <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Kumppanin kelloprofiilin hallintalupa"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index c4fa226..04b4e36 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Impossible d\'accéder à l\'appareil photo du téléphone à partir de votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Impossible d\'accéder à l\'appareil photo de la tablette à partir de votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Vous ne pouvez pas y accéder lorsque vous utilisez la diffusion en continu. Essayez sur votre téléphone à la place."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Impossible d\'afficher des incrustations d\'image pendant une diffusion en continu"</string>
<string name="system_locale_title" msgid="711882686834677268">"Paramètre système par défaut"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARTE <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Autorisation du profil de la montre de l\'application compagnon pour gérer les montres"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 0495ce4..36da634 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Impossible d\'accéder à l\'appareil photo du téléphone depuis votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Impossible d\'accéder à l\'appareil photo de la tablette depuis votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Impossible d\'accéder à cela pendant le streaming. Essayez plutôt sur votre téléphone."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Impossible d\'afficher Picture-in-picture pendant le streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"Paramètre système par défaut"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARTE <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Autorisation du profil de la montre associée pour gérer des montres"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 70f4355..20e3812 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -256,7 +256,7 @@
<string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Usa esta opción na maioría das circunstancias. Permíteche realizar un seguimento do progreso do informe, introducir máis detalles sobre o problema e facer capturas de pantalla. É posible que omita algunhas seccións menos usadas para as que se tarda máis en facer o informe."</string>
<string name="bugreport_option_full_title" msgid="7681035745950045690">"Informe completo"</string>
<string name="bugreport_option_full_summary" msgid="1975130009258435885">"Usa esta opción para que a interferencia sexa mínima cando o teu dispositivo non responda ou funcione demasiado lento, ou ben cando precises todas as seccións do informe. Non poderás introducir máis detalles nin facer máis capturas de pantalla."</string>
- <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Vaise facer unha captura de pantalla para o informe de erro dentro de # segundo.}other{Vaise facer unha captura de pantalla para o informe de erro dentro de # segundos.}}"</string>
+ <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Vaise facer unha captura de pantalla para o informe de erros dentro de # segundo.}other{Vaise facer unha captura de pantalla para o informe de erros dentro de # segundos.}}"</string>
<string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"Realizouse a captura de pantalla co informe de erros"</string>
<string name="bugreport_screenshot_failure_toast" msgid="6736320861311294294">"Produciuse un erro ao realizar a captura de pantalla co informe de erros"</string>
<string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Modo silencioso"</string>
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Non se puido acceder á cámara do teléfono desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>)"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Non se puido acceder á cámara da tableta desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>)"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Non se puido acceder a este contido durante a reprodución en tempo real. Téntao desde o teléfono."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Non se pode ver un vídeo en pantalla superposta mentres se reproduce en tempo real"</string>
<string name="system_locale_title" msgid="711882686834677268">"Opción predeterminada do sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"TARXETA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Permiso de perfil de Companion Watch para xestionar reloxos"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 8a47646..a48e343 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"તમારા <xliff:g id="DEVICE">%1$s</xliff:g> પરથી ફોનના કૅમેરાનો ઍક્સેસ કરી શકતાં નથી"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"તમારા <xliff:g id="DEVICE">%1$s</xliff:g> પરથી ટૅબ્લેટના કૅમેરાનો ઍક્સેસ કરી શકતાં નથી"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"સ્ટ્રીમ કરતી વખતે આ ઍક્સેસ કરી શકાતું નથી. તેના બદલે તમારા ફોન પર પ્રયાસ કરો."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"સ્ટ્રીમ કરતી વખતે ચિત્ર-માં-ચિત્ર જોઈ શકતા નથી"</string>
<string name="system_locale_title" msgid="711882686834677268">"સિસ્ટમ ડિફૉલ્ટ"</string>
<string name="default_card_name" msgid="9198284935962911468">"કાર્ડ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"વૉચ મેનેજ કરવા માટે સાથી વૉચ પ્રોફાઇલની પરવાનગી"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index b6b18ec..5f2ba3a 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"आपके <xliff:g id="DEVICE">%1$s</xliff:g> से फ़ोन के कैमरे को ऐक्सेस नहीं किया जा सकता"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"आपके <xliff:g id="DEVICE">%1$s</xliff:g> से टैबलेट के कैमरे को ऐक्सेस नहीं किया जा सकता"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"स्ट्रीमिंग के दौरान, इसे ऐक्सेस नहीं किया जा सकता. इसके बजाय, अपने फ़ोन पर ऐक्सेस करके देखें."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"स्ट्रीमिंग करते समय, \'पिक्चर में पिक्चर\' सुविधा इस्तेमाल नहीं की जा सकती"</string>
<string name="system_locale_title" msgid="711882686834677268">"सिस्टम डिफ़ॉल्ट"</string>
<string name="default_card_name" msgid="9198284935962911468">"कार्ड <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"स्मार्टवॉच मैनेज करने के लिए, स्मार्टवॉच के साथ काम करने वाले साथी ऐप्लिकेशन पर प्रोफ़ाइल से जुड़ी अनुमति"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 106d85c..711ae1e 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"S vašeg uređaja <xliff:g id="DEVICE">%1$s</xliff:g> nije moguće pristupiti fotoaparatu telefona"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"S vašeg uređaja <xliff:g id="DEVICE">%1$s</xliff:g> nije moguće pristupiti fotoaparatu tableta"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Sadržaju nije moguće pristupiti tijekom streaminga. Pokušajte mu pristupiti na telefonu."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Slika u slici ne može se prikazivati tijekom streaminga"</string>
<string name="system_locale_title" msgid="711882686834677268">"Zadane postavke sustava"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTICA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Dopuštenje profila popratne aplikacije sata za upravljanje satovima"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 357c3d3..a1fa587 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nem lehet hozzáférni a telefon kamerájához a következő eszközön: <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nem lehet hozzáférni a táblagép kamerájához a következő eszközön: <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Ehhez a tartalomhoz nem lehet hozzáférni streamelés közben. Próbálja újra a telefonján."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Streamelés közben nem lehetséges a kép a képben módban való lejátszás"</string>
<string name="system_locale_title" msgid="711882686834677268">"Rendszerbeállítás"</string>
<string name="default_card_name" msgid="9198284935962911468">"KÁRTYA: <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Társóra-profilra vonatkozó engedély az órák kezeléséhez"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 9b20543..8c30f1d 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Հնարավոր չէ օգտագործել հեռախոսի տեսախցիկը ձեր <xliff:g id="DEVICE">%1$s</xliff:g> սարքից"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Հնարավոր չէ օգտագործել պլանշետի տեսախցիկը ձեր <xliff:g id="DEVICE">%1$s</xliff:g> սարքից"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Այս բովանդակությունը հասանելի չէ հեռարձակման ընթացքում։ Օգտագործեք ձեր հեռախոսը։"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Հեռարձակման ժամանակ հնարավոր չէ դիտել նկարը նկարի մեջ"</string>
<string name="system_locale_title" msgid="711882686834677268">"Կանխադրված"</string>
<string name="default_card_name" msgid="9198284935962911468">"ՔԱՐՏ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Ժամացույցները կառավարելու թույլտվություն ուղեկցող հավելվածի համար"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 818173c..120d261 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Tidak dapat mengakses kamera ponsel dari <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Tidak dapat mengakses kamera tablet dari <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Konten ini tidak dapat diakses saat melakukan streaming. Coba di ponsel."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Tidak dapat menampilkan picture-in-picture saat streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"Default sistem"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTU <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Izin profil Smartwatch Pendamping untuk mengelola smartwatch"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index be42540..552fa6c 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ekki er hægt að opna myndavél símans úr <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ekki er hægt að opna myndavél spjaldtölvunnar úr <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Ekki er hægt að opna þetta á meðan streymi stendur yfir. Prófaðu það í símanum í staðinn."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Ekki er hægt að horfa á mynd í mynd á meðan streymi er í gangi"</string>
<string name="system_locale_title" msgid="711882686834677268">"Sjálfgildi kerfis"</string>
<string name="default_card_name" msgid="9198284935962911468">"KORT <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Fylgiforrit úrs – prófílheimild til að stjórna úrum"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 5c12412..74c1ac3 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Impossibile accedere alla fotocamera del telefono dal tuo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Impossibile accedere alla fotocamera del tablet dal tuo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Impossibile accedere a questi contenuti durante lo streaming. Prova a usare il telefono."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Impossibile visualizzare Picture in picture durante lo streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"Predefinita di sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"SCHEDA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Autorizzazione per il profilo degli smartwatch complementari per gestire gli smartwatch"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 3dd65ad..f9693ba 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"לא ניתן לגשת למצלמה של הטלפון מה‑<xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"לא ניתן לגשת למצלמה של הטאבלט מה‑<xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"אי אפשר לגשת לתוכן המאובטח הזה בזמן סטרימינג. במקום זאת, אפשר לנסות בטלפון."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"אי אפשר להציג תמונה בתוך תמונה בזמן סטרימינג"</string>
<string name="system_locale_title" msgid="711882686834677268">"ברירת המחדל של המערכת"</string>
<string name="default_card_name" msgid="9198284935962911468">"כרטיס <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"הרשאת פרופיל שעון לאפליקציה נלווית כדי לנהל שעונים"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index cc3c657..a422e09 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> からスマートフォンのカメラにアクセスできません"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> からタブレットのカメラにアクセスできません"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ストリーミング中はアクセスできません。スマートフォンでのアクセスをお試しください。"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"ストリーミング中はピクチャー イン ピクチャーを表示できません"</string>
<string name="system_locale_title" msgid="711882686834677268">"システムのデフォルト"</string>
<string name="default_card_name" msgid="9198284935962911468">"カード <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"ウォッチを管理できるコンパニオン ウォッチ プロファイル権限"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 4496376..ab98d64 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ტელეფონის კამერაზე წვდომა ვერ მოხერხდა თქვენი <xliff:g id="DEVICE">%1$s</xliff:g>-დან"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ტაბლეტის კამერაზე წვდომა ვერ მოხერხდა თქვენი <xliff:g id="DEVICE">%1$s</xliff:g>-დან"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"მასზე წვდომის მიᲦება შეუძლებელია სტრიმინგის დროს. ცადეთ ტელეფონიდან."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"სტრიმინგის დროს ეკრანის ეკრანში ნახვა შეუძლებელია"</string>
<string name="system_locale_title" msgid="711882686834677268">"სისტემის ნაგულისხმევი"</string>
<string name="default_card_name" msgid="9198284935962911468">"ბარათი <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"კომპანიონი საათის პროფილის ნებართვა საათების მართვაზე"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index dd5d5fc..681aa41 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан телефон камерасын пайдалану мүмкін емес."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан планшет камерасын пайдалану мүмкін емес."</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Трансляция кезінде контентті көру мүмкін емес. Оның орнына телефоннан көріңіз."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Трансляция кезінде суреттегі суретті көру мүмкін емес."</string>
<string name="system_locale_title" msgid="711882686834677268">"Жүйенің әдепкі параметрі"</string>
<string name="default_card_name" msgid="9198284935962911468">"<xliff:g id="CARDNUMBER">%d</xliff:g>-КАРТА"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Сағаттарды басқаруға арналған қосымша сағат профилінің рұқсаты"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index dc0260f..974b314 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"មិនអាចចូលប្រើកាមេរ៉ាទូរសព្ទពី <xliff:g id="DEVICE">%1$s</xliff:g> របស់អ្នកបានទេ"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"មិនអាចចូលប្រើកាមេរ៉ាថេប្លេតពី <xliff:g id="DEVICE">%1$s</xliff:g> របស់អ្នកបានទេ"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"មិនអាចចូលប្រើប្រាស់ខ្លឹមសារនេះបានទេ ពេលផ្សាយ។ សូមសាកល្បងប្រើនៅលើទូរសព្ទរបស់អ្នកជំនួសវិញ។"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"មិនអាចមើលរូបក្នុងរូបខណៈពេលកំពុងផ្សាយបានទេ"</string>
<string name="system_locale_title" msgid="711882686834677268">"លំនាំដើមប្រព័ន្ធ"</string>
<string name="default_card_name" msgid="9198284935962911468">"កាត <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"ការអនុញ្ញាតពីកម្រងព័ត៌មាននាឡិកាដៃគូ ដើម្បីគ្រប់គ្រងនាឡិកា"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 99e210c..9eb8516 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ನಿಮ್ಮ <xliff:g id="DEVICE">%1$s</xliff:g> ಮೂಲಕ ಫೋನ್ನ ಕ್ಯಾಮರಾವನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ನಿಮ್ಮ <xliff:g id="DEVICE">%1$s</xliff:g> ಮೂಲಕ ಟ್ಯಾಬ್ಲೆಟ್ನ ಕ್ಯಾಮರಾವನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ಸ್ಟ್ರೀಮ್ ಮಾಡುವಾಗ ಇದನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಅದರ ಬದಲು ನಿಮ್ಮ ಫೋನ್ನಲ್ಲಿ ಪ್ರಯತ್ನಿಸಿ."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"ಸ್ಟ್ರೀಮ್ ಮಾಡುವಾಗ ಚಿತ್ರದಲ್ಲಿ ಚಿತ್ರವನ್ನು ವೀಕ್ಷಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
<string name="system_locale_title" msgid="711882686834677268">"ಸಿಸ್ಟಂ ಡೀಫಾಲ್ಟ್"</string>
<string name="default_card_name" msgid="9198284935962911468">"ಕಾರ್ಡ್ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"ವಾಚ್ಗಳನ್ನು ನಿರ್ವಹಿಸುವುದಕ್ಕಾಗಿ ಕಂಪ್ಯಾನಿಯನ್ ವಾಚ್ ಪ್ರೊಫೈಲ್ ಅನುಮತಿ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 97c28ad..83510e7 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1372,7 +1372,7 @@
<string name="usb_power_notification_message" msgid="7284765627437897702">"연결된 기기를 충전합니다. 옵션을 더 보려면 탭하세요."</string>
<string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"아날로그 오디오 액세서리가 감지됨"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"연결된 기기가 이 휴대전화와 호환되지 않습니다. 자세히 알아보려면 탭하세요."</string>
- <string name="adb_active_notification_title" msgid="408390247354560331">"USB 디버깅 연결됨"</string>
+ <string name="adb_active_notification_title" msgid="408390247354560331">"USB 디버깅 연결됨."</string>
<string name="adb_active_notification_message" msgid="5617264033476778211">"USB 디버깅을 사용 중지하려면 탭하세요."</string>
<string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"USB 디버깅을 사용하지 않으려면 선택합니다."</string>
<string name="adbwifi_active_notification_title" msgid="6147343659168302473">"무선 디버깅 연결됨"</string>
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"사용자의 <xliff:g id="DEVICE">%1$s</xliff:g>에서 휴대전화 카메라에 액세스할 수 없습니다."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"사용자의 <xliff:g id="DEVICE">%1$s</xliff:g>에서 태블릿 카메라에 액세스할 수 없습니다."</string>
<string name="vdm_secure_window" msgid="161700398158812314">"스트리밍 중에는 액세스할 수 없습니다. 대신 휴대전화에서 시도해 보세요."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"스트리밍 중에는 PIP 모드를 볼 수 없습니다."</string>
<string name="system_locale_title" msgid="711882686834677268">"시스템 기본값"</string>
<string name="default_card_name" msgid="9198284935962911468">"<xliff:g id="CARDNUMBER">%d</xliff:g> 카드"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"시계 관리를 위한 호환 시계 프로필 권한"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index c250dc3..7f63669 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> түзмөгүңүздөн телефондун камерасына мүмкүнчүлүк жок"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> түзмөгүңүздөн планшетиңиздин камерасына мүмкүнчүлүк жок"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Муну алып ойнотуу учурунда көрүүгө болбойт. Анын ордуна телефондон кирип көрүңүз."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Алып ойнотуп жатканда сүрөттөгү сүрөт көрүнбөйт"</string>
<string name="system_locale_title" msgid="711882686834677268">"Системанын демейки параметрлери"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Сааттын көмөкчү профилинин сааттарды тескөө уруксаты"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index bab1220..d078881 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ບໍ່ສາມາດເຂົ້າເຖິງກ້ອງຖ່າຍຮູບຂອງໂທລະສັບຈາກ <xliff:g id="DEVICE">%1$s</xliff:g> ຂອງທ່ານໄດ້"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ບໍ່ສາມາດເຂົ້າເຖິງກ້ອງຖ່າຍຮູບຂອງແທັບເລັດຈາກ <xliff:g id="DEVICE">%1$s</xliff:g> ຂອງທ່ານໄດ້"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ບໍ່ສາມາດເຂົ້າເຖິງເນື້ອຫານີ້ໄດ້ໃນຂະນະທີ່ຍັງສະຕຣີມຢູ່. ກະລຸນາລອງຢູ່ໂທລະສັບຂອງທ່ານແທນ."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"ບໍ່ສາມາດເບິ່ງການສະແດງຜົນຊ້ອນກັນໃນຂະນະທີ່ສະຕຣີມໄດ້"</string>
<string name="system_locale_title" msgid="711882686834677268">"ຄ່າເລີ່ມຕົ້ນຂອງລະບົບ"</string>
<string name="default_card_name" msgid="9198284935962911468">"ບັດ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"ສິດການອະນຸຍາດສຳລັບໂປຣໄຟລ໌ໃນໂມງຊ່ວຍເຫຼືອເພື່ອຈັດການໂມງ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 10b625e..33e23da 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -2318,6 +2318,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nepavyko pasiekti telefono fotoaparato iš „<xliff:g id="DEVICE">%1$s</xliff:g>“"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nepavyko pasiekti planšetinio kompiuterio fotoaparato iš „<xliff:g id="DEVICE">%1$s</xliff:g>“"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Nepavyksta pasiekti perduodant srautu. Pabandykite naudoti telefoną."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Negalima peržiūrėti vaizdo vaizde perduodant srautu"</string>
<string name="system_locale_title" msgid="711882686834677268">"Numatytoji sistemos vertė"</string>
<string name="default_card_name" msgid="9198284935962911468">"KORTELĖ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Papildomos programos laikrodžio profilio leidimas valdyti laikrodžius"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 3076b56..346aa30 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nevar piekļūt tālruņa kamerai no jūsu ierīces (<xliff:g id="DEVICE">%1$s</xliff:g>)."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nevar piekļūt planšetdatora kamerai no jūsu ierīces (<xliff:g id="DEVICE">%1$s</xliff:g>)."</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Straumēšanas laikā nevar piekļūt šim saturam. Mēģiniet tam piekļūt savā tālrunī."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Straumēšanas laikā nevar skatīt attēlu attēlā"</string>
<string name="system_locale_title" msgid="711882686834677268">"Sistēmas noklusējums"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTE <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Palīglietotnes pulksteņa profila atļauja pārvaldīt pulksteņus"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 8622bdc..05246ce 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не може да се пристапи до камерата на вашиот телефон од <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Не може да се пристапи до камерата на вашиот таблет од <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"До ова не може да се пристапи при стриминг. Наместо тоа, пробајте на вашиот телефон."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Не може да се прикажува слика во слика при стримување"</string>
<string name="system_locale_title" msgid="711882686834677268">"Стандарден за системот"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТИЧКА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Дозвола за профилот на придружен часовник за управување со часовници"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index c17ff94..392a580 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"നിങ്ങളുടെ <xliff:g id="DEVICE">%1$s</xliff:g> എന്നതിൽ നിന്ന് ഫോണിന്റെ ക്യാമറ ആക്സസ് ചെയ്യാനാകില്ല"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"നിങ്ങളുടെ <xliff:g id="DEVICE">%1$s</xliff:g> എന്നതിൽ നിന്ന് ടാബ്ലെറ്റിന്റെ ക്യാമറ ആക്സസ് ചെയ്യാനാകില്ല"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"സ്ട്രീം ചെയ്യുമ്പോൾ ഇത് ആക്സസ് ചെയ്യാനാകില്ല. പകരം നിങ്ങളുടെ ഫോണിൽ ശ്രമിച്ച് നോക്കൂ."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"സ്ട്രീമിംഗിനിടെ ചിത്രത്തിനുള്ളിൽ ചിത്രം കാണാനാകില്ല"</string>
<string name="system_locale_title" msgid="711882686834677268">"സിസ്റ്റം ഡിഫോൾട്ട്"</string>
<string name="default_card_name" msgid="9198284935962911468">"കാർഡ് <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"വാച്ചുകൾ മാനേജ് ചെയ്യുന്നതിന് സഹകാരി ആപ്പിനുള്ള വാച്ച് പ്രൊഫൈൽ അനുമതി"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 71be64e..6cd4424 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Таны <xliff:g id="DEVICE">%1$s</xliff:g>-с утасны камерт хандах боломжгүй"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Таны <xliff:g id="DEVICE">%1$s</xliff:g>-с таблетын камерт хандах боломжгүй"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Стримингийн үед үүнд хандах боломжгүй. Оронд нь утас дээрээ туршиж үзнэ үү."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Дамжуулах явцад дэлгэц доторх дэлгэцийг үзэх боломжгүй"</string>
<string name="system_locale_title" msgid="711882686834677268">"Системийн өгөгдмөл"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Дэмжигч цагны профайлын цаг удирдах зөвшөөрөл"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index b04244a..fc99355 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"तुमच्या <xliff:g id="DEVICE">%1$s</xliff:g> वरून फोनचा कॅमेरा अॅक्सेस करू शकत नाही"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"तुमच्या <xliff:g id="DEVICE">%1$s</xliff:g> वरून टॅबलेटचा कॅमेरा अॅक्सेस करू शकत नाही"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"स्ट्रीम करताना हे अॅक्सेस केले जाऊ शकत नाही. त्याऐवजी तुमच्या फोनवर अॅक्सेस करून पहा."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"स्ट्रीम होत असताना चित्रात-चित्र पाहू शकत नाही"</string>
<string name="system_locale_title" msgid="711882686834677268">"सिस्टीम डीफॉल्ट"</string>
<string name="default_card_name" msgid="9198284935962911468">"कार्ड <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"वॉच व्यवस्थापित करण्यासाठी सहयोगी वॉच प्रोफाइलची परवानगी"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index eccb2ae..b0a82cb 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Tidak dapat mengakses kamera telefon daripada <xliff:g id="DEVICE">%1$s</xliff:g> anda"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Tidak dapat mengakses kamera tablet daripada <xliff:g id="DEVICE">%1$s</xliff:g> anda"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Kandungan ini tidak boleh diakses semasa penstriman. Cuba pada telefon anda."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Tidak dapat melihat gambar dalam gambar semasa penstriman"</string>
<string name="system_locale_title" msgid="711882686834677268">"Lalai sistem"</string>
<string name="default_card_name" msgid="9198284935962911468">"KAD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Kebenaran profil Jam Tangan rakan untuk mengurus jam tangan"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 4a8cafc..cf958db 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"သင်၏ <xliff:g id="DEVICE">%1$s</xliff:g> မှ ဖုန်းကင်မရာကို သုံး၍မရပါ"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"သင်၏ <xliff:g id="DEVICE">%1$s</xliff:g> မှ တက်ဘလက်ကင်မရာကို သုံး၍မရပါ"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"တိုက်ရိုက်လွှင့်နေစဉ် ၎င်းကို မသုံးနိုင်ပါ။ ၎င်းအစား ဖုန်းတွင် စမ်းကြည့်ပါ။"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"တိုက်ရိုက်လွှင့်စဉ် နှစ်ခုထပ်၍ မကြည့်နိုင်ပါ"</string>
<string name="system_locale_title" msgid="711882686834677268">"စနစ်မူရင်း"</string>
<string name="default_card_name" msgid="9198284935962911468">"ကတ် <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"လက်ပတ်နာရီများစီမံရန် ‘တွဲဖက်နာရီ’ ပရိုဖိုင်ခွင့်ပြုချက်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index a999815..e307d21 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Det er ikke mulig å få tilgang til telefonkameraet fra <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Det er ikke mulig å få tilgang til kameraet på nettbrettet fra <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Dette er ikke tilgjengelig under strømming. Prøv på telefonen i stedet."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Kan ikke se bilde-i-bilde under strømming"</string>
<string name="system_locale_title" msgid="711882686834677268">"Systemstandard"</string>
<string name="default_card_name" msgid="9198284935962911468">"KORT <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Klokkeprofil-tillatelse for følgeapp for å administrere klokker"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index c448f01..ef82242 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"तपाईंको <xliff:g id="DEVICE">%1$s</xliff:g> मार्फत फोनको क्यामेरा प्रयोग गर्न मिल्दैन"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"तपाईंको <xliff:g id="DEVICE">%1$s</xliff:g> मार्फत ट्याब्लेटको क्यामेरा प्रयोग गर्न मिल्दैन"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"स्ट्रिम गरिरहेका बेला यो सामग्री हेर्न तथा प्रयोग गर्न मिल्दैन। बरु आफ्नो फोनमार्फत सो सामग्री हेर्ने तथा प्रयोग गर्ने प्रयास गर्नुहोस्।"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"स्ट्रिम गरिरहेका बेला picture-in-picture मोड प्रयोग गर्न मिल्दैन"</string>
<string name="system_locale_title" msgid="711882686834677268">"सिस्टम डिफल्ट"</string>
<string name="default_card_name" msgid="9198284935962911468">"कार्ड <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"स्मार्टवाचहरू व्यवस्थापन गर्ने सहयोगी वाच प्रोफाइलसम्बन्धी अनुमति"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 5726d7f..966d42e 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Kan geen toegang tot de camera van de telefoon krijgen vanaf je <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Kan geen toegang tot de camera van de tablet krijgen vanaf je <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Je hebt hier geen toegang toe tijdens streaming. Probeer het in plaats daarvan op je telefoon."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Kan scherm-in-scherm niet bekijken tijdens het streamen"</string>
<string name="system_locale_title" msgid="711882686834677268">"Systeemstandaard"</string>
<string name="default_card_name" msgid="9198284935962911468">"KAART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Smartwatchprofiel voor bijbehorende app om smartwatches te beheren"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 4f5f79d..5743fd7 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ଆପଣଙ୍କ <xliff:g id="DEVICE">%1$s</xliff:g>ରୁ ଫୋନର କ୍ୟାମେରାକୁ ଆକ୍ସେସ କରାଯାଇପାରିବ ନାହିଁ"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ଆପଣଙ୍କ <xliff:g id="DEVICE">%1$s</xliff:g>ରୁ ଟାବଲେଟର କ୍ୟାମେରାକୁ ଆକ୍ସେସ କରାଯାଇପାରିବ ନାହିଁ"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ଷ୍ଟ୍ରିମ କରିବା ସମୟରେ ଏହାକୁ ଆକ୍ସେସ କରାଯାଇପାରିବ ନାହିଁ। ଏହା ପରିବର୍ତ୍ତେ ଆପଣଙ୍କ ଫୋନରେ ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"ଷ୍ଟ୍ରିମ କରିବା ସମୟରେ ପିକଚର-ଇନ-ପିକଚର ଦେଖାଯାଇପାରିବ ନାହିଁ"</string>
<string name="system_locale_title" msgid="711882686834677268">"ସିଷ୍ଟମ ଡିଫଲ୍ଟ"</string>
<string name="default_card_name" msgid="9198284935962911468">"କାର୍ଡ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"ୱାଚଗୁଡ଼ିକୁ ପରିଚାଳନା କରିବା ପାଇଁ ସହଯୋଗୀ ୱାଚ ପ୍ରୋଫାଇଲ ଅନୁମତି"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index afcd67a..8bc7a326 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ਤੁਹਾਡੇ <xliff:g id="DEVICE">%1$s</xliff:g> ਤੋਂ ਫ਼ੋਨ ਦੇ ਕੈਮਰੇ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ਤੁਹਾਡੇ <xliff:g id="DEVICE">%1$s</xliff:g> ਤੋਂ ਟੈਬਲੈੱਟ ਦੇ ਕੈਮਰੇ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ਸਟ੍ਰੀਮਿੰਗ ਦੌਰਾਨ ਇਸ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ। ਇਸਦੀ ਬਜਾਏ ਆਪਣੇ ਫ਼ੋਨ \'ਤੇ ਵਰਤ ਕੇ ਦੇਖੋ।"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"ਸਟ੍ਰੀਮਿੰਗ ਦੌਰਾਨ ਤਸਵੀਰ-ਵਿੱਚ-ਤਸਵੀਰ ਨਹੀਂ ਦੇਖੀ ਜਾ ਸਕਦੀ"</string>
<string name="system_locale_title" msgid="711882686834677268">"ਸਿਸਟਮ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ"</string>
<string name="default_card_name" msgid="9198284935962911468">"ਕਾਰਡ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"ਘੜੀਆਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ ਸੰਬੰਧੀ ਘੜੀ ਪ੍ਰੋਫਾਈਲ ਇਜਾਜ਼ਤ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 8929505..2671e39 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -2318,6 +2318,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nie można korzystać z aparatu telefonu na urządzeniu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nie można korzystać z aparatu tabletu na urządzeniu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Nie można z tego skorzystać podczas strumieniowania. Użyj telefonu."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Podczas strumieniowania nie można wyświetlać obrazu w obrazie"</string>
<string name="system_locale_title" msgid="711882686834677268">"Ustawienie domyślne systemu"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Uprawnienie profilu zegarka towarzyszącego do zarządzania zegarkami"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 09b3e4a8..dfd046a 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Não é possível acessar a câmera do smartphone pelo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Não é possível acessar a câmera do tablet pelo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Não é possível acessar esse conteúdo durante o streaming. Tente pelo smartphone."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Não é possível usar o modo picture-in-picture durante o streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"Padrão do sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"CHIP <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Permissão do perfil do relógio complementar para gerenciar relógios"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 283eb084..d094e3e 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Não é possível aceder à câmara do telemóvel a partir do dispositivo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Não é possível aceder à câmara do tablet a partir do dispositivo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Não é possível aceder a isto durante o streaming. Em alternativa, experimente no telemóvel."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Não é possível ver o ecrã no ecrã durante o streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"Predefinição do sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARTÃO <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Autorização do perfil de relógio associado para gerir relógios"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 09b3e4a8..dfd046a 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Não é possível acessar a câmera do smartphone pelo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Não é possível acessar a câmera do tablet pelo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Não é possível acessar esse conteúdo durante o streaming. Tente pelo smartphone."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Não é possível usar o modo picture-in-picture durante o streaming"</string>
<string name="system_locale_title" msgid="711882686834677268">"Padrão do sistema"</string>
<string name="default_card_name" msgid="9198284935962911468">"CHIP <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Permissão do perfil do relógio complementar para gerenciar relógios"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 676eedf..b386223 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nu se poate accesa camera foto a telefonului de pe <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nu se poate accesa camera foto a tabletei de pe <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Nu se poate accesa în timpul streamingului. Încearcă pe telefon."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Nu se poate viziona picture-in-picture în timpul streamingului"</string>
<string name="system_locale_title" msgid="711882686834677268">"Prestabilită de sistem"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Permisiunea pentru gestionarea ceasurilor din profilul ceasului însoțitor"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index f5fc02c..39aa55a 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -2318,6 +2318,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"У устройства <xliff:g id="DEVICE">%1$s</xliff:g> нет доступа к камере телефона."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"У устройства \"<xliff:g id="DEVICE">%1$s</xliff:g>\" нет доступа к камере планшета."</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Этот контент недоступен во время трансляции. Используйте телефон."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Нельзя запустить режим \"Картинка в картинке\" во время потоковой передачи"</string>
<string name="system_locale_title" msgid="711882686834677268">"Системные настройки по умолчанию"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Разрешение для сопутствующего приложения управлять часами"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index c19c081..bde7d93 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ඔබගේ <xliff:g id="DEVICE">%1$s</xliff:g> වෙතින් දුරකථනයේ කැමරාවට ප්රවේශ විය නොහැකිය"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"ඔබගේ <xliff:g id="DEVICE">%1$s</xliff:g> වෙතින් ටැබ්ලටයේ කැමරාවට ප්රවේශ විය නොහැකිය"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ප්රවාහය කරන අතරේ මෙයට ප්රවේශ විය නොහැක. ඒ වෙනුවට ඔබේ දුරකථනයෙහි උත්සාහ කරන්න."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"ප්රවාහය අතරේ පින්තූරයේ-පින්තූරය බැලිය නොහැක"</string>
<string name="system_locale_title" msgid="711882686834677268">"පද්ධති පෙරනිමිය"</string>
<string name="default_card_name" msgid="9198284935962911468">"කාඩ්පත <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"ඔරලෝසු කළමනාකරණය කිරීමට සහායක ඔරලෝසු පැතිකඩ අවසරය"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 1d5288e..02e6959 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -2318,6 +2318,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> nemáte prístup ku kamere telefónu"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"V zariadení <xliff:g id="DEVICE">%1$s</xliff:g> nemáte prístup ku kamere tabletu"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"K tomuto obsahu nie je počas streamovania prístup. Skúste použiť telefón."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Počas streamingu sa obraz v obraze nedá zobraziť"</string>
<string name="system_locale_title" msgid="711882686834677268">"Predvolené systémom"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Povolenie profilu hodiniek pre sprievodnú aplikáciu umožňujúce spravovať hodinky"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 3959ec6..d3acfba 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -2318,6 +2318,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ni mogoče dostopati do fotoaparata telefona prek naprave <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ni mogoče dostopati do fotoaparata tabličnega računalnika prek naprave <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Do te vsebine ni mogoče dostopati med pretočnim predvajanjem. Poskusite s telefonom."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Slike v sliki ni mogoče prikazati med pretočnim predvajanjem."</string>
<string name="system_locale_title" msgid="711882686834677268">"Sistemsko privzeto"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTICA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Dovoljenje za upravljanje ur v profilu ure v spremljevalni aplikaciji"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index d7ce6e4..228ce16 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nuk mund të qasesh në kamerën e telefonit tënd nga <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Nuk mund të qasesh në kamerën e tabletit tënd nga <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Nuk mund të kesh qasje në të gjatë transmetimit. Provoje në telefon më mirë."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Figura brenda figurës nuk mund të shikohet gjatë transmetimit"</string>
<string name="system_locale_title" msgid="711882686834677268">"Parazgjedhja e sistemit"</string>
<string name="default_card_name" msgid="9198284935962911468">"KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Leje për profilin e \"Orës shoqëruese\" për të menaxhuar orët"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 0aa2f38..84ead15 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -2317,6 +2317,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не може да се приступи камери телефона са <xliff:g id="DEVICE">%1$s</xliff:g> уређаја"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Не може да се приступи камери таблета са <xliff:g id="DEVICE">%1$s</xliff:g> уређаја"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Овом не можете да приступате током стримовања. Пробајте на телефону."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Не можете да гледате слику у слици при стримовању"</string>
<string name="system_locale_title" msgid="711882686834677268">"Подразумевани системски"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТИЦА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Дозвола за профил пратећег сата за управљање сатовима"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 6542c56..3739f1f 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Telefonens kamera kan inte användas från <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Surfplattans kamera kan inte användas från <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Det går inte att komma åt innehållet när du streamar. Testa med telefonen i stället."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Det går inte att visa bild-i-bild när du streamar"</string>
<string name="system_locale_title" msgid="711882686834677268">"Systemets standardinställning"</string>
<string name="default_card_name" msgid="9198284935962911468">"KORT <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Behörighet för den tillhörande klockprofilen att hantera klockor"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 60d5907..86beafe 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Huwezi kufikia kamera ya simu kutoka kwenye <xliff:g id="DEVICE">%1$s</xliff:g> yako"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Haiwezi kufikia kamera ya kompyuta kibao kutoka kwenye <xliff:g id="DEVICE">%1$s</xliff:g> yako"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Huwezi kufikia maudhui haya unapotiririsha. Badala yake jaribu kwenye simu yako."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Huwezi kuona picha iliyopachikwa ndani ya picha nyingine unapotiririsha"</string>
<string name="system_locale_title" msgid="711882686834677268">"Chaguomsingi la mfumo"</string>
<string name="default_card_name" msgid="9198284935962911468">"SIM KADI <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Ruhusa ya wasifu oanifu wa Saa ili kudhibiti saa"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index cdfdca5..783b709 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"உங்கள் <xliff:g id="DEVICE">%1$s</xliff:g> சாதனத்திலிருந்து மொபைலின் கேமராவை அணுக முடியாது"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"உங்கள் <xliff:g id="DEVICE">%1$s</xliff:g> சாதனத்திலிருந்து டேப்லெட்டின் கேமராவை அணுக முடியாது"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"ஸ்ட்ரீமின்போது இதை அணுக முடியாது. அதற்குப் பதிலாக உங்கள் மொபைலில் பயன்படுத்திப் பார்க்கவும்."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"ஸ்ட்ரீம் செய்யும்போது பிக்ச்சர்-இன்-பிக்ச்சர் அம்சத்தைப் பயன்படுத்த முடியாது"</string>
<string name="system_locale_title" msgid="711882686834677268">"சிஸ்டத்தின் இயல்பு"</string>
<string name="default_card_name" msgid="9198284935962911468">"கார்டு <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"வாட்ச்சுகளை நிர்வகிக்க, துணைத் தயாரிப்பு ஆப்ஸில் வாட்ச் சுயவிவரத்தை அனுமதித்தல்"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 3310cd8..634681e 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"మీ <xliff:g id="DEVICE">%1$s</xliff:g> నుండి ఫోన్ కెమెరాను యాక్సెస్ చేయడం సాధ్యపడదు"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"మీ <xliff:g id="DEVICE">%1$s</xliff:g> నుండి టాబ్లెట్ కెమెరాను యాక్సెస్ చేయడం సాధ్యపడదు"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"స్ట్రీమింగ్ చేస్తున్నప్పుడు దీన్ని యాక్సెస్ చేయడం సాధ్యపడదు. బదులుగా మీ ఫోన్లో ట్రై చేయండి."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"స్ట్రీమింగ్ చేస్తున్నప్పుడు పిక్చర్-ఇన్-పిక్చర్ చూడలేరు"</string>
<string name="system_locale_title" msgid="711882686834677268">"సిస్టమ్ ఆటోమేటిక్ సెట్టింగ్"</string>
<string name="default_card_name" msgid="9198284935962911468">"కార్డ్ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"వాచ్లను మేనేజ్ చేయడానికి సహాయక వాచ్ ప్రొఫైల్ అనుమతి"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 346c090..a987d4d 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"เข้าถึงกล้องของโทรศัพท์จาก <xliff:g id="DEVICE">%1$s</xliff:g> ไม่ได้"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"เข้าถึงกล้องของแท็บเล็ตจาก <xliff:g id="DEVICE">%1$s</xliff:g> ไม่ได้"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"เข้าถึงเนื้อหานี้ไม่ได้ขณะที่สตรีมมิง โปรดลองเข้าถึงในโทรศัพท์แทน"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"ดูการแสดงภาพซ้อนภาพขณะสตรีมไม่ได้"</string>
<string name="system_locale_title" msgid="711882686834677268">"ค่าเริ่มต้นของระบบ"</string>
<string name="default_card_name" msgid="9198284935962911468">"ซิมการ์ด <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"สิทธิ์สำหรับโปรไฟล์ในนาฬิกาที่ใช้ร่วมกันเพื่อจัดการนาฬิกา"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 415c9ac..6d7a6fd 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Hindi ma-access ang camera ng telepono mula sa iyong <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Hindi ma-access ang camera ng tablet mula sa iyong <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Hindi ito puwedeng i-access habang nagsi-stream. Subukan na lang sa iyong telepono."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Hindi matingnan nang picture-in-picture habang nagsi-stream"</string>
<string name="system_locale_title" msgid="711882686834677268">"Default ng system"</string>
<string name="default_card_name" msgid="9198284935962911468">"CARD <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Pahintulot sa profile ng Relo ng kasamang app na pamahalaan ang mga relo"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index b411429..329c718 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan telefonun kamerasına erişilemiyor"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan tabletin kamerasına erişilemiyor"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Canlı oynatılırken bu içeriğe erişilemez. Bunun yerine telefonunuzu kullanmayı deneyin."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Yayın sırasında pencere içinde pencere görüntülenemez"</string>
<string name="system_locale_title" msgid="711882686834677268">"Sistem varsayılanı"</string>
<string name="default_card_name" msgid="9198284935962911468">"KART <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Kol saatlerinin yönetimi için Tamamlayıcı Kol Saati Profili İzni"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 4f97154..4bc7abf 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -2318,6 +2318,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не вдається отримати доступ до камери телефона з пристрою <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Не вдається отримати доступ до камери планшета з пристрою <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Цей контент недоступний під час потокового передавання. Спробуйте натомість скористатися телефоном."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Ви не можете переглядати картинку в картинці під час трансляції"</string>
<string name="system_locale_title" msgid="711882686834677268">"Налаштування системи за умовчанням"</string>
<string name="default_card_name" msgid="9198284935962911468">"КАРТКА <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Дозвіл профілю годинника для супутнього додатка на керування годинниками"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index b47bcb1..4755d65 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"آپ کے <xliff:g id="DEVICE">%1$s</xliff:g> سے فون کے کیمرا تک رسائی حاصل نہیں کی جا سکتی"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"آپ کے <xliff:g id="DEVICE">%1$s</xliff:g> سے ٹیبلیٹ کے کیمرا تک رسائی حاصل نہیں کی جا سکتی"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"سلسلہ بندی کے دوران اس تک رسائی حاصل نہیں کی جا سکتی۔ اس کے بجائے اپنے فون پر کوشش کریں۔"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"سلسلہ بندی کے دوران تصویر میں تصویر نہیں دیکھ سکتے"</string>
<string name="system_locale_title" msgid="711882686834677268">"سسٹم ڈیفالٹ"</string>
<string name="default_card_name" msgid="9198284935962911468">"کارڈ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"گھڑیوں کا نظم کرنے کے لیے ساتھی ایپ کی گھڑی کی پروفائل کی اجازت"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 680b2a6..cdf1c93 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> qurilmasidan telefonning kamerasiga kirish imkonsiz"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"<xliff:g id="DEVICE">%1$s</xliff:g> qurilmasidan planshetning kamerasiga kirish imkonsiz"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Bu kontent striming vaqtida ochilmaydi. Telefon orqali urininb koʻring."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Striming vaqtida tasvir ustida tasvir rejimida koʻrib boʻlmaydi"</string>
<string name="system_locale_title" msgid="711882686834677268">"Tizim standarti"</string>
<string name="default_card_name" msgid="9198284935962911468">"SIM KARTA <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Soatlarni boshqarish uchun hamroh Soat profiliga ruxsat"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 8b28907..68bafc8 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Không truy cập được vào máy ảnh trên điện thoại từ <xliff:g id="DEVICE">%1$s</xliff:g> của bạn"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Không truy cập được vào máy ảnh trên máy tính bảng từ <xliff:g id="DEVICE">%1$s</xliff:g> của bạn"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Bạn không thể truy cập vào nội dung này trong khi phát trực tuyến. Hãy thử trên điện thoại."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Không thể xem video ở chế độ hình trong hình khi đang truyền trực tuyến"</string>
<string name="system_locale_title" msgid="711882686834677268">"Theo chế độ mặc định của hệ thống"</string>
<string name="default_card_name" msgid="9198284935962911468">"THẺ <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Quyền sử dụng hồ sơ Đồng hồ của ứng dụng đồng hành để quản lý các đồng hồ"</string>
diff --git a/core/res/res/values-watch/config_material.xml b/core/res/res/values-watch/config_material.xml
index 03d3637..529f18b 100644
--- a/core/res/res/values-watch/config_material.xml
+++ b/core/res/res/values-watch/config_material.xml
@@ -30,9 +30,6 @@
<!-- Always overscan by default to ensure onApplyWindowInsets will always be called. -->
<bool name="config_windowOverscanByDefault">true</bool>
- <!-- Enable windowSwipeToDismiss. -->
- <bool name="config_windowSwipeToDismiss">true</bool>
-
<!-- Style the scrollbars accoridngly. -->
<drawable name="config_scrollbarThumbVertical">@drawable/scrollbar_vertical_thumb</drawable>
<drawable name="config_scrollbarTrackVertical">@drawable/scrollbar_vertical_track</drawable>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index c427065..b0ec73b 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"无法从<xliff:g id="DEVICE">%1$s</xliff:g>上访问手机的摄像头"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"无法从<xliff:g id="DEVICE">%1$s</xliff:g>上访问平板电脑的摄像头"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"流式传输时无法访问此内容。您可以尝试在手机上访问。"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"在线播放时无法查看画中画"</string>
<string name="system_locale_title" msgid="711882686834677268">"系统默认设置"</string>
<string name="default_card_name" msgid="9198284935962911468">"SIM 卡 <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"用于管理手表的配套手表个人资料权限"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 05558f67..c498d14 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"無法從 <xliff:g id="DEVICE">%1$s</xliff:g> 存取手機的相機"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"無法從 <xliff:g id="DEVICE">%1$s</xliff:g> 存取平板電腦的相機"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"串流播放時無法使用,請改用手機。"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"串流期間無法查看畫中畫"</string>
<string name="system_locale_title" msgid="711882686834677268">"系統預設"</string>
<string name="default_card_name" msgid="9198284935962911468">"SIM 卡 <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"用於管理手錶的隨附手錶設定檔權限"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index f3a3203..0089747 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1943,7 +1943,7 @@
<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_regions_section_suggested" msgid="6080131515268225316">"建議的語言"</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>
<string name="language_picker_section_all" msgid="1985809075777564284">"所有語言"</string>
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"無法從 <xliff:g id="DEVICE">%1$s</xliff:g> 存取手機的相機"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"無法從 <xliff:g id="DEVICE">%1$s</xliff:g> 存取平板電腦的相機"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"串流播放時無法存取這項內容,請改用手機。"</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"串流播放時無法查看子母畫面"</string>
<string name="system_locale_title" msgid="711882686834677268">"系統預設"</string>
<string name="default_card_name" msgid="9198284935962911468">"SIM 卡 <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"用於管理智慧手錶的配對智慧手錶設定檔權限"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 1387032..a75f8eb 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -2316,6 +2316,7 @@
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ayikwazi ukufinyelela ikhamera yefoni kusuka ku-<xliff:g id="DEVICE">%1$s</xliff:g> yakho"</string>
<string name="vdm_camera_access_denied" product="tablet" msgid="6895968310395249076">"Ayikwazi ukufinyelela ikhamera yethebulethi kusuka ku-<xliff:g id="DEVICE">%1$s</xliff:g> yakho"</string>
<string name="vdm_secure_window" msgid="161700398158812314">"Lokhu akukwazi ukufinyelelwa ngenkathi usakaza. Zama efonini yakho kunalokho."</string>
+ <string name="vdm_pip_blocked" msgid="4036107522497281397">"Ayikwazi ukubuka isithombe esiphakathi kwesithombe ngenkathi isakaza"</string>
<string name="system_locale_title" msgid="711882686834677268">"Okuzenzakalelayo kwesistimu"</string>
<string name="default_card_name" msgid="9198284935962911468">"IKHADI <xliff:g id="CARDNUMBER">%d</xliff:g>"</string>
<string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Imvume yephrofayela ye-Companion Watch yokuphatha amawashi"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6ffcf20..42a249c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4125,6 +4125,10 @@
-->
<string name="config_wallpaperCropperPackage" translatable="false">com.android.wallpapercropper</string>
+ <!-- Wallpaper will get top app scheduling priority if this is set to true.
+ -->
+ <bool name="config_wallpaperTopApp">false</bool>
+
<!-- True if the device supports at least one form of multi-window.
E.g. freeform, split-screen, picture-in-picture. -->
<bool name="config_supportsMultiWindow">true</bool>
@@ -5255,7 +5259,7 @@
</string-array>
<!-- The integer index of the selected option in config_udfps_touch_detection_options -->
- <integer name="config_selected_udfps_touch_detection">3</integer>
+ <integer name="config_selected_udfps_touch_detection">0</integer>
<!-- An array of arrays of side fingerprint sensor properties relative to each display.
Note: this value is temporary and is expected to be queried directly
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d828f33..a11eaa9 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1786,6 +1786,10 @@
<string name="biometric_dialog_default_title">Verify it\u2019s you</string>
<!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face). [CHAR LIMIT=70] -->
<string name="biometric_dialog_default_subtitle">Use your biometric to continue</string>
+ <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with fingerprint. [CHAR LIMIT=70] -->
+ <string name="biometric_dialog_fingerprint_subtitle">Use your fingerprint to continue</string>
+ <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with face. [CHAR LIMIT=70] -->
+ <string name="biometric_dialog_face_subtitle">Use your face to continue</string>
<!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face) or their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=90] -->
<string name="biometric_or_screen_lock_dialog_default_subtitle">Use your biometric or screen lock to continue</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 96ba3d4..0951aec 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2581,6 +2581,8 @@
<java-symbol type="string" name="biometric_or_screen_lock_app_setting_name" />
<java-symbol type="string" name="biometric_dialog_default_title" />
<java-symbol type="string" name="biometric_dialog_default_subtitle" />
+ <java-symbol type="string" name="biometric_dialog_face_subtitle" />
+ <java-symbol type="string" name="biometric_dialog_fingerprint_subtitle" />
<java-symbol type="string" name="biometric_or_screen_lock_dialog_default_subtitle" />
<java-symbol type="string" name="biometric_error_hw_unavailable" />
<java-symbol type="string" name="biometric_error_user_canceled" />
@@ -3247,6 +3249,7 @@
<!-- WallpaperManager config -->
<java-symbol type="string" name="config_wallpaperCropperPackage" />
<java-symbol type="string" name="expand_action_accessibility" />
+ <java-symbol type="bool" name="config_wallpaperTopApp" />
<java-symbol type="id" name="textSpacerNoTitle" />
<java-symbol type="id" name="titleDividerNoCustom" />
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index ea5e7b6..fc72f61 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -137,4 +137,81 @@
fail("Timed out");
}
}
+
+ /**
+ * Create a database with one table with three columns.
+ */
+ private void createComplexDatabase() {
+ mDatabase.beginTransaction();
+ try {
+ mDatabase.execSQL("CREATE TABLE t1 (i int, d double, t text);");
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ mDatabase.endTransaction();
+ }
+ }
+
+ /**
+ * A three-value insert for the complex database.
+ */
+ private String createComplexInsert() {
+ return "INSERT INTO t1 (i, d, t) VALUES (?1, ?2, ?3)";
+ }
+
+ @Test
+ public void testAutomaticCounters() {
+ final int size = 10;
+
+ createComplexDatabase();
+
+ // Put 10 lines in the database.
+ mDatabase.beginTransaction();
+ try {
+ try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
+ for (int i = 0; i < size; i++) {
+ int vi = i * 3;
+ double vd = i * 2.5;
+ String vt = String.format("text%02dvalue", i);
+ s.bindInt(1, vi);
+ s.bindDouble(2, vd);
+ s.bindText(3, vt);
+ boolean r = s.step();
+ // No row is returned by this query.
+ assertFalse(r);
+ s.reset();
+ assertEquals(i + 1, mDatabase.getLastInsertRowId());
+ assertEquals(1, mDatabase.getLastChangedRowsCount());
+ assertEquals(i + 2, mDatabase.getTotalChangedRowsCount());
+ }
+ }
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ mDatabase.endTransaction();
+ }
+
+ // Put a second 10 lines in the database.
+ mDatabase.beginTransaction();
+ try {
+ try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
+ for (int i = 0; i < size; i++) {
+ int vi = i * 3;
+ double vd = i * 2.5;
+ String vt = String.format("text%02dvalue", i);
+ s.bindInt(1, vi);
+ s.bindDouble(2, vd);
+ s.bindText(3, vt);
+ boolean r = s.step();
+ // No row is returned by this query.
+ assertFalse(r);
+ s.reset();
+ assertEquals(size + i + 1, mDatabase.getLastInsertRowId());
+ assertEquals(1, mDatabase.getLastChangedRowsCount());
+ assertEquals(size + i + 2, mDatabase.getTotalChangedRowsCount());
+ }
+ }
+ mDatabase.setTransactionSuccessful();
+ } finally {
+ mDatabase.endTransaction();
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
index 59a1643..36bb8e5 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
@@ -846,59 +846,6 @@
}
@Test
- public void testLastInsertRowId() {
- final int size = 10;
-
- createComplexDatabase();
-
- // Put 10 lines in the database.
- mDatabase.beginTransaction();
- try {
- try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
- for (int i = 0; i < size; i++) {
- int vi = i * 3;
- double vd = i * 2.5;
- String vt = String.format("text%02dvalue", i);
- s.bindInt(1, vi);
- s.bindDouble(2, vd);
- s.bindText(3, vt);
- boolean r = s.step();
- // No row is returned by this query.
- assertFalse(r);
- s.reset();
- assertEquals(i + 1, mDatabase.getLastInsertRowId());
- }
- }
- mDatabase.setTransactionSuccessful();
- } finally {
- mDatabase.endTransaction();
- }
-
- // Put a second 10 lines in the database.
- mDatabase.beginTransaction();
- try {
- try (SQLiteRawStatement s = mDatabase.createRawStatement(createComplexInsert())) {
- for (int i = 0; i < size; i++) {
- int vi = i * 3;
- double vd = i * 2.5;
- String vt = String.format("text%02dvalue", i);
- s.bindInt(1, vi);
- s.bindDouble(2, vd);
- s.bindText(3, vt);
- boolean r = s.step();
- // No row is returned by this query.
- assertFalse(r);
- s.reset();
- assertEquals(size + i + 1, mDatabase.getLastInsertRowId());
- }
- }
- mDatabase.setTransactionSuccessful();
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- @Test
public void testUnicode() {
// Create the t1 table and put some data in it.
mDatabase.beginTransaction();
diff --git a/core/tests/coretests/src/android/flags/FeatureFlagsTest.java b/core/tests/coretests/src/android/flags/FeatureFlagsTest.java
new file mode 100644
index 0000000..3fc9439
--- /dev/null
+++ b/core/tests/coretests/src/android/flags/FeatureFlagsTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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 android.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+@SmallTest
+@Presubmit
+public class FeatureFlagsTest {
+
+ IFeatureFlagsFake mIFeatureFlagsFake = new IFeatureFlagsFake();
+ FeatureFlags mFeatureFlags = new FeatureFlags(mIFeatureFlagsFake);
+
+ @Before
+ public void setup() {
+ FeatureFlags.setInstance(mFeatureFlags);
+ }
+
+ @Test
+ public void testFusedOff_Disabled() {
+ FusedOffFlag flag = FeatureFlags.fusedOffFlag("test", "a");
+ assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+ }
+
+ @Test
+ public void testFusedOn_Enabled() {
+ FusedOnFlag flag = FeatureFlags.fusedOnFlag("test", "a");
+ assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testBooleanFlag_DefaultDisabled() {
+ BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", false);
+ assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+ }
+
+ @Test
+ public void testBooleanFlag_DefaultEnabled() {
+ BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", true);
+ assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testDynamicBooleanFlag_DefaultDisabled() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isFalse();
+ }
+
+ @Test
+ public void testDynamicBooleanFlag_DefaultEnabled() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", true);
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testBooleanFlag_OverrideBeforeRead() {
+ BooleanFlag flag = FeatureFlags.booleanFlag("test", "a", false);
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "true", false);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testFusedOffFlag_OverrideHasNoEffect() {
+ FusedOffFlag flag = FeatureFlags.fusedOffFlag("test", "a");
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "true", false);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ assertThat(mFeatureFlags.isEnabled(flag)).isFalse();
+ }
+
+ @Test
+ public void testFusedOnFlag_OverrideHasNoEffect() {
+ FusedOnFlag flag = FeatureFlags.fusedOnFlag("test", "a");
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "false", false);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ assertThat(mFeatureFlags.isEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testDynamicFlag_OverrideBeforeRead() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "true", true);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ // Changes to true
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testDynamicFlag_OverrideAfterRead() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), "true", true);
+
+ // Starts false
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isFalse();
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ // Changes to true
+ assertThat(mFeatureFlags.isCurrentlyEnabled(flag)).isTrue();
+ }
+
+ @Test
+ public void testDynamicFlag_FiresListener() {
+ DynamicBooleanFlag flag = FeatureFlags.dynamicBooleanFlag("test", "a", false);
+ AtomicBoolean called = new AtomicBoolean(false);
+ FeatureFlags.ChangeListener listener = flag1 -> called.set(true);
+
+ mFeatureFlags.addChangeListener(listener);
+
+ SyncableFlag syncableFlag = new SyncableFlag(
+ flag.getNamespace(), flag.getName(), flag.getDefault().toString(), true);
+
+ mIFeatureFlagsFake.setFlagOverrides(List.of(syncableFlag));
+
+ // Fires listener.
+ assertThat(called.get()).isTrue();
+ }
+}
diff --git a/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java b/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java
new file mode 100644
index 0000000..bc5d8aa
--- /dev/null
+++ b/core/tests/coretests/src/android/flags/IFeatureFlagsFake.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.flags;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+class IFeatureFlagsFake implements IFeatureFlags {
+
+ private final Set<IFeatureFlagsCallback> mCallbacks = new HashSet<>();
+
+ List<SyncableFlag> mOverrides;
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ @Override
+ public List<SyncableFlag> syncFlags(List<SyncableFlag> flagList) {
+ return mOverrides == null ? flagList : mOverrides;
+ }
+
+ @Override
+ public List<SyncableFlag> queryFlags(List<SyncableFlag> flagList) {
+ return mOverrides == null ? flagList : mOverrides; }
+
+ @Override
+ public void overrideFlag(SyncableFlag syncableFlag) {
+ SyncableFlag match = findFlag(syncableFlag);
+ if (match != null) {
+ mOverrides.remove(match);
+ }
+
+ mOverrides.add(syncableFlag);
+
+ for (IFeatureFlagsCallback cb : mCallbacks) {
+ try {
+ cb.onFlagChange(syncableFlag);
+ } catch (RemoteException e) {
+ // does not happen in fakes.
+ }
+ }
+ }
+
+ @Override
+ public void resetFlag(SyncableFlag syncableFlag) {
+ SyncableFlag match = findFlag(syncableFlag);
+ if (match != null) {
+ mOverrides.remove(match);
+ }
+
+ for (IFeatureFlagsCallback cb : mCallbacks) {
+ try {
+ cb.onFlagChange(syncableFlag);
+ } catch (RemoteException e) {
+ // does not happen in fakes.
+ }
+ }
+ }
+
+ private SyncableFlag findFlag(SyncableFlag syncableFlag) {
+ SyncableFlag match = null;
+ for (SyncableFlag sf : mOverrides) {
+ if (sf.getName().equals(syncableFlag.getName())
+ && sf.getNamespace().equals(syncableFlag.getNamespace())) {
+ match = sf;
+ break;
+ }
+ }
+
+ return match;
+ }
+ @Override
+ public void registerCallback(IFeatureFlagsCallback callback) {
+ mCallbacks.add(callback);
+ }
+
+ @Override
+ public void unregisterCallback(IFeatureFlagsCallback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ public void setFlagOverrides(List<SyncableFlag> flagList) {
+ mOverrides = flagList;
+ for (SyncableFlag sf : flagList) {
+ for (IFeatureFlagsCallback cb : mCallbacks) {
+ try {
+ cb.onFlagChange(sf);
+ } catch (RemoteException e) {
+ // does not happen in fakes.
+ }
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
index c0fdf9f..b31af89 100644
--- a/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
+++ b/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
@@ -29,10 +29,14 @@
import android.os.VibrationEffect;
import android.os.vibrator.PrebakedSegment;
import android.platform.test.annotations.Presubmit;
+import android.util.Xml;
+
+import com.android.modules.utils.TypedXmlPullParser;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.xmlpull.v1.XmlPullParser;
import java.io.IOException;
import java.io.StringReader;
@@ -65,6 +69,61 @@
}
@Test
+ public void testParseTag_succeedAndParserPointsToEndVibrationTag() throws Exception {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_TICK, 0.2497f)
+ .compose();
+ String xml = "<vibration>"
+ + "<primitive-effect name=\"click\"/>"
+ + "<primitive-effect name=\"tick\" scale=\"0.2497\"/>"
+ + "</vibration>";
+ VibrationEffect effect2 = VibrationEffect.startComposition()
+ .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
+ .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
+ .compose();
+ String xml2 = "<vibration>"
+ + "<primitive-effect name=\"low_tick\" delayMs=\"356\"/>"
+ + "<primitive-effect name=\"spin\" scale=\"0.6364\" delayMs=\"7\"/>"
+ + "</vibration>";
+
+ TypedXmlPullParser parser = createXmlPullParser(xml);
+ assertParseTagSucceeds(parser, effect);
+ parser.next();
+ assertEndOfDocument(parser);
+
+ // Test no-issues when an end-tag follows the vibration XML.
+ // To test this, starting with the corresponding "start-tag" is necessary.
+ parser = createXmlPullParser("<next-tag>" + xml + "</next-tag>");
+ // Move the parser once to point to the "<vibration> tag.
+ parser.next();
+ assertParseTagSucceeds(parser, effect);
+ parser.next();
+ assertEndTag(parser, "next-tag");
+
+ parser = createXmlPullParser(xml + "<next-tag>");
+ assertParseTagSucceeds(parser, effect);
+ parser.next();
+ assertStartTag(parser, "next-tag");
+
+ parser = createXmlPullParser(xml + xml2);
+ assertParseTagSucceeds(parser, effect);
+ parser.next();
+ assertParseTagSucceeds(parser, effect2);
+ parser.next();
+ assertEndOfDocument(parser);
+ }
+
+ @Test
+ public void testParseTag_badXml_throwsException() throws Exception {
+ assertParseTagFails(
+ "<vibration>random text<primitive-effect name=\"click\"/></vibration>");
+ assertParseTagFails("<bad-tag><primitive-effect name=\"click\"/></vibration>");
+ assertParseTagFails("<primitive-effect name=\"click\"/></vibration>");
+ assertParseTagFails("<vibration><primitive-effect name=\"click\"/>");
+ }
+
+ @Test
public void testPrimitives_allSucceed() throws IOException {
VibrationEffect effect = VibrationEffect.startComposition()
.addPrimitive(PRIMITIVE_CLICK)
@@ -172,6 +231,41 @@
assertThat(parse(xml, /* flags= */ 0)).isEqualTo(effect);
}
+ private TypedXmlPullParser createXmlPullParser(String xml) throws Exception {
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ parser.setInput(new StringReader(xml));
+ parser.next(); // read START_DOCUMENT
+ return parser;
+ }
+
+ /**
+ * Asserts parsing vibration from an open TypedXmlPullParser succeeds, and that the parser
+ * points to the end "vibration" tag.
+ */
+ private void assertParseTagSucceeds(
+ TypedXmlPullParser parser, VibrationEffect effect) throws Exception {
+ assertThat(parseTag(parser)).isEqualTo(effect);
+
+ assertThat(parser.getEventType()).isEqualTo(XmlPullParser.END_TAG);
+ assertThat(parser.getName()).isEqualTo("vibration");
+ }
+
+ private void assertEndTag(TypedXmlPullParser parser, String expectedTagName) throws Exception {
+ assertThat(parser.getName()).isEqualTo(expectedTagName);
+ assertThat(parser.getEventType()).isEqualTo(parser.END_TAG);
+ }
+
+ private void assertStartTag(TypedXmlPullParser parser, String expectedTagName)
+ throws Exception {
+ assertThat(parser.getName()).isEqualTo(expectedTagName);
+ assertThat(parser.getEventType()).isEqualTo(parser.START_TAG);
+ }
+
+ private void assertEndOfDocument(TypedXmlPullParser parser) throws Exception {
+ assertThat(parser.getEventType()).isEqualTo(parser.END_DOCUMENT);
+ }
+
private void assertHiddenApisParserSucceeds(String xml, VibrationEffect effect)
throws IOException {
assertThat(parse(xml, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS)).isEqualTo(effect);
@@ -183,6 +277,12 @@
() -> serialize(effect, /* flags= */ 0));
}
+ private void assertParseTagFails(String xml) {
+ assertThrows("Expected parsing to fail for " + xml,
+ VibrationXmlParser.VibrationXmlParserException.class,
+ () -> parseTag(createXmlPullParser(xml)));
+ }
+
private void assertPublicApisSerializerSucceeds(VibrationEffect effect,
String... expectedSegments) throws IOException {
assertSerializationContainsSegments(serialize(effect, /* flags= */ 0), expectedSegments);
@@ -214,6 +314,11 @@
return VibrationXmlParser.parse(new StringReader(xml), flags);
}
+ private static VibrationEffect parseTag(TypedXmlPullParser parser)
+ throws IOException, VibrationXmlParser.VibrationXmlParserException {
+ return VibrationXmlParser.parseTag(parser, VibrationXmlParser.FLAG_ALLOW_HIDDEN_APIS);
+ }
+
private static String serialize(VibrationEffect effect, @VibrationXmlSerializer.Flags int flags)
throws IOException {
StringWriter writer = new StringWriter();
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
index 184b9eac..4f722ce 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -353,6 +353,23 @@
public boolean isCreated() {
return false;
}
+
+ @Override
+ public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+ RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
+ new RemoteViews.RemoteCollectionItems.Builder();
+ itemsBuilder.setHasStableIds(hasStableIds())
+ .setViewTypeCount(getViewTypeCount());
+ try {
+ for (int i = 0; i < mCount; i++) {
+ itemsBuilder.addItem(getItemId(i), getViewAt(i));
+ }
+ } catch (RemoteException e) {
+ // No-op
+ }
+
+ return itemsBuilder.build();
+ }
}
private static class DistinctIntent extends Intent {
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 2c85fe4..c4530f6 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -346,4 +346,8 @@
<!-- Allow IMS service entitlement app to schedule jobs to run when app in background. -->
<allow-in-power-save-except-idle package="com.android.imsserviceentitlement" />
+
+ <!-- Allow device lock controller app to schedule jobs and alarms when app in background,
+ otherwise, it may not be able to enforce provision for managed devices. -->
+ <allow-in-power-save package="com.android.devicelockcontroller" />
</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index aa0a8d9..3206dd2 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -257,6 +257,7 @@
<permission name="android.permission.CLEAR_APP_CACHE"/>
<permission name="android.permission.ACCESS_INSTANT_APPS" />
<permission name="android.permission.CONNECTIVITY_INTERNAL"/>
+ <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
<permission name="android.permission.DELETE_CACHE_FILES"/>
<permission name="android.permission.DELETE_PACKAGES"/>
<permission name="android.permission.DUMP"/>
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
index 2b1515a..3bb2564 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.pm.PackageManager;
import android.hardware.security.keymint.KeyParameter;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyProperties;
@@ -300,12 +299,6 @@
return false;
}
- private static boolean hasKeyMintV2() {
- PackageManager pm = android.app.AppGlobals.getInitialApplication().getPackageManager();
- return pm.hasSystemFeature(PackageManager.FEATURE_HARDWARE_KEYSTORE, 200)
- && !pm.hasSystemFeature(PackageManager.FEATURE_HARDWARE_KEYSTORE, 300);
- }
-
@Override
protected final void addAlgorithmSpecificParametersToBegin(
@NonNull List<KeyParameter> parameters, Authorization[] keyCharacteristics) {
@@ -314,12 +307,11 @@
KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest
));
// Only add the KM_TAG_RSA_OAEP_MGF_DIGEST tag to begin() if the MGF Digest is
- // present in the key properties or KeyMint version is 200. Keys generated prior to
- // Android 14 did not have this tag (Keystore didn't add it) and hence not present in
- // imported key as well, so specifying any MGF digest tag would cause a begin()
- // operation (on an Android 14 device) to fail (with a key that was generated on
- // Android 13 or below).
- if (isMgfDigestTagPresentInKeyProperties(keyCharacteristics) || hasKeyMintV2()) {
+ // present in the key properties. Keys generated prior to Android 14 did not have
+ // this tag (Keystore didn't add it) so specifying any MGF digest tag would cause
+ // a begin() operation (on an Android 14 device) to fail (with a key that was generated
+ // on Android 13 or below).
+ if (isMgfDigestTagPresentInKeyProperties(keyCharacteristics)) {
parameters.add(KeyStore2ParameterUtils.makeEnum(
KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mKeymasterMgf1Digest
));
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 0059577..ccf9552 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -296,7 +296,7 @@
* {@link IllegalArgumentException} since this can cause negative UI effects down stream.
*
* @param context a proxy for the {@link android.view.Window} that contains the
- * {@link DisplayFeature}.
+ * {@link DisplayFeature}.
* @return a {@link List} of {@link DisplayFeature}s that are within the
* {@link android.view.Window} of the {@link Activity}
*/
@@ -328,10 +328,32 @@
rotateRectToDisplayRotation(displayId, featureRect);
transformToWindowSpaceRect(windowConfiguration, featureRect);
- if (!isZero(featureRect)) {
+ if (isZero(featureRect)) {
// TODO(b/228641877): Remove guarding when fixed.
- features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
+ continue;
}
+ if (featureRect.left != 0 && featureRect.top != 0) {
+ throw new IllegalArgumentException("Bounding rectangle must start at the top or "
+ + "left of the window. BaseFeatureRect: " + baseFeature.getRect()
+ + ", FeatureRect: " + featureRect
+ + ", WindowConfiguration: " + windowConfiguration);
+
+ }
+ if (featureRect.left == 0
+ && featureRect.width() != windowConfiguration.getBounds().width()) {
+ throw new IllegalArgumentException("Horizontal FoldingFeature must have full width."
+ + " BaseFeatureRect: " + baseFeature.getRect()
+ + ", FeatureRect: " + featureRect
+ + ", WindowConfiguration: " + windowConfiguration);
+ }
+ if (featureRect.top == 0
+ && featureRect.height() != windowConfiguration.getBounds().height()) {
+ throw new IllegalArgumentException("Vertical FoldingFeature must have full height."
+ + " BaseFeatureRect: " + baseFeature.getRect()
+ + ", FeatureRect: " + featureRect
+ + ", WindowConfiguration: " + windowConfiguration);
+ }
+ features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
}
return features;
}
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 6d14440..f8d7b6b 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
@@ -202,17 +202,18 @@
}
/**
- * Moves a single task to freeform and sets the taskBounds to the passed in bounds,
- * startBounds
+ * The first part of the animated move to desktop transition. Applies the changes to move task
+ * to desktop mode and sets the taskBounds to the passed in bounds, startBounds. This is
+ * followed with a call to {@link finishMoveToDesktop} or {@link cancelMoveToDesktop}.
*/
- fun moveToFreeform(
+ fun startMoveToDesktop(
taskInfo: RunningTaskInfo,
startBounds: Rect,
dragToDesktopValueAnimator: MoveToDesktopAnimator
) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveToFreeform with bounds taskId=%d",
+ "DesktopTasksController: startMoveToDesktop taskId=%d",
taskInfo.taskId
)
val wct = WindowContainerTransaction()
@@ -221,18 +222,21 @@
wct.setBounds(taskInfo.token, startBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct,
- dragToDesktopValueAnimator, mOnAnimationFinishedCallback)
+ enterDesktopTaskTransitionHandler.startMoveToDesktop(wct, dragToDesktopValueAnimator,
+ mOnAnimationFinishedCallback)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
}
- /** Brings apps to front and sets freeform task bounds */
- private fun moveToDesktopWithAnimation(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
+ /**
+ * The second part of the animated move to desktop transition, called after
+ * {@link startMoveToDesktop}. Brings apps to front and sets freeform task bounds.
+ */
+ private fun finalizeMoveToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: moveToDesktop with animation taskId=%d",
+ "DesktopTasksController: finalizeMoveToDesktop taskId=%d",
taskInfo.taskId
)
val wct = WindowContainerTransaction()
@@ -241,8 +245,8 @@
wct.setBounds(taskInfo.token, freeformBounds)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- enterDesktopTaskTransitionHandler.startTransition(
- Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct, mOnAnimationFinishedCallback)
+ enterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct,
+ mOnAnimationFinishedCallback)
} else {
shellTaskOrganizer.applyTransaction(wct)
releaseVisualIndicator()
@@ -272,13 +276,14 @@
}
/**
- * Move a task to fullscreen after being dragged from fullscreen and released back into
- * status bar area
+ * The second part of the animated move to desktop transition, called after
+ * {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen
+ * and released back into status bar area.
*/
- fun cancelMoveToFreeform(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) {
+ fun cancelMoveToDesktop(task: RunningTaskInfo, moveToDesktopAnimator: MoveToDesktopAnimator) {
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: cancelMoveToFreeform taskId=%d",
+ "DesktopTasksController: cancelMoveToDesktop taskId=%d",
task.taskId
)
val wct = WindowContainerTransaction()
@@ -784,7 +789,7 @@
taskInfo: RunningTaskInfo,
freeformBounds: Rect
) {
- moveToDesktopWithAnimation(taskInfo, freeformBounds)
+ finalizeMoveToDesktop(taskInfo, freeformBounds)
}
private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 650cac5..1acf783 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -79,7 +79,7 @@
* @param wct WindowContainerTransaction for transition
* @param onAnimationEndCallback to be called after animation
*/
- public void startTransition(@WindowManager.TransitionType int type,
+ private void startTransition(@WindowManager.TransitionType int type,
@NonNull WindowContainerTransaction wct,
Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
mOnAnimationFinishedCallback = onAnimationEndCallback;
@@ -88,17 +88,29 @@
}
/**
- * Starts Transition of type TRANSIT_ENTER_FREEFORM
+ * Starts Transition of type TRANSIT_START_MOVE_TO_DESKTOP_MODE
* @param wct WindowContainerTransaction for transition
* @param moveToDesktopAnimator Animator that shrinks and positions task during two part move
* to desktop animation
* @param onAnimationEndCallback to be called after animation
*/
- public void startMoveToFreeformAnimation(@NonNull WindowContainerTransaction wct,
+ public void startMoveToDesktop(@NonNull WindowContainerTransaction wct,
@NonNull MoveToDesktopAnimator moveToDesktopAnimator,
Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
mMoveToDesktopAnimator = moveToDesktopAnimator;
- startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct, onAnimationEndCallback);
+ startTransition(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, wct,
+ onAnimationEndCallback);
+ }
+
+ /**
+ * Starts Transition of type TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
+ * @param wct WindowContainerTransaction for transition
+ * @param onAnimationEndCallback to be called after animation
+ */
+ public void finalizeMoveToDesktop(@NonNull WindowContainerTransaction wct,
+ Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+ startTransition(Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE, wct,
+ onAnimationEndCallback);
}
/**
@@ -155,7 +167,7 @@
}
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (type == Transitions.TRANSIT_ENTER_FREEFORM
+ if (type == Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
// Transitioning to freeform but keeping fullscreen bounds, so the crop is set
// to null and we don't require an animation
@@ -182,7 +194,7 @@
}
Rect endBounds = change.getEndAbsBounds();
- if (type == Transitions.TRANSIT_ENTER_DESKTOP_MODE
+ if (type == Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
&& !endBounds.isEmpty()) {
// This Transition animates a task to freeform bounds after being dragged into freeform
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 7565996..4ca383f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -149,11 +149,13 @@
/** Transition type for maximize to freeform transition. */
public static final int TRANSIT_RESTORE_FROM_MAXIMIZE = WindowManager.TRANSIT_FIRST_CUSTOM + 9;
- /** Transition type to freeform in desktop mode. */
- public static final int TRANSIT_ENTER_FREEFORM = WindowManager.TRANSIT_FIRST_CUSTOM + 10;
+ /** Transition type for starting the move to desktop mode. */
+ public static final int TRANSIT_START_MOVE_TO_DESKTOP_MODE =
+ WindowManager.TRANSIT_FIRST_CUSTOM + 10;
- /** Transition type to freeform in desktop mode. */
- public static final int TRANSIT_ENTER_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 11;
+ /** Transition type for finalizing the move to desktop mode. */
+ public static final int TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE =
+ WindowManager.TRANSIT_FIRST_CUSTOM + 11;
/** Transition type to fullscreen from desktop mode. */
public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
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 1a18fc2..80cf96a 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
@@ -197,7 +197,7 @@
@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change) {
if (change.getMode() == WindowManager.TRANSIT_CHANGE
- && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE
+ && (info.getType() == Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE
|| info.getType() == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
|| info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
|| info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) {
@@ -616,7 +616,7 @@
} else if (mMoveToDesktopAnimator != null) {
relevantDecor.incrementRelayoutBlock();
mDesktopTasksController.ifPresent(
- c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
+ c -> c.cancelMoveToDesktop(relevantDecor.mTaskInfo,
mMoveToDesktopAnimator));
mMoveToDesktopAnimator = null;
return;
@@ -643,7 +643,7 @@
mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo,
relevantDecor.mTaskSurface);
mDesktopTasksController.ifPresent(
- c -> c.moveToFreeform(relevantDecor.mTaskInfo,
+ c -> c.startMoveToDesktop(relevantDecor.mTaskInfo,
mDragToDesktopAnimationStartBounds,
mMoveToDesktopAnimator));
mMoveToDesktopAnimator.startAnimation();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt
index fd56a6e..8a3c2c9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt
@@ -42,7 +42,7 @@
import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary
import org.junit.Assert.assertNotNull
-internal object SplitScreenUtils {
+object SplitScreenUtils {
private const val TIMEOUT_MS = 3_000L
private const val DRAG_DURATION_MS = 1_000L
private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index 0f9579d..69c8ecd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.appcompat
import android.content.Context
-import android.system.helpers.CommandsHelper
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTestData
@@ -29,15 +28,18 @@
import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.appWindowKeepVisible
import com.android.wm.shell.flicker.layerKeepVisible
-import org.junit.After
+
import org.junit.Assume
import org.junit.Before
+import org.junit.Rule
abstract class BaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
protected val context: Context = instrumentation.context
protected val letterboxApp = LetterboxAppHelper(instrumentation)
- lateinit var cmdHelper: CommandsHelper
- private lateinit var letterboxStyle: HashMap<String, String>
+
+ @JvmField
+ @Rule
+ val letterboxRule: LetterboxRule = LetterboxRule()
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -52,50 +54,7 @@
@Before
fun before() {
- cmdHelper = CommandsHelper.getInstance(instrumentation)
- Assume.assumeTrue(tapl.isTablet && isIgnoreOrientationRequest())
- letterboxStyle = mapLetterboxStyle()
- resetLetterboxStyle()
- setLetterboxEducationEnabled(false)
- }
-
- @After
- fun after() {
- resetLetterboxStyle()
- }
-
- private fun mapLetterboxStyle(): HashMap<String, String> {
- val res = cmdHelper.executeShellCommand("wm get-letterbox-style")
- val lines = res.lines()
- val map = HashMap<String, String>()
- for (line in lines) {
- val keyValuePair = line.split(":")
- if (keyValuePair.size == 2) {
- val key = keyValuePair[0].trim()
- map[key] = keyValuePair[1].trim()
- }
- }
- return map
- }
-
- private fun getLetterboxStyle(): HashMap<String, String> {
- if (!::letterboxStyle.isInitialized) {
- letterboxStyle = mapLetterboxStyle()
- }
- return letterboxStyle
- }
-
- private fun resetLetterboxStyle() {
- cmdHelper.executeShellCommand("wm reset-letterbox-style")
- }
-
- private fun setLetterboxEducationEnabled(enabled: Boolean) {
- cmdHelper.executeShellCommand("wm set-letterbox-style --isEducationEnabled $enabled")
- }
-
- private fun isIgnoreOrientationRequest(): Boolean {
- val res = cmdHelper.executeShellCommand("wm get-ignore-orientation-request")
- return res != null && res.contains("true")
+ Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
}
fun FlickerTestData.setStartRotation() = setRotation(flicker.scenario.startRotation)
@@ -115,7 +74,7 @@
/** Only run on tests with config_letterboxActivityCornersRadius != 0 in devices */
private fun assumeLetterboxRoundedCornersEnabled() {
- Assume.assumeTrue(getLetterboxStyle().getValue("Corner radius") != "0")
+ Assume.assumeTrue(letterboxRule.hasCornerRadius)
}
fun assertLetterboxAppVisibleAtStartAndEnd() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
new file mode 100644
index 0000000..5a1136f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/LetterboxRule.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.app.Instrumentation
+import android.system.helpers.CommandsHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * JUnit Rule to handle letterboxStyles and states
+ */
+class LetterboxRule(
+ private val withLetterboxEducationEnabled: Boolean = false,
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
+ private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
+) : TestRule {
+
+ private val execAdb: (String) -> String = {cmd -> cmdHelper.executeShellCommand(cmd)}
+ private lateinit var _letterboxStyle: MutableMap<String, String>
+
+ val letterboxStyle: Map<String, String>
+ get() {
+ if (!::_letterboxStyle.isInitialized) {
+ _letterboxStyle = mapLetterboxStyle()
+ }
+ return _letterboxStyle
+ }
+
+ val cornerRadius: Int?
+ get() = asInt(letterboxStyle["Corner radius"])
+
+ val hasCornerRadius: Boolean
+ get() {
+ val radius = cornerRadius
+ return radius != null && radius > 0
+ }
+
+ val isIgnoreOrientationRequest: Boolean
+ get() = execAdb("wm get-ignore-orientation-request")?.contains("true") ?: false
+
+ override fun apply(base: Statement?, description: Description?): Statement {
+ resetLetterboxStyle()
+ _letterboxStyle = mapLetterboxStyle()
+ val isLetterboxEducationEnabled = _letterboxStyle.getValue("Is education enabled")
+ var hasLetterboxEducationStateChanged = false
+ if ("$withLetterboxEducationEnabled" != isLetterboxEducationEnabled) {
+ hasLetterboxEducationStateChanged = true
+ execAdb("wm set-letterbox-style --isEducationEnabled " +
+ withLetterboxEducationEnabled)
+ }
+ return try {
+ object : Statement() {
+ @Throws(Throwable::class)
+ override fun evaluate() {
+ base!!.evaluate()
+ }
+ }
+ } finally {
+ if (hasLetterboxEducationStateChanged) {
+ execAdb("wm set-letterbox-style --isEducationEnabled " +
+ isLetterboxEducationEnabled
+ )
+ }
+ resetLetterboxStyle()
+ }
+ }
+
+ private fun mapLetterboxStyle(): HashMap<String, String> {
+ val res = execAdb("wm get-letterbox-style")
+ val lines = res.lines()
+ val map = HashMap<String, String>()
+ for (line in lines) {
+ val keyValuePair = line.split(":")
+ if (keyValuePair.size == 2) {
+ val key = keyValuePair[0].trim()
+ map[key] = keyValuePair[1].trim()
+ }
+ }
+ return map
+ }
+
+ private fun resetLetterboxStyle() {
+ execAdb("wm reset-letterbox-style")
+ }
+
+ private fun asInt(str: String?): Int? = try {
+ str?.toInt()
+ } catch (e: NumberFormatException) {
+ null
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
index a7bd258..67d5718 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenAppInSizeCompatModeTest.kt
@@ -31,7 +31,7 @@
/**
* Test launching app in size compat mode.
*
- * To run this test: `atest WMShellFlickerTests:OpenAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsOther:OpenAppInSizeCompatModeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
new file mode 100644
index 0000000..e6ca261
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.platform.test.annotations.Postsubmit
+import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching app in size compat mode.
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:OpenTransparentActivityTest`
+ *
+ * Actions:
+ * ```
+ * Launch a letteboxed app and then a transparent activity from it. We test the bounds
+ * are the same.
+ * ```
+ *
+ * Notes:
+ * ```
+ * Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [BaseTest]
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseAppCompat(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ letterboxTranslucentLauncherApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ waitAndGetLaunchTransparent()?.click() ?: error("Launch Transparent not found")
+ }
+ teardown {
+ letterboxTranslucentApp.exit(wmHelper)
+ letterboxTranslucentLauncherApp.exit(wmHelper)
+ }
+ }
+
+ /**
+ * Checks the transparent activity is launched on top of the opaque one
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityIsLaunchedOnTopOfOpaqueActivity() {
+ flicker.assertWm {
+ this.isAppWindowOnTop(letterboxTranslucentLauncherApp)
+ .then()
+ .isAppWindowOnTop(letterboxTranslucentApp)
+ }
+ }
+
+ /**
+ * Checks that the activity is letterboxed
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityIsLetterboxed() {
+ flicker.assertLayers { isVisible(ComponentNameMatcher.LETTERBOX) }
+ }
+
+ /**
+ * Checks that the translucent activity inherits bounds from the opaque one.
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityInheritsBoundsFromOpaqueActivity() {
+ flicker.assertLayersEnd {
+ this.visibleRegion(letterboxTranslucentApp)
+ .coversExactly(visibleRegion(letterboxTranslucentLauncherApp).region)
+ }
+ }
+
+ /**
+ * Checks that the translucent activity has rounded corners
+ */
+ @Postsubmit
+ @Test
+ fun translucentActivityHasRoundedCorners() {
+ flicker.assertLayersEnd {
+ this.hasRoundedCorners(letterboxTranslucentApp)
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.rotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return LegacyFlickerTestFactory
+ .nonRotationTests(supportedRotations = listOf(Rotation.ROTATION_90))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
index e875aae..68fa8d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RepositionFixedPortraitAppTest.kt
@@ -32,7 +32,9 @@
/**
* Test launching a fixed portrait letterboxed app in landscape and repositioning to the right.
*
- * To run this test: `atest WMShellFlickerTests:RepositionFixedPortraitAppTest` Actions:
+ * To run this test: `atest WMShellFlickerTestsOther:RepositionFixedPortraitAppTest`
+ *
+ * Actions:
*
* ```
* Launch a fixed portrait app in landscape to letterbox app
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
index a18a144..fcb6931a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RestartAppInSizeCompatModeTest.kt
@@ -31,7 +31,7 @@
/**
* Test restarting app in size compat mode.
*
- * To run this test: `atest WMShellFlickerTests:RestartAppInSizeCompatModeTest`
+ * To run this test: `atest WMShellFlickerTestsOther:RestartAppInSizeCompatModeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
new file mode 100644
index 0000000..ea0392c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/TransparentBaseAppCompat.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.content.Context
+import android.tools.device.flicker.legacy.FlickerTestData
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.helpers.FIND_TIMEOUT
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.BaseTest
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+
+abstract class TransparentBaseAppCompat(flicker: LegacyFlickerTest) : BaseTest(flicker) {
+ protected val context: Context = instrumentation.context
+ protected val letterboxTranslucentLauncherApp = LetterboxAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.LaunchTransparentActivity.LABEL,
+ component = ActivityOptions.LaunchTransparentActivity.COMPONENT.toFlickerComponent()
+ )
+ protected val letterboxTranslucentApp = LetterboxAppHelper(
+ instrumentation,
+ launcherName = ActivityOptions.TransparentActivity.LABEL,
+ component = ActivityOptions.TransparentActivity.COMPONENT.toFlickerComponent()
+ )
+
+ @JvmField
+ @Rule
+ val letterboxRule: LetterboxRule = LetterboxRule()
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
+ }
+
+ protected fun FlickerTestData.waitAndGetLaunchTransparent(): UiObject2? =
+ device.wait(
+ Until.findObject(By.text("Launch Transparent")),
+ FIND_TIMEOUT
+ )
+
+ protected fun FlickerTestData.goBack() = device.pressBack()
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
deleted file mode 100644
index 92b6227..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavPortraitBenchmark.kt
+++ /dev/null
@@ -1,32 +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.wm.shell.flicker.service.splitscreen.benchmark
-
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
-import org.junit.Test
-
-@RequiresDevice
-class CopyContentInSplitGesturalNavPortraitBenchmark : CopyContentInSplit(Rotation.ROTATION_0) {
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun copyContentInSplit() = super.copyContentInSplit()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
deleted file mode 100644
index 0495079..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavLandscapeBenchmark.kt
+++ /dev/null
@@ -1,32 +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.wm.shell.flicker.service.splitscreen.benchmark
-
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
-import org.junit.Test
-
-@RequiresDevice
-class DragDividerToResizeGesturalNavLandscapeBenchmark : DragDividerToResize(Rotation.ROTATION_90) {
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun dragDividerToResize() = super.dragDividerToResize()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
deleted file mode 100644
index aab18a6..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark.kt
+++ /dev/null
@@ -1,33 +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.wm.shell.flicker.service.splitscreen.benchmark
-
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
-import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
-import org.junit.Test
-
-@RequiresDevice
-class SwitchBetweenSplitPairsGesturalNavLandscapeBenchmark :
- SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
deleted file mode 100644
index 840401c..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark.kt
+++ /dev/null
@@ -1,34 +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.wm.shell.flicker.service.splitscreen.benchmark
-
-import android.platform.test.annotations.PlatinumTest
-import android.platform.test.annotations.Presubmit
-import androidx.test.filters.RequiresDevice
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.BlockJUnit4ClassRunner
-
-@RequiresDevice
-@RunWith(BlockJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavPortraitBenchmark : UnlockKeyguardToSplitScreen() {
- @PlatinumTest(focusArea = "sysui")
- @Presubmit
- @Test
- override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
deleted file mode 100644
index a5c5122..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
+++ /dev/null
@@ -1,40 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
- @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
deleted file mode 100644
index 092fb67..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
+++ /dev/null
@@ -1,40 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
- @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
deleted file mode 100644
index 8cb25fe..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class DismissSplitScreenByDividerGesturalNavLandscape :
- DismissSplitScreenByDivider(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
- @Test
- override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
deleted file mode 100644
index fa1be63..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class DismissSplitScreenByDividerGesturalNavPortrait :
- DismissSplitScreenByDivider(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
- @Test
- override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
deleted file mode 100644
index aa35237..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class DismissSplitScreenByGoHomeGesturalNavLandscape :
- DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
- @Test
- override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
deleted file mode 100644
index e195360..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class DismissSplitScreenByGoHomeGesturalNavPortrait :
- DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
- @Test
- override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
deleted file mode 100644
index c1b3aad..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
+++ /dev/null
@@ -1,43 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
- @Test
- override fun dragDividerToResize() = super.dragDividerToResize()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
deleted file mode 100644
index c6e2e85..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
+++ /dev/null
@@ -1,43 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
- @Test
- override fun dragDividerToResize() = super.dragDividerToResize()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
deleted file mode 100644
index 5f771c7..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
- EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
deleted file mode 100644
index 729a401..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
- EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
deleted file mode 100644
index 6e4cf9f..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
+++ /dev/null
@@ -1,45 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
- EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromNotification() =
- super.enterSplitScreenByDragFromNotification()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
deleted file mode 100644
index cc28702..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
+++ /dev/null
@@ -1,45 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
- EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromNotification() =
- super.enterSplitScreenByDragFromNotification()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
deleted file mode 100644
index 736604f..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
- EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
deleted file mode 100644
index 8df8dfa..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
- EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
deleted file mode 100644
index 378f055..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
- EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
deleted file mode 100644
index b33d262..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
- EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
deleted file mode 100644
index b1d3858..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenFromOverviewGesturalNavLandscape :
- EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
deleted file mode 100644
index 6d824c7..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class EnterSplitScreenFromOverviewGesturalNavPortrait :
- EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
- @Test
- override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
deleted file mode 100644
index f1d3d0c..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchAppByDoubleTapDividerGesturalNavLandscape :
- SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
-
- @ExpectedScenarios([])
- @Test
- override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
deleted file mode 100644
index a867bac..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchAppByDoubleTapDividerGesturalNavPortrait :
- SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
-
- @ExpectedScenarios([])
- @Test
- override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
deleted file mode 100644
index 76247ba..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
- SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
deleted file mode 100644
index e179da8..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
- SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
deleted file mode 100644
index 20f554f..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBackToSplitFromHomeGesturalNavLandscape :
- SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
deleted file mode 100644
index f7776ee..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBackToSplitFromHomeGesturalNavPortrait :
- SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
deleted file mode 100644
index 00f6073..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBackToSplitFromRecentGesturalNavLandscape :
- SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
deleted file mode 100644
index b3340e7..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ /dev/null
@@ -1,44 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBackToSplitFromRecentGesturalNavPortrait :
- SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
deleted file mode 100644
index 3da61e5..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
+++ /dev/null
@@ -1,43 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
deleted file mode 100644
index 627ae18..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
+++ /dev/null
@@ -1,43 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.Rotation
-import android.tools.common.flicker.FlickerConfig
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.common.flicker.annotation.FlickerConfigProvider
-import android.tools.common.flicker.config.FlickerConfig
-import android.tools.common.flicker.config.FlickerServiceConfig
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
-
- companion object {
- @JvmStatic
- @FlickerConfigProvider
- fun flickerConfigProvider(): FlickerConfig =
- FlickerConfig().use(FlickerServiceConfig.DEFAULT)
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
deleted file mode 100644
index 7cbc1c3..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
+++ /dev/null
@@ -1,31 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
deleted file mode 100644
index 2eb81e0..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
+++ /dev/null
@@ -1,31 +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.wm.shell.flicker.service.splitscreen.flicker
-
-import android.tools.common.flicker.annotation.ExpectedScenarios
-import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
-import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(FlickerServiceJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
-
- @ExpectedScenarios(["QUICKSWITCH"])
- @Test
- override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
similarity index 81%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
index 566adec..2494054 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
import org.junit.Test
-@RequiresDevice
-class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) {
+class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
similarity index 81%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
index 566adec..57943ec 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/CopyContentInSplitGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
import org.junit.Test
-@RequiresDevice
-class CopyContentInSplitGesturalNavLandscapeBenchmark : CopyContentInSplit(Rotation.ROTATION_90) {
+class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
index e6d56b5..6f0e202 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByDividerGesturalNavLandscapeBenchmark :
+class DismissSplitScreenByDividerGesturalNavLandscape :
DismissSplitScreenByDivider(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
index 6752c58..dac8fa2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByDividerGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByDividerGesturalNavPortraitBenchmark :
+class DismissSplitScreenByDividerGesturalNavPortrait :
DismissSplitScreenByDivider(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index 7c9ab99..baecc16 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByGoHomeGesturalNavLandscapeBenchmark :
+class DismissSplitScreenByGoHomeGesturalNavLandscape :
DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index 4b79571..3063ea5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
import org.junit.Test
-@RequiresDevice
-class DismissSplitScreenByGoHomeGesturalNavPortraitBenchmark :
+class DismissSplitScreenByGoHomeGesturalNavPortrait :
DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
similarity index 81%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
index 71ef48b..41660ba 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
import org.junit.Test
-@RequiresDevice
-class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) {
+class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
similarity index 81%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
index 71ef48b..c41ffb7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/DragDividerToResizeGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
import org.junit.Test
-@RequiresDevice
-class DragDividerToResizeGesturalNavPortraitBenchmark : DragDividerToResize(Rotation.ROTATION_0) {
+class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index c78729c..afde55b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromAllAppsGesturalNavLandscapeBenchmark :
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index 30bce2f6..3765fc4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromAllAppsGesturalNavPortraitBenchmark :
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
similarity index 88%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
index b33ea7c..1e128fd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromNotificationGesturalNavLandscapeBenchmark :
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
index 07a86a5..7767872 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromNotificationGesturalNavPortraitBenchmark :
+class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
index 9a1d127..4ca4bd1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromShortcutGesturalNavLandscapeBenchmark :
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
index 266e268..2d9d258 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromShortcutGesturalNavPortraitBenchmark :
+class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
index 83fc30b..c9282ac 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromTaskbarGesturalNavLandscapeBenchmark :
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
index b2f1929..68c6d6c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenByDragFromTaskbarGesturalNavPortraitBenchmark :
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
index dae92dd..304529e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenFromOverviewGesturalNavLandscapeBenchmark :
+class EnterSplitScreenFromOverviewGesturalNavLandscape :
EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
index 732047b..53a6b44 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
import org.junit.Test
-@RequiresDevice
-class EnterSplitScreenFromOverviewGesturalNavPortraitBenchmark :
+class EnterSplitScreenFromOverviewGesturalNavPortrait :
EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
index 1de7efd..a467830 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
import org.junit.Test
-@RequiresDevice
-class SwitchAppByDoubleTapDividerGesturalNavLandscapeBenchmark :
+class SwitchAppByDoubleTapDividerGesturalNavLandscape :
SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
index 1a046aa..1524233 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
import org.junit.Test
-@RequiresDevice
-class SwitchAppByDoubleTapDividerGesturalNavPortraitBenchmark :
+class SwitchAppByDoubleTapDividerGesturalNavPortrait :
SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
index 6e88f0e..0389659 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromAnotherAppGesturalNavLandscapeBenchmark :
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
index d26a29c..7fadf18 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromAnotherAppGesturalNavPortraitBenchmark :
+class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
index 4a552b0..148cc52 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromHomeGesturalNavLandscapeBenchmark :
+class SwitchBackToSplitFromHomeGesturalNavLandscape :
SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
index b7376ea..0641fb0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromHomeGesturalNavPortraitBenchmark :
+class SwitchBackToSplitFromHomeGesturalNavPortrait :
SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index b2d05e4..741f871 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromRecentGesturalNavLandscapeBenchmark :
+class SwitchBackToSplitFromRecentGesturalNavLandscape :
SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
similarity index 84%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index 6de31b1..778e2d6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
import org.junit.Test
-@RequiresDevice
-class SwitchBackToSplitFromRecentGesturalNavPortraitBenchmark :
+class SwitchBackToSplitFromRecentGesturalNavPortrait :
SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
similarity index 80%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
index b074f2c..dcdca70 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -14,18 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
import org.junit.Test
-@RequiresDevice
-class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark :
- SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
similarity index 80%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
index b074f2c..3c69311 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/SwitchBetweenSplitPairsGesturalNavPortraitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -14,18 +14,15 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
import android.tools.common.Rotation
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
import org.junit.Test
-@RequiresDevice
-class SwitchBetweenSplitPairsGesturalNavPortraitBenchmark :
- SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
similarity index 82%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
index c402aa4..c6566f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -14,19 +14,17 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
-@RequiresDevice
@RunWith(BlockJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() {
+class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
similarity index 82%
copy from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
copy to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
index c402aa4..bb1a502 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/benchmark/UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -14,19 +14,17 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service.splitscreen.benchmark
+package com.android.wm.shell.flicker.service.splitscreen.platinum
import android.platform.test.annotations.PlatinumTest
import android.platform.test.annotations.Presubmit
-import androidx.test.filters.RequiresDevice
import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
-@RequiresDevice
@RunWith(BlockJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavLandscapeBenchmark : UnlockKeyguardToSplitScreen() {
+class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
@PlatinumTest(focusArea = "sysui")
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
index 677aeb07..74f441d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -25,6 +25,7 @@
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.wm.shell.flicker.service.Utils
import org.junit.After
+import org.junit.Assume
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
@@ -45,6 +46,8 @@
@Before
fun setup() {
+ Assume.assumeTrue(tapl.isTablet)
+
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
index f4f6878..8d93c0d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -25,6 +25,7 @@
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.wm.shell.flicker.service.Utils
import org.junit.After
+import org.junit.Assume
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
@@ -46,6 +47,8 @@
@Before
fun setup() {
+ Assume.assumeTrue(tapl.isTablet)
+
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index 322f711..ed08041 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -25,6 +25,7 @@
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.wm.shell.flicker.service.Utils
import org.junit.After
+import org.junit.Assume
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
@@ -45,6 +46,8 @@
@Before
fun setup() {
+ Assume.assumeTrue(tapl.isTablet)
+
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index d44d177..9f9d4bb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -47,6 +47,7 @@
fun setup() {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
+ tapl.workspace.switchToOverview().dismissAllTasks()
SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
index c6642f3..885ae38 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandlerTest.java
@@ -99,16 +99,17 @@
final int taskId = 1;
WindowContainerTransaction wct = new WindowContainerTransaction();
doReturn(mToken).when(mTransitions)
- .startTransition(Transitions.TRANSIT_ENTER_FREEFORM, wct,
+ .startTransition(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE, wct,
mEnterDesktopTaskTransitionHandler);
doReturn(taskId).when(mMoveToDesktopAnimator).getTaskId();
- mEnterDesktopTaskTransitionHandler.startMoveToFreeformAnimation(wct,
+ mEnterDesktopTaskTransitionHandler.startMoveToDesktop(wct,
mMoveToDesktopAnimator, null);
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
- TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_FREEFORM, change);
+ TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_START_MOVE_TO_DESKTOP_MODE,
+ change);
assertTrue(mEnterDesktopTaskTransitionHandler
@@ -120,17 +121,18 @@
@Test
public void testTransitEnterDesktopModeAnimation() throws Throwable {
- final int transitionType = Transitions.TRANSIT_ENTER_DESKTOP_MODE;
+ final int transitionType = Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE;
final int taskId = 1;
WindowContainerTransaction wct = new WindowContainerTransaction();
doReturn(mToken).when(mTransitions)
.startTransition(transitionType, wct, mEnterDesktopTaskTransitionHandler);
- mEnterDesktopTaskTransitionHandler.startTransition(transitionType, wct, null);
+ mEnterDesktopTaskTransitionHandler.finalizeMoveToDesktop(wct, null);
TransitionInfo.Change change =
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FREEFORM);
change.setEndAbsBounds(new Rect(0, 0, 1, 1));
- TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_ENTER_DESKTOP_MODE, change);
+ TransitionInfo info = createTransitionInfo(
+ Transitions.TRANSIT_FINALIZE_MOVE_TO_DESKTOP_MODE, change);
runOnUiThread(() -> {
try {
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index f71e728..b870023 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -88,6 +88,9 @@
mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename));
validateCache(identity, size);
mInitialized = true;
+ if (identity != nullptr && size > 0 && mIDHash.size()) {
+ set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size());
+ }
}
}
@@ -96,11 +99,6 @@
mFilename = filename;
}
-BlobCache* ShaderCache::getBlobCacheLocked() {
- LOG_ALWAYS_FATAL_IF(!mInitialized, "ShaderCache has not been initialized");
- return mBlobCache.get();
-}
-
sk_sp<SkData> ShaderCache::load(const SkData& key) {
ATRACE_NAME("ShaderCache::load");
size_t keySize = key.size();
@@ -115,8 +113,7 @@
if (!valueBuffer) {
return nullptr;
}
- BlobCache* bc = getBlobCacheLocked();
- size_t valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
+ size_t valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
int maxTries = 3;
while (valueSize > mObservedBlobValueSize && maxTries > 0) {
mObservedBlobValueSize = std::min(valueSize, maxValueSize);
@@ -126,7 +123,7 @@
return nullptr;
}
valueBuffer = newValueBuffer;
- valueSize = bc->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
+ valueSize = mBlobCache->get(key.data(), keySize, valueBuffer, mObservedBlobValueSize);
maxTries--;
}
if (!valueSize) {
@@ -143,16 +140,17 @@
return SkData::MakeFromMalloc(valueBuffer, valueSize);
}
-namespace {
-// Helper for BlobCache::set to trace the result.
-void set(BlobCache* cache, const void* key, size_t keySize, const void* value, size_t valueSize) {
- switch (cache->set(key, keySize, value, valueSize)) {
+void ShaderCache::set(const void* key, size_t keySize, const void* value, size_t valueSize) {
+ switch (mBlobCache->set(key, keySize, value, valueSize)) {
case BlobCache::InsertResult::kInserted:
// This is what we expect/hope. It means the cache is large enough.
return;
case BlobCache::InsertResult::kDidClean: {
ATRACE_FORMAT("ShaderCache: evicted an entry to fit {key: %lu value %lu}!", keySize,
valueSize);
+ if (mIDHash.size()) {
+ set(&sIDKey, sizeof(sIDKey), mIDHash.data(), mIDHash.size());
+ }
return;
}
case BlobCache::InsertResult::kNotEnoughSpace: {
@@ -172,15 +170,10 @@
}
}
}
-} // namespace
void ShaderCache::saveToDiskLocked() {
ATRACE_NAME("ShaderCache::saveToDiskLocked");
if (mInitialized && mBlobCache) {
- if (mIDHash.size()) {
- auto key = sIDKey;
- set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size());
- }
// The most straightforward way to make ownership shared
mMutex.unlock();
mMutex.lock_shared();
@@ -209,11 +202,10 @@
const void* value = data.data();
- BlobCache* bc = getBlobCacheLocked();
if (mInStoreVkPipelineInProgress) {
if (mOldPipelineCacheSize == -1) {
// Record the initial pipeline cache size stored in the file.
- mOldPipelineCacheSize = bc->get(key.data(), keySize, nullptr, 0);
+ mOldPipelineCacheSize = mBlobCache->get(key.data(), keySize, nullptr, 0);
}
if (mNewPipelineCacheSize != -1 && mNewPipelineCacheSize == valueSize) {
// There has not been change in pipeline cache size. Stop trying to save.
@@ -228,7 +220,7 @@
mNewPipelineCacheSize = -1;
mTryToStorePipelineCache = true;
}
- set(bc, key.data(), keySize, value, valueSize);
+ set(key.data(), keySize, value, valueSize);
if (!mSavePending && mDeferredSaveDelayMs > 0) {
mSavePending = true;
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index c63a65a..6ccb212 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -97,20 +97,18 @@
void operator=(const ShaderCache&) = delete;
/**
- * "getBlobCacheLocked" returns the BlobCache object being used to store the
- * key/value blob pairs. If the BlobCache object has not yet been created,
- * this will do so, loading the serialized cache contents from disk if
- * possible.
- */
- BlobCache* getBlobCacheLocked() REQUIRES(mMutex);
-
- /**
* "validateCache" updates the cache to match the given identity. If the
* cache currently has the wrong identity, all entries in the cache are cleared.
*/
bool validateCache(const void* identity, ssize_t size) REQUIRES(mMutex);
/**
+ * Helper for BlobCache::set to trace the result and ensure the identity hash
+ * does not get evicted.
+ */
+ void set(const void* key, size_t keySize, const void* value, size_t valueSize) REQUIRES(mMutex);
+
+ /**
* "saveToDiskLocked" attempts to save the current contents of the cache to
* disk. If the identity hash exists, we will insert the identity hash into
* the cache for next validation.
@@ -128,11 +126,9 @@
bool mInitialized GUARDED_BY(mMutex) = false;
/**
- * "mBlobCache" is the cache in which the key/value blob pairs are stored. It
- * is initially NULL, and will be initialized by getBlobCacheLocked the
- * first time it's needed.
- * The blob cache contains the Android build number. We treat version mismatches as an empty
- * cache (logic implemented in BlobCache::unflatten).
+ * "mBlobCache" is the cache in which the key/value blob pairs are stored.
+ * The blob cache contains the Android build number. We treat version mismatches
+ * as an empty cache (logic implemented in BlobCache::unflatten).
*/
std::unique_ptr<FileBlobCache> mBlobCache GUARDED_BY(mMutex);
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index eb45707..f340945 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -41,7 +41,7 @@
namespace uirenderer {
namespace renderthread {
-static std::array<std::string_view, 19> sEnableExtensions{
+static std::array<std::string_view, 20> sEnableExtensions{
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
@@ -61,6 +61,7 @@
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
+ VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME,
};
static bool shouldEnableExtension(const std::string_view& extension) {
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index f3f32eb..e227999 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -14,30 +14,32 @@
* limitations under the License.
*/
-#include "tests/common/LeakChecker.h"
-#include "tests/common/TestScene.h"
-
-#include "Properties.h"
-#include "hwui/Typeface.h"
-#include "HardwareBitmapUploader.h"
-#include "renderthread/RenderProxy.h"
-
+#include <android-base/parsebool.h>
#include <benchmark/benchmark.h>
+#include <errno.h>
+#include <fcntl.h>
#include <fnmatch.h>
#include <getopt.h>
#include <pthread.h>
#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
+
+#include <regex>
#include <string>
#include <unordered_map>
#include <vector>
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
+#include "HardwareBitmapUploader.h"
+#include "Properties.h"
+#include "hwui/Typeface.h"
+#include "renderthread/RenderProxy.h"
+#include "tests/common/LeakChecker.h"
+#include "tests/common/TestScene.h"
using namespace android;
+using namespace android::base;
using namespace android::uirenderer;
using namespace android::uirenderer::test;
@@ -69,6 +71,9 @@
--onscreen Render tests on device screen. By default tests
are offscreen rendered
--benchmark_format Set output format. Possible values are tabular, json, csv
+ --benchmark_list_tests Lists the tests that would run but does not run them
+ --benchmark_filter=<regex> Filters the test set to the given regex. If prefixed with `-` and test
+ that doesn't match the given regex is run
--renderer=TYPE Sets the render pipeline to use. May be skiagl or skiavk
--skip-leak-check Skips the memory leak check
--report-gpu-memory[=verbose] Dumps the GPU memory usage after each test run
@@ -140,6 +145,9 @@
if (!strcmp(format, "tabular")) {
gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
} else if (!strcmp(format, "json")) {
+ // We cannot print the leak check if outputing to JSON as that will break
+ // JSON parsers since it's not JSON-formatted
+ gRunLeakCheck = false;
gBenchmarkReporter.reset(new benchmark::JSONReporter());
} else {
fprintf(stderr, "Unknown format '%s'\n", format);
@@ -160,6 +168,24 @@
return true;
}
+static void addTestsThatMatchFilter(std::string spec) {
+ if (spec.empty() || spec == "all") {
+ spec = "."; // Regexp that matches all benchmarks
+ }
+ bool isNegativeFilter = false;
+ if (spec[0] == '-') {
+ spec.replace(0, 1, "");
+ isNegativeFilter = true;
+ }
+ std::regex re(spec, std::regex_constants::extended);
+ for (auto& iter : TestScene::testMap()) {
+ if ((isNegativeFilter && !std::regex_search(iter.first, re)) ||
+ (!isNegativeFilter && std::regex_search(iter.first, re))) {
+ gRunTests.push_back(iter.second);
+ }
+ }
+}
+
// For options that only exist in long-form. Anything in the
// 0-255 range is reserved for short options (which just use their ASCII value)
namespace LongOpts {
@@ -170,6 +196,8 @@
ReportFrametime,
CpuSet,
BenchmarkFormat,
+ BenchmarkListTests,
+ BenchmarkFilter,
Onscreen,
Offscreen,
Renderer,
@@ -179,14 +207,16 @@
}
static const struct option LONG_OPTIONS[] = {
- {"frames", required_argument, nullptr, 'f'},
- {"repeat", required_argument, nullptr, 'r'},
+ {"count", required_argument, nullptr, 'c'},
+ {"runs", required_argument, nullptr, 'r'},
{"help", no_argument, nullptr, 'h'},
{"list", no_argument, nullptr, LongOpts::List},
{"wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu},
{"report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime},
{"cpuset", required_argument, nullptr, LongOpts::CpuSet},
{"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat},
+ {"benchmark_list_tests", optional_argument, nullptr, LongOpts::BenchmarkListTests},
+ {"benchmark_filter", required_argument, nullptr, LongOpts::BenchmarkFilter},
{"onscreen", no_argument, nullptr, LongOpts::Onscreen},
{"offscreen", no_argument, nullptr, LongOpts::Offscreen},
{"renderer", required_argument, nullptr, LongOpts::Renderer},
@@ -197,8 +227,12 @@
static const char* SHORT_OPTIONS = "c:r:h";
void parseOptions(int argc, char* argv[]) {
+ benchmark::BenchmarkReporter::Context::executable_name = (argc > 0) ? argv[0] : "unknown";
+
int c;
bool error = false;
+ bool listTestsOnly = false;
+ bool testsAreFiltered = false;
opterr = 0;
while (true) {
@@ -272,6 +306,21 @@
}
break;
+ case LongOpts::BenchmarkListTests:
+ if (!optarg || ParseBool(optarg) == ParseBoolResult::kTrue) {
+ listTestsOnly = true;
+ }
+ break;
+
+ case LongOpts::BenchmarkFilter:
+ if (!optarg) {
+ error = true;
+ break;
+ }
+ addTestsThatMatchFilter(optarg);
+ testsAreFiltered = true;
+ break;
+
case LongOpts::Renderer:
if (!optarg) {
error = true;
@@ -346,11 +395,18 @@
}
}
} while (optind < argc);
- } else {
+ } else if (gRunTests.empty() && !testsAreFiltered) {
for (auto& iter : TestScene::testMap()) {
gRunTests.push_back(iter.second);
}
}
+
+ if (listTestsOnly) {
+ for (auto& iter : gRunTests) {
+ std::cout << iter.name << std::endl;
+ }
+ exit(EXIT_SUCCESS);
+ }
}
int main(int argc, char* argv[]) {
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 3def1d0..91fa873 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -450,27 +450,27 @@
public static final String FEATURE_REMOTE_GROUP_PLAYBACK =
"android.media.route.feature.REMOTE_GROUP_PLAYBACK";
- final String mId;
- final CharSequence mName;
- final List<String> mFeatures;
+ private final String mId;
+ private final CharSequence mName;
+ private final List<String> mFeatures;
@Type
- final int mType;
- final boolean mIsSystem;
- final Uri mIconUri;
- final CharSequence mDescription;
+ private final int mType;
+ private final boolean mIsSystem;
+ private final Uri mIconUri;
+ private final CharSequence mDescription;
@ConnectionState
- final int mConnectionState;
- final String mClientPackageName;
- final String mPackageName;
- final int mVolumeHandling;
- final int mVolumeMax;
- final int mVolume;
- final String mAddress;
- final Set<String> mDeduplicationIds;
- final Bundle mExtras;
- final String mProviderId;
- final boolean mIsVisibilityRestricted;
- final Set<String> mAllowedPackages;
+ private final int mConnectionState;
+ private final String mClientPackageName;
+ private final String mPackageName;
+ private final int mVolumeHandling;
+ private final int mVolumeMax;
+ private final int mVolume;
+ private final String mAddress;
+ private final Set<String> mDeduplicationIds;
+ private final Bundle mExtras;
+ private final String mProviderId;
+ private final boolean mIsVisibilityRestricted;
+ private final Set<String> mAllowedPackages;
MediaRoute2Info(@NonNull Builder builder) {
mId = builder.mId;
@@ -949,28 +949,28 @@
* Builder for {@link MediaRoute2Info media route info}.
*/
public static final class Builder {
- final String mId;
- final CharSequence mName;
- final List<String> mFeatures;
+ private final String mId;
+ private final CharSequence mName;
+ private final List<String> mFeatures;
@Type
- int mType = TYPE_UNKNOWN;
- boolean mIsSystem;
- Uri mIconUri;
- CharSequence mDescription;
+ private int mType = TYPE_UNKNOWN;
+ private boolean mIsSystem;
+ private Uri mIconUri;
+ private CharSequence mDescription;
@ConnectionState
- int mConnectionState;
- String mClientPackageName;
- String mPackageName;
- int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
- int mVolumeMax;
- int mVolume;
- String mAddress;
- Set<String> mDeduplicationIds;
- Bundle mExtras;
- String mProviderId;
- boolean mIsVisibilityRestricted;
- Set<String> mAllowedPackages;
+ private int mConnectionState;
+ private String mClientPackageName;
+ private String mPackageName;
+ private int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
+ private int mVolumeMax;
+ private int mVolume;
+ private String mAddress;
+ private Set<String> mDeduplicationIds;
+ private Bundle mExtras;
+ private String mProviderId;
+ private boolean mIsVisibilityRestricted;
+ private Set<String> mAllowedPackages;
/**
* Constructor for builder to create {@link MediaRoute2Info}.
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
index 9b33704..eccf604 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiver.java
@@ -191,6 +191,8 @@
&& isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED)
&& isPendingIntentValid(intent,
SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION)
+ && isPendingIntentValid(intent,
+ SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED)
&& isPendingIntentValid(intent, SlicePurchaseController.EXTRA_INTENT_SUCCESS)
&& isPendingIntentValid(intent,
SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
@@ -276,6 +278,8 @@
case SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
case SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION:
return "not default data subscription";
+ case SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED:
+ return "notifications disabled";
case SlicePurchaseController.EXTRA_INTENT_SUCCESS: return "success";
case SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN:
return "notification shown";
@@ -321,26 +325,45 @@
}
private void onDisplayPerformanceBoostNotification(@NonNull Context context,
- @NonNull Intent intent, boolean repeat) {
- if (!repeat && !isIntentValid(intent)) {
+ @NonNull Intent intent, boolean localeChanged) {
+ if (!localeChanged && !isIntentValid(intent)) {
sendSlicePurchaseAppResponse(intent,
SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
return;
}
Resources res = getResources(context);
- NotificationChannel channel = new NotificationChannel(
- PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID,
- res.getString(R.string.performance_boost_notification_channel),
- NotificationManager.IMPORTANCE_DEFAULT);
- // CarrierDefaultApp notifications are unblockable by default. Make this channel blockable
- // to allow users to disable notifications posted to this channel without affecting other
- // notifications in this application.
- channel.setBlockable(true);
- context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+ NotificationManager notificationManager =
+ context.getSystemService(NotificationManager.class);
+ NotificationChannel channel = notificationManager.getNotificationChannel(
+ PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID);
+ if (channel == null) {
+ channel = new NotificationChannel(
+ PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID,
+ res.getString(R.string.performance_boost_notification_channel),
+ NotificationManager.IMPORTANCE_DEFAULT);
+ // CarrierDefaultApp notifications are unblockable by default.
+ // Make this channel blockable to allow users to disable notifications posted to this
+ // channel without affecting other notifications in this application.
+ channel.setBlockable(true);
+ context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
+ } else if (localeChanged) {
+ // If the channel already exists but the locale has changed, update the channel name.
+ channel.setName(res.getString(R.string.performance_boost_notification_channel));
+ }
+
+ boolean channelNotificationsDisabled =
+ channel.getImportance() == NotificationManager.IMPORTANCE_NONE;
+ if (channelNotificationsDisabled || !notificationManager.areNotificationsEnabled()) {
+ // If notifications are disabled for the app or channel, fail the purchase request.
+ logd("Purchase request failed because notifications are disabled for the "
+ + (channelNotificationsDisabled ? "channel." : "application."));
+ sendSlicePurchaseAppResponse(intent,
+ SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED);
+ return;
+ }
String carrier = intent.getStringExtra(SlicePurchaseController.EXTRA_CARRIER);
-
Notification notification =
new Notification.Builder(context, PERFORMANCE_BOOST_NOTIFICATION_CHANNEL_ID)
.setContentTitle(res.getString(
@@ -369,11 +392,12 @@
int capability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
- logd((repeat ? "Update" : "Display") + " the performance boost notification for capability "
+ logd((localeChanged ? "Update" : "Display")
+ + " the performance boost notification for capability "
+ TelephonyManager.convertPremiumCapabilityToString(capability));
context.getSystemService(NotificationManager.class).notifyAsUser(
PERFORMANCE_BOOST_NOTIFICATION_TAG, capability, notification, UserHandle.ALL);
- if (!repeat) {
+ if (!localeChanged) {
sIntents.put(capability, intent);
sendSlicePurchaseAppResponse(intent,
SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
index 61847b5..3c8ef6e 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/SlicePurchaseBroadcastReceiverTest.java
@@ -32,6 +32,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -72,6 +73,7 @@
@Mock PendingIntent mContentIntent1;
@Mock PendingIntent mContentIntent2;
@Mock PendingIntent mNotificationShownIntent;
+ @Mock PendingIntent mNotificationsDisabledIntent;
@Mock Context mContext;
@Mock Resources mResources;
@Mock Configuration mConfiguration;
@@ -90,6 +92,7 @@
doReturn("").when(mResources).getString(anyInt());
doReturn(mNotificationManager).when(mContext)
.getSystemService(eq(NotificationManager.class));
+ doReturn(true).when(mNotificationManager).areNotificationsEnabled();
doReturn(mApplicationInfo).when(mContext).getApplicationInfo();
doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(mSpiedResources).when(mContext).getResources();
@@ -221,12 +224,10 @@
doReturn(true).when(mPendingIntent).isBroadcast();
doReturn(mPendingIntent).when(mIntent).getParcelableExtra(
anyString(), eq(PendingIntent.class));
- doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(mNotificationShownIntent)
- .getCreatorPackage();
- doReturn(true).when(mNotificationShownIntent).isBroadcast();
- doReturn(mNotificationShownIntent).when(mIntent).getParcelableExtra(
- eq(SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN),
- eq(PendingIntent.class));
+ createValidPendingIntent(mNotificationShownIntent,
+ SlicePurchaseController.EXTRA_INTENT_NOTIFICATION_SHOWN);
+ createValidPendingIntent(mNotificationsDisabledIntent,
+ SlicePurchaseController.EXTRA_INTENT_NOTIFICATIONS_DISABLED);
// spy notification intents to prevent PendingIntent issues
doReturn(mContentIntent1).when(mSlicePurchaseBroadcastReceiver).createContentIntent(
@@ -253,6 +254,12 @@
mSlicePurchaseBroadcastReceiver.onReceive(mContext, mIntent);
}
+ private void createValidPendingIntent(@NonNull PendingIntent intent, @NonNull String extra) {
+ doReturn(TelephonyManager.PHONE_PROCESS_NAME).when(intent).getCreatorPackage();
+ doReturn(true).when(intent).isBroadcast();
+ doReturn(intent).when(mIntent).getParcelableExtra(eq(extra), eq(PendingIntent.class));
+ }
+
@Test
public void testNotificationCanceled() {
// send ACTION_NOTIFICATION_CANCELED
@@ -335,4 +342,22 @@
clearInvocations(mConfiguration);
return captor.getValue();
}
+
+ @Test
+ public void testNotificationsDisabled() throws Exception {
+ doReturn(false).when(mNotificationManager).areNotificationsEnabled();
+
+ displayPerformanceBoostNotification();
+
+ // verify notification was not shown
+ verify(mNotificationManager, never()).notifyAsUser(
+ eq(SlicePurchaseBroadcastReceiver.PERFORMANCE_BOOST_NOTIFICATION_TAG),
+ eq(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY),
+ any(),
+ eq(UserHandle.ALL));
+ verify(mNotificationShownIntent, never()).send();
+
+ // verify SlicePurchaseController was notified that notifications are disabled
+ verify(mNotificationsDisabledIntent).send();
+ }
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
index 3505cfb..74f04e0 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
@@ -33,8 +33,6 @@
import androidx.annotation.Nullable;
-import java.io.File;
-
/**
* Installation failed: Return status code to the caller or display failure UI to user
*/
@@ -101,14 +99,8 @@
// Set header icon and title
PackageUtil.AppSnippet as;
PackageManager pm = getPackageManager();
-
- if ("package".equals(packageURI.getScheme())) {
- as = new PackageUtil.AppSnippet(pm.getApplicationLabel(appInfo),
- pm.getApplicationIcon(appInfo));
- } else {
- final File sourceFile = new File(packageURI.getPath());
- as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
- }
+ as = intent.getParcelableExtra(PackageInstallerActivity.EXTRA_APP_SNIPPET,
+ PackageUtil.AppSnippet.class);
// Store label for dialog
mLabel = as.label;
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 7bea339..1088ace 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -16,6 +16,7 @@
package com.android.packageinstaller;
+import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_APP_SNIPPET;
import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
import android.app.PendingIntent;
@@ -86,7 +87,8 @@
// ContentResolver.SCHEME_FILE
// STAGED_SESSION_ID extra contains an ID of a previously staged install session.
final File sourceFile = new File(mPackageURI.getPath());
- PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
+ PackageUtil.AppSnippet as = getIntent()
+ .getParcelableExtra(EXTRA_APP_SNIPPET, PackageUtil.AppSnippet.class);
mAlert.setIcon(as.icon);
mAlert.setTitle(as.label);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
index ff991d2..fbc9525 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
@@ -22,7 +22,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
@@ -30,7 +29,6 @@
import androidx.annotation.Nullable;
-import java.io.File;
import java.util.List;
/**
@@ -65,18 +63,8 @@
ApplicationInfo appInfo =
intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
mAppPackageName = appInfo.packageName;
- Uri packageURI = intent.getData();
-
- // Set header icon and title
- PackageManager pm = getPackageManager();
-
- if ("package".equals(packageURI.getScheme())) {
- mAppSnippet = new PackageUtil.AppSnippet(pm.getApplicationLabel(appInfo),
- pm.getApplicationIcon(appInfo));
- } else {
- File sourceFile = new File(packageURI.getPath());
- mAppSnippet = PackageUtil.getAppSnippet(this, appInfo, sourceFile);
- }
+ mAppSnippet = intent.getParcelableExtra(PackageInstallerActivity.EXTRA_APP_SNIPPET,
+ PackageUtil.AppSnippet.class);
mLaunchIntent = getPackageManager().getLaunchIntentForPackage(mAppPackageName);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index bc7fa02..b66679a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -84,6 +84,7 @@
static final String EXTRA_CALLING_ATTRIBUTION_TAG = "EXTRA_CALLING_ATTRIBUTION_TAG";
static final String EXTRA_ORIGINAL_SOURCE_INFO = "EXTRA_ORIGINAL_SOURCE_INFO";
static final String EXTRA_STAGED_SESSION_ID = "EXTRA_STAGED_SESSION_ID";
+ static final String EXTRA_APP_SNIPPET = "EXTRA_APP_SNIPPET";
private static final String ALLOW_UNKNOWN_SOURCES_KEY =
PackageInstallerActivity.class.getName() + "ALLOW_UNKNOWN_SOURCES_KEY";
@@ -708,6 +709,9 @@
if (stagedSessionId > 0) {
newIntent.putExtra(EXTRA_STAGED_SESSION_ID, stagedSessionId);
}
+ if (mAppSnippet != null) {
+ newIntent.putExtra(EXTRA_APP_SNIPPET, mAppSnippet);
+ }
newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
if (mLocalLOGV) Log.i(TAG, "downloaded app uri=" + mPackageURI);
startActivity(newIntent);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index c2d4f18..5880a29 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -28,8 +28,13 @@
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;
@@ -117,7 +122,7 @@
icon);
}
- static final class AppSnippet {
+ static final class AppSnippet implements Parcelable {
@NonNull public CharSequence label;
@Nullable public Drawable icon;
public AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon) {
@@ -125,10 +130,52 @@
this.icon = icon;
}
+ private AppSnippet(Parcel in) {
+ label = in.readString();
+ Bitmap bmp = in.readParcelable(getClass().getClassLoader(), Bitmap.class);
+ icon = new BitmapDrawable(Resources.getSystem(), bmp);
+ }
+
@Override
public String toString() {
return "AppSnippet[" + label + (icon != null ? "(has" : "(no ") + " icon)]";
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(label.toString());
+ Bitmap bmp = getBitmapFromDrawable(icon);
+ dest.writeParcelable(bmp, 0);
+ }
+
+ private Bitmap getBitmapFromDrawable(Drawable drawable) {
+ // Create an empty bitmap with the dimensions of our drawable
+ final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(),
+ Bitmap.Config.ARGB_8888);
+ // Associate it with a canvas. This canvas will draw the icon on the bitmap
+ final Canvas canvas = new Canvas(bmp);
+ // Draw the drawable in the canvas. The canvas will ultimately paint the drawable in the
+ // bitmap held within
+ drawable.draw(canvas);
+
+ return bmp;
+ }
+
+ public static final Parcelable.Creator<AppSnippet> CREATOR = new Parcelable.Creator<>() {
+ public AppSnippet createFromParcel(Parcel in) {
+ return new AppSnippet(in);
+ }
+
+ public AppSnippet[] newArray(int size) {
+ return new AppSnippet[size];
+ }
+ };
}
/**
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index a0ff216..ddb92b1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -27,14 +27,17 @@
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.material3.Divider
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import com.android.settingslib.development.DevelopmentSettingsEnabler
import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.ui.SettingsBody
@@ -80,11 +83,22 @@
}
@Composable
- fun FooterAppVersion() {
+ fun FooterAppVersion(showPackageName: Boolean = rememberIsDevelopmentSettingsEnabled()) {
if (packageInfo.versionName == null) return
- Divider()
- Box(modifier = Modifier.padding(SettingsDimension.itemPadding)) {
+ HorizontalDivider()
+ Column(modifier = Modifier.padding(SettingsDimension.itemPadding)) {
SettingsBody(stringResource(R.string.version_text, packageInfo.versionNameBidiWrapped))
+ if (showPackageName) {
+ SettingsBody(packageInfo.packageName)
+ }
+ }
+ }
+
+ @Composable
+ private fun rememberIsDevelopmentSettingsEnabled(): Boolean {
+ val context = LocalContext.current
+ return remember {
+ DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
index e846687..155905b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.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.
@@ -19,26 +19,26 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
import com.android.settingslib.spaprivileged.model.app.AppRecord
@Composable
fun <T : AppRecord> AppListItemModel<T>.AppListSwitchItem(
- onClick: () -> Unit,
checked: State<Boolean?>,
changeable: State<Boolean>,
onCheckedChange: ((newChecked: Boolean) -> Unit)?,
) {
- TwoTargetSwitchPreference(
+ SwitchPreference(
model = object : SwitchPreferenceModel {
override val title = label
override val summary = this@AppListSwitchItem.summary
+ override val icon = @Composable {
+ AppIcon(record.app, SettingsDimension.appIconItemSize)
+ }
override val checked = checked
override val changeable = changeable
override val onCheckedChange = onCheckedChange
},
- icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
- onClick = onClick,
)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
new file mode 100644
index 0000000..99d3840
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+
+@Composable
+fun <T : AppRecord> AppListItemModel<T>.AppListTwoTargetSwitchItem(
+ onClick: () -> Unit,
+ checked: State<Boolean?>,
+ changeable: State<Boolean>,
+ onCheckedChange: ((newChecked: Boolean) -> Unit)?,
+) {
+ TwoTargetSwitchPreference(
+ model = object : SwitchPreferenceModel {
+ override val title = label
+ override val summary = this@AppListTwoTargetSwitchItem.summary
+ override val checked = checked
+ override val changeable = changeable
+ override val onCheckedChange = onCheckedChange
+ },
+ icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
+ onClick = onClick,
+ )
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
index bb56c10..6831e56 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
@@ -93,6 +93,7 @@
val packageInfo = PackageInfo().apply {
applicationInfo = APP
versionName = VERSION_NAME
+ packageName = PACKAGE_NAME
}
val appInfoProvider = AppInfoProvider(packageInfo)
@@ -105,9 +106,45 @@
composeTestRule.onNodeWithText("version $VERSION_NAME").assertIsDisplayed()
}
+ @Test
+ fun footerAppVersion_developmentEnabled_packageNameIsDisplayed() {
+ val packageInfo = PackageInfo().apply {
+ applicationInfo = APP
+ versionName = VERSION_NAME
+ packageName = PACKAGE_NAME
+ }
+ val appInfoProvider = AppInfoProvider(packageInfo)
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ appInfoProvider.FooterAppVersion(true)
+ }
+ }
+ composeTestRule.onNodeWithText(PACKAGE_NAME).assertIsDisplayed()
+ }
+
+
+ @Test
+ fun footerAppVersion_developmentDisabled_packageNameDoesNotExist() {
+ val packageInfo = PackageInfo().apply {
+ applicationInfo = APP
+ versionName = VERSION_NAME
+ packageName = PACKAGE_NAME
+ }
+ val appInfoProvider = AppInfoProvider(packageInfo)
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ appInfoProvider.FooterAppVersion(false)
+ }
+ }
+ composeTestRule.onNodeWithText(PACKAGE_NAME).assertDoesNotExist()
+ }
+
private companion object {
const val LABEL = "Label"
const val VERSION_NAME = "VersionName"
+ const val PACKAGE_NAME = "package.name"
val APP = object : ApplicationInfo() {
override fun loadLabel(pm: PackageManager) = LABEL
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
index abdcd3b..2fd1b10 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.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.
@@ -43,7 +43,6 @@
fun appLabel_displayed() {
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(null),
changeable = stateOf(false),
onCheckedChange = {},
@@ -57,7 +56,6 @@
fun summary_displayed() {
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(null),
changeable = stateOf(false),
onCheckedChange = {},
@@ -68,27 +66,9 @@
}
@Test
- fun title_onClick() {
- var titleClicked = false
- composeTestRule.setContent {
- ITEM_MODEL.AppListSwitchItem(
- onClick = { titleClicked = true },
- checked = stateOf(false),
- changeable = stateOf(false),
- onCheckedChange = {},
- )
- }
-
- composeTestRule.onNodeWithText(LABEL).performClick()
-
- assertThat(titleClicked).isTrue()
- }
-
- @Test
fun switch_checkIsNull() {
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(null),
changeable = stateOf(false),
onCheckedChange = {},
@@ -102,7 +82,6 @@
fun switch_checked() {
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(true),
changeable = stateOf(false),
onCheckedChange = {},
@@ -116,7 +95,6 @@
fun switch_notChecked() {
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(false),
changeable = stateOf(false),
onCheckedChange = {},
@@ -130,7 +108,6 @@
fun switch_changeable() {
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(false),
changeable = stateOf(true),
onCheckedChange = {},
@@ -144,7 +121,6 @@
fun switch_notChangeable() {
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(false),
changeable = stateOf(false),
onCheckedChange = {},
@@ -159,7 +135,6 @@
var switchClicked = false
composeTestRule.setContent {
ITEM_MODEL.AppListSwitchItem(
- onClick = {},
checked = stateOf(false),
changeable = stateOf(true),
onCheckedChange = { switchClicked = true },
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
new file mode 100644
index 0000000..6e7fc8e
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItemTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.isToggleable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListTwoTargetSwitchItemTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun appLabel_displayed() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(null),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+ }
+
+ @Test
+ fun summary_displayed() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(null),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed()
+ }
+
+ @Test
+ fun title_onClick() {
+ var titleClicked = false
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = { titleClicked = true },
+ checked = stateOf(false),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+
+ assertThat(titleClicked).isTrue()
+ }
+
+ @Test
+ fun switch_checkIsNull() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(null),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertDoesNotExist()
+ }
+
+ @Test
+ fun switch_checked() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(true),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsOn()
+ }
+
+ @Test
+ fun switch_notChecked() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsOff()
+ }
+
+ @Test
+ fun switch_changeable() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(true),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsEnabled()
+ }
+
+ @Test
+ fun switch_notChangeable() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsNotEnabled()
+ }
+
+ @Test
+ fun switch_onClick() {
+ var switchClicked = false
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListTwoTargetSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(true),
+ onCheckedChange = { switchClicked = true },
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).performClick()
+
+ assertThat(switchClicked).isTrue()
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "package.name"
+ const val LABEL = "Label"
+ const val SUMMARY = "Summary"
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+ val ITEM_MODEL = AppListItemModel(
+ record = object : AppRecord {
+ override val app = APP
+ },
+ label = LABEL,
+ summary = stateOf(SUMMARY),
+ )
+ }
+}
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index 612a928..1a938d6 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -179,7 +179,8 @@
* Check whether tile has order.
*/
public boolean hasOrder() {
- return mMetaData.containsKey(META_DATA_KEY_ORDER)
+ return mMetaData != null
+ && mMetaData.containsKey(META_DATA_KEY_ORDER)
&& mMetaData.get(META_DATA_KEY_ORDER) instanceof Integer;
}
@@ -204,7 +205,7 @@
CharSequence title = null;
ensureMetadataNotStale(context);
final PackageManager packageManager = context.getPackageManager();
- if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
+ if (mMetaData != null && mMetaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
if (mMetaData.containsKey(META_DATA_PREFERENCE_TITLE_URI)) {
// If has as uri to provide dynamic title, skip loading here. UI will later load
// at tile binding time.
diff --git a/packages/SettingsLib/res/values-eu/arrays.xml b/packages/SettingsLib/res/values-eu/arrays.xml
index 3b980c5..17d5e1d 100644
--- a/packages/SettingsLib/res/values-eu/arrays.xml
+++ b/packages/SettingsLib/res/values-eu/arrays.xml
@@ -208,7 +208,7 @@
<item msgid="2675263395797191850">"Animazioa desaktibatuta"</item>
<item msgid="5790132543372767872">"Animazio-eskala: 0,5x"</item>
<item msgid="2529692189302148746">"Animazio-eskala: 1×"</item>
- <item msgid="8072785072237082286">"Animazio-eskala: 1,5x"</item>
+ <item msgid="8072785072237082286">"Animazio-eskala: 1,5×"</item>
<item msgid="3531560925718232560">"Animazio-eskala 2x"</item>
<item msgid="4542853094898215187">"Animazio-eskala: 5x"</item>
<item msgid="5643881346223901195">"Animazio-eskala: 10x"</item>
@@ -217,7 +217,7 @@
<item msgid="3376676813923486384">"Animazioa desaktibatuta"</item>
<item msgid="753422683600269114">"Animazio-eskala: 0,5x"</item>
<item msgid="3695427132155563489">"Animazio-eskala: 1×"</item>
- <item msgid="9032615844198098981">"Animazio-eskala: 1,5x"</item>
+ <item msgid="9032615844198098981">"Animazio-eskala: 1,5×"</item>
<item msgid="8473868962499332073">"Animazio-eskala: 2x"</item>
<item msgid="4403482320438668316">"Animazio-eskala: 5x"</item>
<item msgid="169579387974966641">"Animazio-eskala: 10x"</item>
@@ -226,7 +226,7 @@
<item msgid="6416998593844817378">"Animazioa desaktibatuta"</item>
<item msgid="875345630014338616">"Animazio-eskala: 0,5x"</item>
<item msgid="2753729231187104962">"Animazio-eskala: 1×"</item>
- <item msgid="1368370459723665338">"Animazio-eskala: 1,5x"</item>
+ <item msgid="1368370459723665338">"Animazio-eskala: 1,5×"</item>
<item msgid="5768005350534383389">"Animazio-eskala: 2x"</item>
<item msgid="3728265127284005444">"Animazio-eskala: 5x"</item>
<item msgid="2464080977843960236">"Animazio-eskala: 10x"</item>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index f522fd1..2118d2c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -356,11 +356,7 @@
connectDevice();
}
- public HearingAidInfo getHearingAidInfo() {
- return mHearingAidInfo;
- }
-
- public void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
+ void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
mHearingAidInfo = hearingAidInfo;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 0c1b793..441d3a5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -20,6 +20,7 @@
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.ScanFilter;
import android.content.Context;
import android.util.Log;
@@ -114,10 +115,21 @@
/**
* Create and return a new {@link CachedBluetoothDevice}. This assumes
* that {@link #findDevice} has already been called and returned null.
- * @param device the address of the new Bluetooth device
+ * @param device the new Bluetooth device
* @return the newly created CachedBluetoothDevice object
*/
public CachedBluetoothDevice addDevice(BluetoothDevice device) {
+ return addDevice(device, /*leScanFilters=*/null);
+ }
+
+ /**
+ * Create and return a new {@link CachedBluetoothDevice}. This assumes
+ * that {@link #findDevice} has already been called and returned null.
+ * @param device the new Bluetooth device
+ * @param leScanFilters the BLE scan filters which the device matched
+ * @return the newly created CachedBluetoothDevice object
+ */
+ public CachedBluetoothDevice addDevice(BluetoothDevice device, List<ScanFilter> leScanFilters) {
CachedBluetoothDevice newDevice;
final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
synchronized (this) {
@@ -125,7 +137,7 @@
if (newDevice == null) {
newDevice = new CachedBluetoothDevice(mContext, profileManager, device);
mCsipDeviceManager.initCsipDeviceIfNeeded(newDevice);
- mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice);
+ mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(newDevice, leScanFilters);
if (!mCsipDeviceManager.setMemberDeviceIfNeeded(newDevice)
&& !mHearingAidDeviceManager.setSubDeviceIfNeeded(newDevice)) {
mCachedDevices.add(newDevice);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index e5e5782..efba953 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -18,10 +18,13 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.le.ScanFilter;
import android.content.ContentResolver;
import android.content.Context;
import android.media.AudioDeviceAttributes;
import android.media.audiopolicy.AudioProductStrategy;
+import android.os.ParcelUuid;
import android.provider.Settings;
import android.util.Log;
@@ -59,7 +62,8 @@
mRoutingHelper = routingHelper;
}
- void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) {
+ void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice,
+ List<ScanFilter> leScanFilters) {
long hiSyncId = getHiSyncId(newDevice.getDevice());
if (isValidHiSyncId(hiSyncId)) {
// Once hiSyncId is valid, assign hearing aid info
@@ -68,6 +72,21 @@
.setAshaDeviceMode(getDeviceMode(newDevice.getDevice()))
.setHiSyncId(hiSyncId);
newDevice.setHearingAidInfo(infoBuilder.build());
+ } else if (leScanFilters != null && !newDevice.isHearingAidDevice()) {
+ // If the device is added with hearing aid scan filter during pairing, set an empty
+ // hearing aid info to indicate it's a hearing aid device. The info will be updated
+ // when corresponding profiles connected.
+ for (ScanFilter leScanFilter: leScanFilters) {
+ final ParcelUuid serviceUuid = leScanFilter.getServiceUuid();
+ final ParcelUuid serviceDataUuid = leScanFilter.getServiceDataUuid();
+ if (BluetoothUuid.HEARING_AID.equals(serviceUuid)
+ || BluetoothUuid.HAS.equals(serviceUuid)
+ || BluetoothUuid.HEARING_AID.equals(serviceDataUuid)
+ || BluetoothUuid.HAS.equals(serviceDataUuid)) {
+ newDevice.setHearingAidInfo(new HearingAidInfo.Builder().build());
+ break;
+ }
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 7a48838..bff51e3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -496,16 +496,6 @@
return info.getName();
}
- boolean shouldDisableMediaOutput(String packageName) {
- if (TextUtils.isEmpty(packageName)) {
- Log.w(TAG, "shouldDisableMediaOutput() package name is null or empty!");
- return true;
- }
-
- // Disable when there is no transferable route
- return getTransferableRoutes(packageName).isEmpty();
- }
-
@TargetApi(Build.VERSION_CODES.R)
boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) {
return sessionInfo.isSystemSession() // System sessions are not remote
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 8479df1..fd5ca84 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -453,13 +453,6 @@
}
/**
- * Returns {@code true} if needed to disable media output, otherwise returns {@code false}.
- */
- public boolean shouldDisableMediaOutput(String packageName) {
- return mInfoMediaManager.shouldDisableMediaOutput(packageName);
- }
-
- /**
* Returns {@code true} if needed to enable volume seekbar, otherwise returns {@code false}.
*/
public boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 0d5de88..ea10944 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -33,6 +33,8 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.le.ScanFilter;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
@@ -147,7 +149,7 @@
HearingAidProfile.DeviceSide.SIDE_RIGHT);
assertThat(mCachedDevice1.getHiSyncId()).isNotEqualTo(HISYNCID1);
- mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1);
+ mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
@@ -164,12 +166,43 @@
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
BluetoothHearingAid.HI_SYNC_ID_INVALID);
- mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1);
+ mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
}
/**
+ * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and hearing aid scan filter, set an
+ * empty hearing aid info on the device.
+ */
+ @Test
+ public void initHearingAidDeviceIfNeeded_hearingAidScanFilter_setHearingAidInfo() {
+ when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
+ BluetoothHearingAid.HI_SYNC_ID_INVALID);
+ final ScanFilter scanFilter = new ScanFilter.Builder()
+ .setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}).build();
+
+ mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
+
+ assertThat(mCachedDevice1.isHearingAidDevice()).isTrue();
+ }
+
+ /**
+ * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and random scan filter, not to set
+ * hearing aid info on the device.
+ */
+ @Test
+ public void initHearingAidDeviceIfNeeded_randomScanFilter_setHearingAidInfo() {
+ when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
+ BluetoothHearingAid.HI_SYNC_ID_INVALID);
+ final ScanFilter scanFilter = new ScanFilter.Builder().build();
+
+ mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
+
+ assertThat(mCachedDevice1.isHearingAidDevice()).isFalse();
+ }
+
+ /**
* Test setSubDeviceIfNeeded, a device with same HiSyncId will be set as sub device
*/
@Test
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 45b5de4..866ef9d 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
@@ -1083,51 +1083,4 @@
assertThat(device.getState()).isEqualTo(STATE_SELECTED);
assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(device);
}
-
- @Test
- public void shouldDisableMediaOutput_infosIsEmpty_returnsTrue() {
- mShadowRouter2Manager.setTransferableRoutes(new ArrayList<>());
-
- assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isTrue();
- }
-
- @Test
- public void shouldDisableMediaOutput_infosSizeEqual1_returnsFalse() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- final List<MediaRoute2Info> infos = new ArrayList<>();
- infos.add(info);
- mShadowRouter2Manager.setTransferableRoutes(infos);
-
- when(info.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
-
- assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse();
- }
-
- @Test
- public void shouldDisableMediaOutput_infosSizeEqual1AndNotCastDevice_returnsFalse() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- final List<MediaRoute2Info> infos = new ArrayList<>();
- infos.add(info);
- mShadowRouter2Manager.setTransferableRoutes(infos);
-
- when(info.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
-
- assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse();
- }
-
-
- @Test
- public void shouldDisableMediaOutput_infosSizeOverThan1_returnsFalse() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- final MediaRoute2Info info2 = mock(MediaRoute2Info.class);
- final List<MediaRoute2Info> infos = new ArrayList<>();
- infos.add(info);
- infos.add(info2);
- mShadowRouter2Manager.setTransferableRoutes(infos);
-
- when(info.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
- when(info2.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
-
- assertThat(mInfoMediaManager.shouldDisableMediaOutput("test")).isFalse();
- }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 6a5535d..6b0a906 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -61,6 +61,7 @@
Settings.System.TTY_MODE,
Settings.System.MASTER_MONO,
Settings.System.MASTER_BALANCE,
+ Settings.System.STAY_AWAKE_ON_FOLD,
Settings.System.SOUND_EFFECTS_ENABLED,
Settings.System.HAPTIC_FEEDBACK_ENABLED,
Settings.System.POWER_SOUNDS_ENABLED, // moved to global
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 061bdeb..0dd8569 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -218,6 +218,7 @@
VALIDATORS.put(System.WIFI_STATIC_DNS1, LENIENT_IP_ADDRESS_VALIDATOR);
VALIDATORS.put(System.WIFI_STATIC_DNS2, LENIENT_IP_ADDRESS_VALIDATOR);
VALIDATORS.put(System.SHOW_BATTERY_PERCENT, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.STAY_AWAKE_ON_FOLD, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.WEAR_ACCESSIBILITY_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, BOOLEAN_VALIDATOR);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 7d8b066..368115b 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -320,6 +320,7 @@
<uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
+ <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
<uses-permission android:name="android.permission.SUSPEND_APPS" />
<uses-permission android:name="android.permission.OBSERVE_APP_USAGE" />
<uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" />
diff --git a/packages/Shell/res/values-gl/strings.xml b/packages/Shell/res/values-gl/strings.xml
index 912dc85..9d4f7de 100644
--- a/packages/Shell/res/values-gl/strings.xml
+++ b/packages/Shell/res/values-gl/strings.xml
@@ -20,7 +20,7 @@
<string name="bugreport_notification_channel" msgid="2574150205913861141">"Informes de erros"</string>
<string name="bugreport_in_progress_title" msgid="4311705936714972757">"Estase xerando o informe de erros <xliff:g id="ID">#%d</xliff:g>"</string>
<string name="bugreport_finished_title" msgid="4429132808670114081">"Rexistrouse o informe de erros <xliff:g id="ID">#%d</xliff:g>"</string>
- <string name="bugreport_updating_title" msgid="4423539949559634214">"Engadindo detalles ao informe de erro"</string>
+ <string name="bugreport_updating_title" msgid="4423539949559634214">"Engadindo detalles ao informe de erros"</string>
<string name="bugreport_updating_wait" msgid="3322151947853929470">"Agarda..."</string>
<string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"O informe de erros aparecerá no teléfono en breve"</string>
<string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Selecciona para compartir o teu informe de erros"</string>
@@ -32,7 +32,7 @@
<string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Non mostrar outra vez"</string>
<string name="bugreport_storage_title" msgid="5332488144740527109">"Informes de erros"</string>
<string name="bugreport_unreadable_text" msgid="586517851044535486">"Non se puido ler o ficheiro de informe de erros"</string>
- <string name="bugreport_add_details_to_zip_failed" msgid="1302931926486712371">"Non se puideron engadir os detalles do informe de erro ao ficheiro zip"</string>
+ <string name="bugreport_add_details_to_zip_failed" msgid="1302931926486712371">"Non se puideron engadir os detalles do informe de erros ao ficheiro zip"</string>
<string name="bugreport_unnamed" msgid="2800582406842092709">"sen nome"</string>
<string name="bugreport_info_action" msgid="2158204228510576227">"Detalles"</string>
<string name="bugreport_screenshot_action" msgid="8677781721940614995">"Captura de pantalla"</string>
diff --git a/packages/SoundPicker/Android.bp b/packages/SoundPicker/Android.bp
index 1ac9bbb..235e672 100644
--- a/packages/SoundPicker/Android.bp
+++ b/packages/SoundPicker/Android.bp
@@ -34,4 +34,13 @@
platform_apis: true,
certificate: "media",
privileged: true,
+
+ optimize: {
+ enabled: true,
+ optimize: true,
+ shrink: true,
+ shrink_resources: true,
+ obfuscate: false,
+ proguard_compatibility: false,
+ },
}
diff --git a/packages/SoundPicker/res/layout/fragment_sound_picker.xml b/packages/SoundPicker/res/layout/fragment_ringtone_picker.xml
similarity index 100%
rename from packages/SoundPicker/res/layout/fragment_sound_picker.xml
rename to packages/SoundPicker/res/layout/fragment_ringtone_picker.xml
diff --git a/packages/SoundPicker/res/layout/fragment_vibration_picker.xml b/packages/SoundPicker/res/layout/fragment_vibration_picker.xml
deleted file mode 100644
index 34d95aa2..0000000
--- a/packages/SoundPicker/res/layout/fragment_vibration_picker.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<androidx.constraintlayout.widget.ConstraintLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:text="@string/empty_list"
- android:textColor="?android:attr/colorAccent"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:gravity="center"
- />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java b/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java
new file mode 100644
index 0000000..4fc2a86
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/BasePickerFragment.java
@@ -0,0 +1,312 @@
+/*
+ * 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.soundpicker;
+
+import android.app.Activity;
+import android.content.ContentProvider;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import dagger.hilt.android.AndroidEntryPoint;
+
+import java.util.Objects;
+
+/**
+ * Base class for generic picker fragments.
+ *
+ * <p>This fragment displays a recycler view that is populated by a {@link RingtoneListViewAdapter}
+ * with data provided by a {@link RingtoneListHandler}. Each item can be selected on click,
+ * which also triggers a ringtone preview performed by the shared {@link RingtonePickerViewModel}.
+ * The ringtone preview uses the selection state of all picker fragments (e.g. sound selected by
+ * one fragment and vibration selected by another).
+ */
+@AndroidEntryPoint(Fragment.class)
+public abstract class BasePickerFragment extends Hilt_BasePickerFragment implements
+ RingtoneListViewAdapter.Callbacks {
+
+ private static final String TAG = "BasePickerFragment";
+ private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
+ private boolean mIsManagedProfile;
+ private Drawable mWorkIconDrawable;
+
+ protected RingtoneListViewAdapter mRingtoneListViewAdapter;
+ protected RecyclerView mRecyclerView;
+ protected RingtonePickerViewModel.Config mPickerConfig;
+ protected RingtonePickerViewModel mRingtonePickerViewModel;
+ protected RingtoneListHandler.Config mRingtoneListConfig;
+ protected RingtoneListHandler mRingtoneListHandler;
+
+ public BasePickerFragment() {
+ super(R.layout.fragment_ringtone_picker);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
+ RingtonePickerViewModel.class);
+ mRingtoneListHandler = getRingtoneListHandler();
+ mRecyclerView = view.requireViewById(R.id.recycler_view);
+
+ mPickerConfig = mRingtonePickerViewModel.getPickerConfig();
+ mRingtoneListConfig = mRingtoneListHandler.getRingtoneListConfig();
+
+ mIsManagedProfile = UserManager.get(requireActivity()).isManagedProfile(
+ mPickerConfig.userId);
+
+ mRingtoneListViewAdapter = createRingtoneListViewAdapter();
+ mRecyclerView.setHasFixedSize(true);
+ mRecyclerView.setAdapter(mRingtoneListViewAdapter);
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
+ setSelectedItem(mRingtoneListHandler.getSelectedItemPosition());
+ prepareRecyclerView(mRecyclerView);
+ }
+
+ @Override
+ public boolean isWorkRingtone(int position) {
+ if (!mIsManagedProfile) {
+ return false;
+ }
+
+ /*
+ * Display the work icon if the ringtone belongs to a work profile. We
+ * can tell that a ringtone belongs to a work profile if the picker user
+ * is a managed profile, the ringtone Uri is in external storage, and
+ * either the uri has no user id or has the id of the picker user
+ */
+ Uri currentUri = mRingtoneListHandler.getRingtoneUri(position);
+ int uriUserId = ContentProvider.getUserIdFromUri(currentUri,
+ mPickerConfig.userId);
+ Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
+
+ return uriUserId == mPickerConfig.userId
+ && uriWithoutUserId.toString().startsWith(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString());
+ }
+
+ @Override
+ public Drawable getWorkIconDrawable() {
+ if (mWorkIconDrawable == null) {
+ mWorkIconDrawable = requireActivity().getPackageManager()
+ .getUserBadgeForDensityNoBackground(
+ UserHandle.of(mPickerConfig.userId), /* density= */ -1);
+ }
+
+ return mWorkIconDrawable;
+ }
+
+ @Override
+ public void onRingtoneSelected(int position) {
+ setSelectedItem(position);
+
+ // In the buttonless (watch-only) version, preemptively set our result since
+ // we won't have another chance to do so before the activity closes.
+ if (!mPickerConfig.showOkCancelButtons) {
+ setSuccessResultWithSelectedRingtone();
+ }
+
+ // Play clip
+ mRingtonePickerViewModel.playRingtone();
+ }
+
+ @Override
+ public void onAddRingtoneSelected() {
+ addRingtoneAsync();
+ }
+
+ /**
+ * Sets up the list by adding fixed items to the top and bottom, if required. And sets the
+ * selected item in the list.
+ * @param recyclerView The recyclerview that contains the list of displayed items.
+ */
+ protected void prepareRecyclerView(@NonNull RecyclerView recyclerView) {
+ // Reset the static item count, as this method can be called multiple times
+ mRingtoneListHandler.resetFixedItems();
+
+ if (mRingtoneListConfig.hasDefaultItem) {
+ int defaultItemPos = addDefaultRingtoneItem();
+
+ if (getSelectedItem() < 0
+ && RingtoneManager.isDefault(mRingtoneListConfig.initialSelectedUri)) {
+ setSelectedItem(defaultItemPos);
+ }
+ }
+
+ if (mRingtoneListConfig.hasSilentItem) {
+ int silentItemPos = addSilentItem();
+
+ // The 'Silent' item should use a null Uri
+ if (getSelectedItem() < 0
+ && mRingtoneListConfig.initialSelectedUri == null) {
+ setSelectedItem(silentItemPos);
+ }
+ }
+
+ if (getSelectedItem() < 0) {
+ setSelectedItem(mRingtoneListHandler.getRingtonePosition(
+ mRingtoneListConfig.initialSelectedUri));
+ }
+
+ // In the buttonless (watch-only) version, preemptively set our result since we won't
+ // have another chance to do so before the activity closes.
+ if (!mPickerConfig.showOkCancelButtons) {
+ setSuccessResultWithSelectedRingtone();
+ }
+
+ addNewRingtoneItem();
+
+ // Enable context menu in ringtone items
+ registerForContextMenu(recyclerView);
+ }
+
+ /**
+ * Returns the fragment's sound/vibration list handler.
+ * @return The ringtone list handler.
+ */
+ protected abstract RingtoneListHandler getRingtoneListHandler();
+
+ /**
+ * Starts the process to add a new ringtone to the list of ringtones asynchronously.
+ * Currently, only works for adding sound files.
+ */
+ protected abstract void addRingtoneAsync();
+
+ /**
+ * Adds an item to the end of the list that can be used to add new ringtones to the list.
+ * Currently, only works for adding sound files.
+ */
+ protected abstract void addNewRingtoneItem();
+
+ protected int getSelectedItem() {
+ return mRingtoneListHandler.getSelectedItemPosition();
+ }
+
+ /**
+ * Returns the selected URI to the caller activity.
+ */
+ protected void setSuccessResultWithSelectedRingtone() {
+ requireActivity().setResult(Activity.RESULT_OK,
+ new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI,
+ mRingtonePickerViewModel.getSelectedRingtoneUri()));
+ }
+
+ /**
+ * Creates a ringtone recyclerview adapter using the ringtone manager cursor.
+ * @return The created RingtoneListViewAdapter.
+ */
+ protected RingtoneListViewAdapter createRingtoneListViewAdapter() {
+ LocalizedCursor cursor = new LocalizedCursor(
+ mRingtoneListHandler.getRingtoneCursor(), getResources(), COLUMN_LABEL);
+ return new RingtoneListViewAdapter(cursor, /* RingtoneListViewAdapterCallbacks= */ this);
+ }
+
+ /**
+ * Sets the selected item in the list and scroll to the position in the recyclerview.
+ * @param pos the position of the selected item in the list.
+ */
+ protected void setSelectedItem(int pos) {
+ Objects.requireNonNull(mRingtoneListViewAdapter);
+ mRingtoneListHandler.setSelectedItemPosition(pos);
+ mRingtoneListViewAdapter.setSelectedItem(pos);
+ mRingtoneListHandler.setSelectedItemId(mRingtoneListViewAdapter.getItemId(pos));
+ mRecyclerView.scrollToPosition(pos);
+ }
+
+ /**
+ * Adds a fixed item to the fixed items list . A fixed item is one that is not from
+ * the RingtoneManager.
+ *
+ * @param textResId The resource ID of the text for the item.
+ * @return The index of the inserted fixed item in the adapter.
+ */
+ protected int addFixedItem(int textResId) {
+ return mRingtoneListViewAdapter.addTitleForFixedItem(textResId);
+ }
+
+ /**
+ * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
+ * selected item position to match the new position of the chosen ringtone.
+ * <p>
+ * This should only need to happen after adding or removing a ringtone.
+ */
+ protected void requeryForAdapter() {
+ mRingtonePickerViewModel.reinit();
+ // Refresh and set a new cursor, and closing the old one.
+ mRingtoneListViewAdapter = createRingtoneListViewAdapter();
+ mRecyclerView.setAdapter(mRingtoneListViewAdapter);
+ prepareRecyclerView(mRecyclerView);
+
+ // Update selected item location.
+ for (int i = 0; i < mRingtoneListViewAdapter.getItemCount(); i++) {
+ if (mRingtoneListViewAdapter.getItemId(i)
+ == mRingtoneListHandler.getSelectedItemId()) {
+ setSelectedItem(i);
+ return;
+ }
+ }
+
+ // If selected item is still unknown, then set it to the default item, if available.
+ // If it's not available, then attempt to set it to the silent item in the list.
+ int selectedPosition = mRingtoneListHandler.getDefaultItemPosition();
+
+ if (selectedPosition < 0) {
+ selectedPosition = mRingtoneListHandler.getSilentItemPosition();
+ }
+
+ setSelectedItem(selectedPosition);
+ }
+
+ private int addDefaultRingtoneItem() {
+ int defaultItemPosInAdapter = addFixedItem(
+ RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
+ mPickerConfig.ringtoneType));
+ int defaultItemPosInListHandler = mRingtoneListHandler.addDefaultItem();
+
+ if (defaultItemPosInAdapter != defaultItemPosInListHandler) {
+ Log.wtf(TAG, "Default item position in adapter and list handler must match.");
+ return RingtoneListHandler.ITEM_POSITION_UNKNOWN;
+ }
+
+ return defaultItemPosInListHandler;
+ }
+
+ private int addSilentItem() {
+ int silentItemPosInAdapter = addFixedItem(com.android.internal.R.string.ringtone_silent);
+ int silentItemPosInListHandler = mRingtoneListHandler.addSilentItem();
+
+ if (silentItemPosInAdapter != silentItemPosInListHandler) {
+ Log.wtf(TAG, "Silent item position in adapter and list handler must match.");
+ return RingtoneListHandler.ITEM_POSITION_UNKNOWN;
+ }
+
+ return silentItemPosInListHandler;
+ }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java
index 0a8a73b..cb41eab 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneFactory.java
@@ -17,8 +17,8 @@
package com.android.soundpicker;
import android.content.Context;
+import android.media.AudioAttributes;
import android.media.Ringtone;
-import android.media.RingtoneManager;
import android.net.Uri;
import dagger.hilt.android.qualifiers.ApplicationContext;
@@ -40,12 +40,23 @@
}
/**
- * Returns a {@link Ringtone} based on the provided URI.
+ * Returns a {@link Ringtone} built from the provided URI and audio attributes flags.
*
- * @param uri The URI used to get the {@link Ringtone}
- * @return a {@link Ringtone}
+ * @param uri The URI used to build the {@link Ringtone}.
+ * @param audioAttributesFlags A combination of audio attribute flags that affect the volume
+ * and settings when playing the ringtone.
+ * @return the built {@link Ringtone}.
*/
- public Ringtone create(Uri uri) {
- return RingtoneManager.getRingtone(mApplicationContext, uri);
+ public Ringtone create(Uri uri, int audioAttributesFlags) {
+ AudioAttributes audioAttributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setFlags(audioAttributesFlags)
+ .build();
+ // TODO: We are currently only using MEDIA_SOUND for enabledMedia. This will change once we
+ // start playing sound and/or vibration.
+ return new Ringtone.Builder(mApplicationContext, Ringtone.MEDIA_SOUND, audioAttributes)
+ .setUri(uri)
+ .build();
}
}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java
new file mode 100644
index 0000000..bb38e0e
--- /dev/null
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneListHandler.java
@@ -0,0 +1,222 @@
+/*
+ * 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.soundpicker;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.Nullable;
+import android.database.Cursor;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import javax.inject.Inject;
+
+/**
+ * Handles ringtone list state and actions. This includes keeping track of the selected item,
+ * ringtone manager cursor and added items to the list.
+ */
+public class RingtoneListHandler {
+
+ // TODO: We're using an empty URI instead of null, because null URIs still produce a sound,
+ // while empty ones don't (Potentially this might be due to empty URIs being perceived as
+ // malformed ones). We will switch to using the official silent URIs (SOUND_OFF, VIBRATION_OFF)
+ // once they become available.
+ static final Uri SILENT_URI = Uri.EMPTY;
+ static final int ITEM_POSITION_UNKNOWN = -1;
+
+ private static final String TAG = "RingtoneListHandler";
+
+ /** The position in the list of the 'Silent' item. */
+ private int mSilentItemPosition = ITEM_POSITION_UNKNOWN;
+ /** The position in the list of the 'Default' item. */
+ private int mDefaultItemPosition = ITEM_POSITION_UNKNOWN;
+ /** The number of fixed items in the list. */
+ private int mFixedItemCount;
+ /**
+ * Stable ID for the ringtone that is currently selected (may be -1 if no ringtone is selected).
+ */
+ private long mSelectedItemId = -1;
+ private int mSelectedItemPosition = ITEM_POSITION_UNKNOWN;
+
+ private RingtoneManager mRingtoneManager;
+ private Config mRingtoneListConfig;
+ private Cursor mRingtoneCursor;
+
+ /**
+ * Holds immutable info on the ringtone list that is displayed.
+ */
+ static final class Config {
+ /**
+ * Whether this list has the 'Default' item.
+ */
+ public final boolean hasDefaultItem;
+ /**
+ * The Uri to play when the 'Default' item is clicked.
+ */
+ public final Uri uriForDefaultItem;
+ /**
+ * Whether this list has the 'Silent' item.
+ */
+ public final boolean hasSilentItem;
+ /**
+ * The initially selected uri in the list.
+ */
+ public final Uri initialSelectedUri;
+
+ Config(boolean hasDefaultItem, Uri uriForDefaultItem, boolean hasSilentItem,
+ Uri initialSelectedUri) {
+ this.hasDefaultItem = hasDefaultItem;
+ this.uriForDefaultItem = uriForDefaultItem;
+ this.hasSilentItem = hasSilentItem;
+ this.initialSelectedUri = initialSelectedUri;
+ }
+ }
+
+ @Inject
+ RingtoneListHandler() {
+ }
+
+ void init(@NonNull Config ringtoneListConfig,
+ @NonNull RingtoneManager ringtoneManager, @NonNull Cursor ringtoneCursor) {
+ mRingtoneManager = requireNonNull(ringtoneManager);
+ mRingtoneListConfig = requireNonNull(ringtoneListConfig);
+ mRingtoneCursor = requireNonNull(ringtoneCursor);
+ }
+
+ Config getRingtoneListConfig() {
+ return mRingtoneListConfig;
+ }
+
+ Cursor getRingtoneCursor() {
+ requireInitCalled();
+ return mRingtoneCursor;
+ }
+
+ Uri getRingtoneUri(int position) {
+ if (position < 0) {
+ Log.w(TAG, "Selected item position is unknown.");
+ // When the selected item is ITEM_POSITION_UNKNOWN, it is not the case we expected.
+ // We return SILENT_URI for this case.
+ return SILENT_URI;
+ } else if (position == mDefaultItemPosition) {
+ // Use the default Uri that they originally gave us.
+ return mRingtoneListConfig.uriForDefaultItem;
+ } else if (position == mSilentItemPosition) {
+ // Use SILENT_URI for the 'Silent' item.
+ return SILENT_URI;
+ } else {
+ requireInitCalled();
+ return mRingtoneManager.getRingtoneUri(mapListPositionToRingtonePosition(position));
+ }
+ }
+
+ int getRingtonePosition(Uri uri) {
+ requireInitCalled();
+ return mapRingtonePositionToListPosition(mRingtoneManager.getRingtonePosition(uri));
+ }
+
+ void resetFixedItems() {
+ mFixedItemCount = 0;
+ mDefaultItemPosition = ITEM_POSITION_UNKNOWN;
+ mSilentItemPosition = ITEM_POSITION_UNKNOWN;
+ }
+
+ int addDefaultItem() {
+ if (mDefaultItemPosition < 0) {
+ mDefaultItemPosition = addFixedItem();
+ }
+ return mDefaultItemPosition;
+ }
+
+ int getDefaultItemPosition() {
+ return mDefaultItemPosition;
+ }
+
+ int addSilentItem() {
+ if (mSilentItemPosition < 0) {
+ mSilentItemPosition = addFixedItem();
+ }
+ return mSilentItemPosition;
+ }
+
+ public int getSilentItemPosition() {
+ return mSilentItemPosition;
+ }
+
+ int getSelectedItemPosition() {
+ return mSelectedItemPosition;
+ }
+
+ void setSelectedItemPosition(int selectedItemPosition) {
+ mSelectedItemPosition = selectedItemPosition;
+ }
+
+ void setSelectedItemId(long selectedItemId) {
+ mSelectedItemId = selectedItemId;
+ }
+
+ long getSelectedItemId() {
+ return mSelectedItemId;
+ }
+
+ @Nullable
+ Uri getSelectedRingtoneUri() {
+ return getRingtoneUri(mSelectedItemPosition);
+ }
+
+ /**
+ * Maps the item position in the list, to its equivalent position in the RingtoneManager.
+ *
+ * @param itemPosition the position of item in the list.
+ * @return position of the item in the RingtoneManager.
+ */
+ private int mapListPositionToRingtonePosition(int itemPosition) {
+ // If the manager position is less than add items, then return that.
+ if (itemPosition < mFixedItemCount) return itemPosition;
+
+ return itemPosition - mFixedItemCount;
+ }
+
+ /**
+ * Maps the item position in the RingtoneManager, to its equivalent position in the list.
+ *
+ * @param itemPosition the position of the item in the RingtoneManager.
+ * @return position of the item in the list.
+ */
+ private int mapRingtonePositionToListPosition(int itemPosition) {
+ // If the manager position is less than add items, then return that.
+ if (itemPosition < 0) return itemPosition;
+
+ return itemPosition + mFixedItemCount;
+ }
+
+ /**
+ * Increments the number of added fixed items and returns the index of the newest added item.
+ * @return index of the newest added fixed item.
+ */
+ private int addFixedItem() {
+ return mFixedItemCount++;
+ }
+
+ private void requireInitCalled() {
+ requireNonNull(mRingtoneManager);
+ requireNonNull(mRingtoneCursor);
+ }
+}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneAdapter.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java
similarity index 82%
rename from packages/SoundPicker/src/com/android/soundpicker/RingtoneAdapter.java
rename to packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java
index 1f33aa2..4ca8943 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtoneAdapter.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneListViewAdapter.java
@@ -51,36 +51,30 @@
* but not selected and its position will never change.
* </ul>
*/
-final class RingtoneAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+final class RingtoneListViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int VIEW_TYPE_FIXED_ITEM = 0;
private static final int VIEW_TYPE_DYNAMIC_ITEM = 1;
private static final int VIEW_TYPE_ADD_RINGTONE_ITEM = 2;
private final Cursor mCursor;
private final List<Integer> mFixedItemTitles;
- private final WorkRingtoneProvider mWorkRingtoneProvider;
- private final RingtoneSelectionListener mRingtoneSelectionListener;
+ private final Callbacks mCallbacks;
private final int mRowIDColumn;
private int mSelectedItem = -1;
@StringRes private Integer mAddRingtoneItemTitle;
- /** Listener for ringtone selections. */
- interface RingtoneSelectionListener {
+ /** Provides callbacks for the adapter. */
+ interface Callbacks {
void onRingtoneSelected(int position);
void onAddRingtoneSelected();
- }
- /** Provides a mean to detect work ringtones. */
- interface WorkRingtoneProvider {
boolean isWorkRingtone(int position);
-
Drawable getWorkIconDrawable();
}
- RingtoneAdapter(Cursor cursor, RingtoneSelectionListener ringtoneSelectionListener,
- WorkRingtoneProvider workRingtoneProvider) {
+ RingtoneListViewAdapter(Cursor cursor,
+ Callbacks callbacks) {
mCursor = cursor;
- mRingtoneSelectionListener = ringtoneSelectionListener;
- mWorkRingtoneProvider = workRingtoneProvider;
+ mCallbacks = callbacks;
mFixedItemTitles = new ArrayList<>();
mRowIDColumn = mCursor != null ? mCursor.getColumnIndex("_id") : -1;
setHasStableIds(true);
@@ -92,9 +86,15 @@
notifyItemChanged(mSelectedItem);
}
- void addTitleForFixedItem(@StringRes int textResId) {
+ /**
+ * Adds title to the fixed items list and returns the index of the newest added item.
+ * @param textResId the title to add to the fixed items list.
+ * @return The index of the newest added item in the fixed items list.
+ */
+ int addTitleForFixedItem(@StringRes int textResId) {
mFixedItemTitles.add(textResId);
notifyItemInserted(mFixedItemTitles.size() - 1);
+ return mFixedItemTitles.size() - 1;
}
void addTitleForAddRingtoneItem(@StringRes int textResId) {
@@ -112,18 +112,19 @@
com.android.internal.R.layout.select_dialog_singlechoice_material, parent,
false);
- return new FixedItemViewHolder(fixedItemView, mRingtoneSelectionListener);
+ return new FixedItemViewHolder(fixedItemView, mCallbacks);
}
if (viewType == VIEW_TYPE_ADD_RINGTONE_ITEM) {
View addRingtoneItemView = inflater.inflate(R.layout.add_new_sound_item, parent, false);
- return new AddRingtoneItemViewHolder(addRingtoneItemView, mRingtoneSelectionListener);
+ return new AddRingtoneItemViewHolder(addRingtoneItemView,
+ mCallbacks);
}
View view = inflater.inflate(R.layout.radio_with_work_badge, parent, false);
- return new DynamicItemViewHolder(view, mRingtoneSelectionListener);
+ return new DynamicItemViewHolder(view, mCallbacks);
}
@Override
@@ -152,9 +153,9 @@
throw new IllegalStateException("Could not move cursor to position: " + pos);
}
- Drawable workIcon = (mWorkRingtoneProvider != null)
- && mWorkRingtoneProvider.isWorkRingtone(position)
- ? mWorkRingtoneProvider.getWorkIconDrawable() : null;
+ Drawable workIcon = (mCallbacks != null)
+ && mCallbacks.isWorkRingtone(position)
+ ? mCallbacks.getWorkIconDrawable() : null;
viewHolder.onBind(mCursor.getString(RingtoneManager.TITLE_COLUMN_INDEX),
/* isChecked= */ position == mSelectedItem, workIcon);
@@ -207,18 +208,15 @@
private final CheckedTextView mTitleTextView;
private final ImageView mWorkIcon;
- DynamicItemViewHolder(View itemView, RingtoneSelectionListener listener) {
+ DynamicItemViewHolder(View itemView, Callbacks listener) {
super(itemView);
- mTitleTextView = itemView.findViewById(R.id.checked_text_view);
- mWorkIcon = itemView.findViewById(R.id.work_icon);
+ mTitleTextView = itemView.requireViewById(R.id.checked_text_view);
+ mWorkIcon = itemView.requireViewById(R.id.work_icon);
itemView.setOnClickListener(v -> listener.onRingtoneSelected(this.getLayoutPosition()));
}
void onBind(String title, boolean isChecked, Drawable workIcon) {
- Objects.requireNonNull(mTitleTextView);
- Objects.requireNonNull(mWorkIcon);
-
mTitleTextView.setText(title);
mTitleTextView.setChecked(isChecked);
@@ -234,7 +232,7 @@
private static class FixedItemViewHolder extends RecyclerView.ViewHolder {
private final CheckedTextView mTitleTextView;
- FixedItemViewHolder(View itemView, RingtoneSelectionListener listener) {
+ FixedItemViewHolder(View itemView, Callbacks listener) {
super(itemView);
mTitleTextView = (CheckedTextView) itemView;
@@ -252,16 +250,14 @@
private static class AddRingtoneItemViewHolder extends RecyclerView.ViewHolder {
private final TextView mTitleTextView;
- AddRingtoneItemViewHolder(View itemView, RingtoneSelectionListener listener) {
+ AddRingtoneItemViewHolder(View itemView, Callbacks listener) {
super(itemView);
- mTitleTextView = itemView.findViewById(R.id.add_new_sound_text);
+ mTitleTextView = itemView.requireViewById(R.id.add_new_sound_text);
itemView.setOnClickListener(v -> listener.onAddRingtoneSelected());
}
void onBind(@StringRes int title) {
- Objects.requireNonNull(mTitleTextView);
-
mTitleTextView.setText(title);
}
}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index 4d7cf1c..90a14f9 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -40,12 +40,15 @@
public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity {
private static final String TAG = "RingtonePickerActivity";
- // TODO: Use the extra key from RingtoneManager once it's added.
+ // TODO: Use the extra keys from RingtoneManager once they're added.
private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY";
+ private static final String EXTRA_VIBRATION_SHOW_DEFAULT = "EXTRA_VIBRATION_SHOW_DEFAULT";
+ private static final String EXTRA_VIBRATION_DEFAULT_URI = "EXTRA_VIBRATION_DEFAULT_URI";
+ private static final String EXTRA_VIBRATION_SHOW_SILENT = "EXTRA_VIBRATION_SHOW_SILENT";
+ private static final String EXTRA_VIBRATION_EXISTING_URI = "EXTRA_VIBRATION_EXISTING_URI";
private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false;
private RingtonePickerViewModel mRingtonePickerViewModel;
-
private int mAttributesFlags;
@Override
@@ -65,19 +68,6 @@
int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,
RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN);
- // Get whether to show the 'Default' item, and the URI to play when the default is clicked
- boolean hasDefaultItem =
- intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
- // The Uri to play when the 'Default' item is clicked.
- Uri uriForDefaultItem =
- intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
- if (uriForDefaultItem == null) {
- uriForDefaultItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
- }
-
- // Get whether this list has the 'Silent' item.
- boolean hasSilentItem =
- intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
// AudioAttributes flags
mAttributesFlags |= intent.getIntExtra(
RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
@@ -85,10 +75,6 @@
boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons);
- // Get the URI whose list item should have a checkmark
- Uri existingUri = intent
- .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
-
String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE);
if (title == null) {
title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType));
@@ -97,12 +83,15 @@
RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType(
ringtonePickerCategory);
- mRingtonePickerViewModel.init(new RingtonePickerViewModel.PickerConfig(title, pickerUserId,
- ringtoneType, hasDefaultItem, uriForDefaultItem, hasSilentItem,
- mAttributesFlags, existingUri, showOkCancelButtons, pickerType));
+ RingtoneListHandler.Config soundListConfig = getSoundListConfig(pickerType, intent,
+ ringtoneType);
+ RingtoneListHandler.Config vibrationListConfig = getVibrationListConfig(pickerType, intent);
- // The volume keys will control the stream that we are choosing a ringtone for
- setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
+ RingtonePickerViewModel.Config pickerConfig =
+ new RingtonePickerViewModel.Config(title, pickerUserId, ringtoneType,
+ showOkCancelButtons, mAttributesFlags, pickerType);
+
+ mRingtonePickerViewModel.init(pickerConfig, soundListConfig, vibrationListConfig);
if (savedInstanceState == null) {
TabbedDialogFragment dialogFragment = new TabbedDialogFragment();
@@ -115,8 +104,73 @@
ft.addToBackStack(null);
dialogFragment.show(ft, TabbedDialogFragment.TAG);
}
+
+ // The volume keys will control the stream that we are choosing a ringtone for
+ setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType());
}
+ private RingtoneListHandler.Config getSoundListConfig(
+ RingtonePickerViewModel.PickerType pickerType, Intent intent, int ringtoneType) {
+ if (pickerType != RingtonePickerViewModel.PickerType.SOUND_PICKER
+ && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
+ // This ringtone picker does not require a sound picker.
+ return null;
+ }
+
+ // Get whether to show the 'Default' sound item, and the URI to play when it's clicked
+ boolean hasDefaultSoundItem =
+ intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+
+ // The Uri to play when the 'Default' sound item is clicked.
+ Uri uriForDefaultSoundItem =
+ intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
+ if (uriForDefaultSoundItem == null) {
+ uriForDefaultSoundItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType);
+ }
+
+ // Get whether this list has the 'Silent' sound item.
+ boolean hasSilentSoundItem =
+ intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
+
+ // AudioAttributes flags
+ mAttributesFlags |= intent.getIntExtra(
+ RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS,
+ 0 /*defaultValue == no flags*/);
+
+ // Get the sound URI whose list item should have a checkmark
+ Uri existingSoundUri = intent
+ .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
+
+ return new RingtoneListHandler.Config(hasDefaultSoundItem,
+ uriForDefaultSoundItem, hasSilentSoundItem, existingSoundUri);
+ }
+
+ private RingtoneListHandler.Config getVibrationListConfig(
+ RingtonePickerViewModel.PickerType pickerType, Intent intent) {
+ if (pickerType != RingtonePickerViewModel.PickerType.VIBRATION_PICKER
+ && pickerType != RingtonePickerViewModel.PickerType.RINGTONE_PICKER) {
+ // This ringtone picker does not require a vibration picker.
+ return null;
+ }
+
+ // Get whether to show the 'Default' vibration item, and the URI to play when it's clicked
+ boolean hasDefaultVibrationItem =
+ intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_DEFAULT, false);
+
+ // The Uri to play when the 'Default' vibration item is clicked.
+ Uri uriForDefaultVibrationItem = intent.getParcelableExtra(EXTRA_VIBRATION_DEFAULT_URI);
+
+ // Get whether this list has the 'Silent' vibration item.
+ boolean hasSilentVibrationItem =
+ intent.getBooleanExtra(EXTRA_VIBRATION_SHOW_SILENT, true);
+
+ // Get the vibration URI whose list item should have a checkmark
+ Uri existingVibrationUri = intent.getParcelableExtra(EXTRA_VIBRATION_EXISTING_URI);
+
+ return new RingtoneListHandler.Config(
+ hasDefaultVibrationItem, uriForDefaultVibrationItem, hasSilentVibrationItem,
+ existingVibrationUri);
+ }
@Override
public void onDestroy() {
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java
index 914f16a..2c09711 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java
@@ -20,8 +20,6 @@
import android.annotation.Nullable;
import android.annotation.StringRes;
-import android.database.Cursor;
-import android.media.AudioAttributes;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
@@ -45,7 +43,8 @@
import javax.inject.Inject;
/**
- * View model for {@link RingtonePickerActivity}.
+ * A view model which holds immutable info about the picker state and means to retrieve and play
+ * currently selected ringtones.
*/
@HiltViewModel
public final class RingtonePickerViewModel extends ViewModel {
@@ -58,39 +57,25 @@
*/
@VisibleForTesting
static Ringtone sPlayingRingtone;
+
private static final String TAG = "RingtonePickerViewModel";
- private static final String RINGTONE_MANAGER_NULL_MESSAGE =
- "RingtoneManager must not be null. Did you forget to call "
- + "RingtonePickerViewModel#initRingtoneManager?";
- private static final int ITEM_POSITION_UNKNOWN = -1;
private final RingtoneManagerFactory mRingtoneManagerFactory;
private final RingtoneFactory mRingtoneFactory;
+ private final RingtoneListHandler mSoundListHandler;
+ private final RingtoneListHandler mVibrationListHandler;
private final ListeningExecutorService mListeningExecutorService;
- /** The position in the list of the 'Silent' item. */
- private int mSilentItemPosition = ITEM_POSITION_UNKNOWN;
- /** The position in the list of the ringtone to sample. */
- private int mSampleItemPosition = ITEM_POSITION_UNKNOWN;
- /** The position in the list of the 'Default' item. */
- private int mDefaultItemPosition = ITEM_POSITION_UNKNOWN;
- /** The number of fixed items in the list. */
- private int mFixedItemCount;
- private ListenableFuture<Uri> mAddCustomRingtoneFuture;
private RingtoneManager mRingtoneManager;
/**
- * Stable ID for the ringtone that is currently selected (may be -1 if no ringtone is selected).
- */
- private long mSelectedItemId = -1;
- private int mSelectedItemPosition = ITEM_POSITION_UNKNOWN;
-
- /**
* The ringtone that's currently playing.
*/
private Ringtone mCurrentRingtone;
- private PickerConfig mPickerConfig;
+ private Config mPickerConfig;
+
+ private ListenableFuture<Uri> mAddCustomRingtoneFuture;
public enum PickerType {
RINGTONE_PICKER,
@@ -101,7 +86,7 @@
/**
* Holds immutable info on the picker that should be displayed.
*/
- static final class PickerConfig {
+ static final class Config {
public final String title;
/**
* Id of the user to which the ringtone picker should list the ringtones.
@@ -112,45 +97,23 @@
*/
public final int ringtoneType;
/**
- * Whether this list has the 'Default' item.
- */
- public final boolean hasDefaultItem;
- /**
- * The Uri to play when the 'Default' item is clicked.
- */
- public final Uri uriForDefaultItem;
- /**
- * Whether this list has the 'Silent' item.
- */
- public final boolean hasSilentItem;
- /**
* AudioAttributes flags.
*/
public final int audioAttributesFlags;
/**
- * The Uri to place a checkmark next to.
- */
- public final Uri existingUri;
- /**
* In the buttonless (watch-only) version we don't show the OK/Cancel buttons.
*/
public final boolean showOkCancelButtons;
public final PickerType mPickerType;
- PickerConfig(String title, int userId, int ringtoneType,
- boolean hasDefaultItem, Uri uriForDefaultItem, boolean hasSilentItem,
- int audioAttributesFlags, Uri existingUri, boolean showOkCancelButtons,
- PickerType pickerType) {
+ Config(String title, int userId, int ringtoneType, boolean showOkCancelButtons,
+ int audioAttributesFlags, PickerType pickerType) {
this.title = title;
this.userId = userId;
this.ringtoneType = ringtoneType;
- this.hasDefaultItem = hasDefaultItem;
- this.uriForDefaultItem = uriForDefaultItem;
- this.hasSilentItem = hasSilentItem;
- this.audioAttributesFlags = audioAttributesFlags;
- this.existingUri = existingUri;
this.showOkCancelButtons = showOkCancelButtons;
+ this.audioAttributesFlags = audioAttributesFlags;
this.mPickerType = pickerType;
}
}
@@ -158,17 +121,14 @@
@Inject
RingtonePickerViewModel(RingtoneManagerFactory ringtoneManagerFactory,
RingtoneFactory ringtoneFactory,
- ListeningExecutorServiceFactory listeningExecutorServiceFactory) {
+ ListeningExecutorServiceFactory listeningExecutorServiceFactory,
+ RingtoneListHandler soundListHandler,
+ RingtoneListHandler vibrationListHandler) {
mRingtoneManagerFactory = ringtoneManagerFactory;
mRingtoneFactory = ringtoneFactory;
mListeningExecutorService = listeningExecutorServiceFactory.createSingleThreadExecutor();
- }
-
- @NonNull
- PickerConfig getPickerConfig() {
- return requireNonNull(mPickerConfig,
- "PickerConfig was never set. Did you forget to call "
- + "RingtonePickerViewModel#init?");
+ mSoundListHandler = soundListHandler;
+ mVibrationListHandler = vibrationListHandler;
}
@StringRes
@@ -218,129 +178,68 @@
}
}
- void init(@NonNull PickerConfig pickerConfig) {
+ void init(@NonNull Config pickerConfig,
+ RingtoneListHandler.Config soundListConfig,
+ RingtoneListHandler.Config vibrationListConfig) {
mRingtoneManager = mRingtoneManagerFactory.create();
mPickerConfig = pickerConfig;
- if (pickerConfig.ringtoneType != RINGTONE_TYPE_UNKNOWN) {
- mRingtoneManager.setType(pickerConfig.ringtoneType);
+ if (mPickerConfig.ringtoneType != RINGTONE_TYPE_UNKNOWN) {
+ mRingtoneManager.setType(mPickerConfig.ringtoneType);
+ }
+ if (soundListConfig != null) {
+ mSoundListHandler.init(soundListConfig, mRingtoneManager,
+ mRingtoneManager.getCursor());
+ }
+ if (vibrationListConfig != null) {
+ // TODO: Switch to the vibration cursor, once the API is made available.
+ mVibrationListHandler.init(vibrationListConfig, mRingtoneManager,
+ mRingtoneManager.getCursor());
}
}
/**
- * Adds an audio file to the list of ringtones asynchronously.
- * Any previous async tasks are canceled before start the new one.
+ * Re-initializes the view model which is required after updating any of the picker lists.
+ * This could happen when adding a custom ringtone.
+ */
+ void reinit() {
+ init(mPickerConfig, mSoundListHandler.getRingtoneListConfig(),
+ mVibrationListHandler.getRingtoneListConfig());
+ }
+
+ @NonNull
+ Config getPickerConfig() {
+ requireInitCalled();
+ return mPickerConfig;
+ }
+
+ @NonNull
+ RingtoneListHandler getSoundListHandler() {
+ return mSoundListHandler;
+ }
+
+ @NonNull
+ RingtoneListHandler getVibrationListHandler() {
+ return mVibrationListHandler;
+ }
+
+ /**
+ * Combined the currently selected sound and vibration URIs and returns a unified URI. If the
+ * picker does not show either sound or vibration, that portion of the URI will be null.
*
- * @param uri Uri of the file to be added as ringtone. Must be a media file.
- * @param type The type of the ringtone to be added.
- * @param callback The callback to invoke when the task is completed.
- * @param executor The executor to run the callback on when the task completes.
+ * Currently only the sound URI is returned, since we don't have the API to retrieve vibrations
+ * yet.
+ * @return Combined sound and vibration URI.
*/
- void addRingtoneAsync(Uri uri, int type, FutureCallback<Uri> callback, Executor executor) {
- // Cancel any currently running add ringtone tasks before starting a new one
- cancelPendingAsyncTasks();
- mAddCustomRingtoneFuture = mListeningExecutorService.submit(() -> addRingtone(uri, type));
- Futures.addCallback(mAddCustomRingtoneFuture, callback, executor);
- }
-
- /**
- * Cancels all pending async tasks.
- */
- void cancelPendingAsyncTasks() {
- if (mAddCustomRingtoneFuture != null && !mAddCustomRingtoneFuture.isDone()) {
- mAddCustomRingtoneFuture.cancel(/* mayInterruptIfRunning= */ true);
- }
+ Uri getSelectedRingtoneUri() {
+ // TODO: Combine sound and vibration URIs before returning.
+ return mSoundListHandler.getSelectedRingtoneUri();
}
int getRingtoneStreamType() {
- requireNonNull(mRingtoneManager, RINGTONE_MANAGER_NULL_MESSAGE);
+ requireInitCalled();
return mRingtoneManager.inferStreamType();
}
- Cursor getRingtoneCursor() {
- requireNonNull(mRingtoneManager, RINGTONE_MANAGER_NULL_MESSAGE);
- return mRingtoneManager.getCursor();
- }
-
- Uri getRingtoneUri(int position) {
- requireNonNull(mRingtoneManager, RINGTONE_MANAGER_NULL_MESSAGE);
- return mRingtoneManager.getRingtoneUri(mapListPositionToRingtonePosition(position));
- }
-
- int getRingtonePosition(Uri uri) {
- requireNonNull(mRingtoneManager, RINGTONE_MANAGER_NULL_MESSAGE);
- return mapRingtonePositionToListPosition(mRingtoneManager.getRingtonePosition(uri));
- }
-
- /**
- * Maps the item position in the list, to its equivalent position in the RingtoneManager.
- *
- * @param itemPosition the position of item in the list.
- * @return position of the item in the RingtoneManager.
- */
- private int mapListPositionToRingtonePosition(int itemPosition) {
- // If the manager position is -1 (for not found), then return that.
- if (itemPosition < 0) return itemPosition;
-
- return itemPosition - mFixedItemCount;
- }
-
- /**
- * Maps the item position in the RingtoneManager, to its equivalent position in the list.
- *
- * @param itemPosition the position of the item in the RingtoneManager.
- * @return position of the item in the list.
- */
- private int mapRingtonePositionToListPosition(int itemPosition) {
- // If the manager position is -1 (for not found), then return that.
- if (itemPosition < 0) return itemPosition;
-
- return itemPosition + mFixedItemCount;
- }
-
- void resetFixedItemCount() {
- mFixedItemCount = 0;
- }
-
- int incrementAndGetFixedItemCount() {
- return mFixedItemCount++;
- }
-
- void setDefaultItemPosition(int defaultItemPosition) {
- mDefaultItemPosition = defaultItemPosition;
- }
-
- int getSilentItemPosition() {
- return mSilentItemPosition;
- }
-
- void setSilentItemPosition(int silentItemPosition) {
- mSilentItemPosition = silentItemPosition;
- }
-
- public int getSampleItemPosition() {
- return mSampleItemPosition;
- }
-
- public void setSampleItemPosition(int sampleItemPosition) {
- mSampleItemPosition = sampleItemPosition;
- }
-
- public int getSelectedItemPosition() {
- return mSelectedItemPosition;
- }
-
- public void setSelectedItemPosition(int selectedItemPosition) {
- mSelectedItemPosition = selectedItemPosition;
- }
-
- public void setSelectedItemId(long selectedItemId) {
- mSelectedItemId = selectedItemId;
- }
-
- public long getSelectedItemId() {
- return mSelectedItemId;
- }
-
void onPause(boolean isChangingConfigurations) {
if (!isChangingConfigurations) {
stopAnyPlayingRingtone();
@@ -355,51 +254,50 @@
}
}
- @Nullable
- Uri getCurrentlySelectedRingtoneUri() {
- if (mSelectedItemPosition == ITEM_POSITION_UNKNOWN) {
- // When the selected item is POS_UNKNOWN, it is not the case we expected.
- // We return null for this case.
- return null;
- } else if (mSelectedItemPosition == mDefaultItemPosition) {
- // Use the default Uri that they originally gave us.
- return mPickerConfig.uriForDefaultItem;
- } else if (mSelectedItemPosition == mSilentItemPosition) {
- // Use a null Uri for the 'Silent' item.
- return null;
- } else {
- return getRingtoneUri(mSelectedItemPosition);
+ /**
+ * Plays a ringtone which is created using the currently selected sound and vibration URIs. If
+ * this is a sound or vibration only picker, then the other portion of the URI will be empty
+ * and should not affect the played ringtone.
+ *
+ * Currently, we only use the sound URI to create the ringtone, since we still don't have the
+ * API to retrieve the available vibrations list.
+ */
+ void playRingtone() {
+ requireInitCalled();
+ stopAnyPlayingRingtone();
+
+ mCurrentRingtone = mRingtoneFactory.create(getSelectedRingtoneUri(),
+ mPickerConfig.audioAttributesFlags);
+
+ if (mCurrentRingtone != null) {
+ mCurrentRingtone.play();
}
}
- void playRingtone(int position, Uri uriForDefaultItem, int attributesFlags) {
- requireNonNull(mRingtoneManager, RINGTONE_MANAGER_NULL_MESSAGE);
- stopAnyPlayingRingtone();
- if (mSampleItemPosition == mSilentItemPosition) {
- return;
+ /**
+ * Cancels all pending async tasks.
+ */
+ void cancelPendingAsyncTasks() {
+ if (mAddCustomRingtoneFuture != null && !mAddCustomRingtoneFuture.isDone()) {
+ mAddCustomRingtoneFuture.cancel(/* mayInterruptIfRunning= */ true);
}
+ }
- if (mSampleItemPosition == mDefaultItemPosition) {
- mCurrentRingtone = mRingtoneFactory.create(uriForDefaultItem);
- /*
- * Stream type of mDefaultRingtone is not set explicitly here. It should be set in
- * accordance with mRingtoneManager of this Activity.
- */
- if (mCurrentRingtone != null) {
- mCurrentRingtone.setStreamType(mRingtoneManager.inferStreamType());
- }
- } else {
- mCurrentRingtone = mRingtoneManager.getRingtone(
- mapListPositionToRingtonePosition(position));
- }
-
- if (mCurrentRingtone != null) {
- if (attributesFlags != 0) {
- mCurrentRingtone.setAudioAttributes(new AudioAttributes.Builder(
- mCurrentRingtone.getAudioAttributes()).setFlags(attributesFlags).build());
- }
- mCurrentRingtone.play();
- }
+ /**
+ * Adds an audio file to the list of ringtones asynchronously.
+ * Any previous async tasks are canceled before start the new one.
+ *
+ * @param uri Uri of the file to be added as ringtone. Must be a media file.
+ * @param type The type of the ringtone to be added.
+ * @param callback The callback to invoke when the task is completed.
+ * @param executor The executor to run the callback on when the task completes.
+ */
+ void addSoundRingtoneAsync(Uri uri, int type, FutureCallback<Uri> callback, Executor executor) {
+ // Cancel any currently running add ringtone tasks before starting a new one
+ cancelPendingAsyncTasks();
+ mAddCustomRingtoneFuture = mListeningExecutorService.submit(
+ () -> addRingtone(uri, type));
+ Futures.addCallback(mAddCustomRingtoneFuture, callback, executor);
}
/**
@@ -412,7 +310,7 @@
*/
@Nullable
private Uri addRingtone(Uri uri, int type) throws IOException {
- requireNonNull(mRingtoneManager, RINGTONE_MANAGER_NULL_MESSAGE);
+ requireInitCalled();
return mRingtoneManager.addCustomExternalRingtone(uri, type);
}
@@ -434,4 +332,9 @@
}
mCurrentRingtone = null;
}
+
+ private void requireInitCalled() {
+ requireNonNull(mRingtoneManager);
+ requireNonNull(mPickerConfig);
+ }
}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java b/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java
index e07d109..a37191f 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java
@@ -17,16 +17,10 @@
package com.android.soundpicker;
import android.app.Activity;
-import android.content.ContentProvider;
import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
@@ -35,74 +29,20 @@
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
-import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
import com.google.common.util.concurrent.FutureCallback;
-import dagger.hilt.android.AndroidEntryPoint;
-
import org.jetbrains.annotations.NotNull;
-import java.util.Objects;
-
/**
- * A fragment that will display a picker used to select sound or silent. It also includes the
+ * A fragment that displays a picker used to select sound or silent. It also includes the
* ability to add custom sounds.
*/
-@AndroidEntryPoint(Fragment.class)
-public class SoundPickerFragment extends Hilt_SoundPickerFragment {
+public class SoundPickerFragment extends BasePickerFragment {
private static final String TAG = "SoundPickerFragment";
- private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE;
- private static final int POS_UNKNOWN = -1;
-
- private RingtonePickerViewModel.PickerConfig mPickerConfig;
- private boolean mIsManagedProfile;
- private RingtonePickerViewModel mRingtonePickerViewModel;
- private RingtoneAdapter mRingtoneAdapter;
- private RecyclerView mSoundRecyclerView;
-
- private final RingtoneAdapter.WorkRingtoneProvider mWorkRingtoneProvider =
- new RingtoneAdapter.WorkRingtoneProvider() {
- private Drawable mWorkIconDrawable;
- @Override
- public boolean isWorkRingtone(int position) {
- if (mIsManagedProfile) {
- /*
- * Display the w ork icon if the ringtone belongs to a work profile. We
- * can tell that a ringtone belongs to a work profile if the picker user
- * is a managed profile, the ringtone Uri is in external storage, and
- * either the uri has no user id or has the id of the picker user
- */
- Uri currentUri = mRingtonePickerViewModel.getRingtoneUri(position);
- int uriUserId = ContentProvider.getUserIdFromUri(currentUri,
- mPickerConfig.userId);
- Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri);
-
- return uriUserId == mPickerConfig.userId
- && uriWithoutUserId.toString().startsWith(
- MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString());
- }
-
- return false;
- }
-
- @Override
- public Drawable getWorkIconDrawable() {
- if (mWorkIconDrawable == null) {
- mWorkIconDrawable = requireActivity().getPackageManager()
- .getUserBadgeForDensityNoBackground(
- UserHandle.of(mPickerConfig.userId), /* density= */ -1);
- }
-
- return mWorkIconDrawable;
- }
- };
private final FutureCallback<Uri> mAddCustomRingtoneCallback = new FutureCallback<>() {
@Override
@@ -127,7 +67,7 @@
if (result.getResultCode() == Activity.RESULT_OK) {
// There are no request codes
Intent data = result.getData();
- mRingtonePickerViewModel.addRingtoneAsync(data.getData(),
+ mRingtonePickerViewModel.addSoundRingtoneAsync(data.getData(),
mPickerConfig.ringtoneType,
mAddCustomRingtoneCallback,
// Causes the callback to be executed on the main thread.
@@ -137,130 +77,34 @@
}
});
- private final RingtoneAdapter.RingtoneSelectionListener mRingtoneSelectionListener =
- new RingtoneAdapter.RingtoneSelectionListener() {
- @Override
- public void onRingtoneSelected(int position) {
- SoundPickerFragment.this.setSelectedItem(position);
-
- // In the buttonless (watch-only) version, preemptively set our result since
- // we won't have another chance to do so before the activity closes.
- if (!mPickerConfig.showOkCancelButtons) {
- setSuccessResultWithRingtone(
- mRingtonePickerViewModel.getCurrentlySelectedRingtoneUri());
- }
-
- // Play clip
- playRingtone(position);
- }
-
- @Override
- public void onAddRingtoneSelected() {
- // The "Add new ringtone" item was clicked. Start a file picker intent to
- // select only audio files (MIME type "audio/*")
- final Intent chooseFile = getMediaFilePickerIntent();
- mActivityResultLauncher.launch(chooseFile);
- }
- };
-
- public SoundPickerFragment() {
- super(R.layout.fragment_sound_picker);
+ @Override
+ public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
+ mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
+ RingtonePickerViewModel.class);
+ super.onViewCreated(view, savedInstanceState);
}
@Override
- public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
- RingtonePickerViewModel.class);
- mSoundRecyclerView = view.findViewById(R.id.recycler_view);
- Objects.requireNonNull(mSoundRecyclerView);
-
- mPickerConfig = mRingtonePickerViewModel.getPickerConfig();
- mIsManagedProfile = UserManager.get(requireActivity()).isManagedProfile(
- mPickerConfig.userId);
-
- mRingtoneAdapter = createRingtoneAdapter();
- mSoundRecyclerView.setHasFixedSize(true);
- mSoundRecyclerView.setAdapter(mRingtoneAdapter);
- mSoundRecyclerView.setLayoutManager(new LinearLayoutManager(requireActivity()));
- setSelectedItem(mRingtonePickerViewModel.getSelectedItemPosition());
- prepareRecyclerView(mSoundRecyclerView);
+ protected RingtoneListHandler getRingtoneListHandler() {
+ return mRingtonePickerViewModel.getSoundListHandler();
}
- private void prepareRecyclerView(@NonNull RecyclerView recyclerView) {
- // Reset the static item count, as this method can be called multiple times
- mRingtonePickerViewModel.resetFixedItemCount();
+ @Override
+ protected void addRingtoneAsync() {
+ // The "Add new ringtone" item was clicked. Start a file picker intent to
+ // select only audio files (MIME type "audio/*")
+ final Intent chooseFile = getMediaFilePickerIntent();
+ mActivityResultLauncher.launch(chooseFile);
+ }
- if (mPickerConfig.hasDefaultItem) {
- int defaultItemPos = addDefaultRingtoneItem();
-
- if (getSelectedItem() == POS_UNKNOWN
- && RingtoneManager.isDefault(mPickerConfig.existingUri)) {
- setSelectedItem(defaultItemPos);
- }
- }
-
- if (mPickerConfig.hasSilentItem) {
- int silentItemPos = addSilentItem();
-
- // The 'Silent' item should use a null Uri
- if (getSelectedItem() == POS_UNKNOWN && mPickerConfig.existingUri == null) {
- setSelectedItem(silentItemPos);
- }
- }
-
- if (getSelectedItem() == POS_UNKNOWN) {
- setSelectedItem(
- mRingtonePickerViewModel.getRingtonePosition(mPickerConfig.existingUri));
- }
-
- // In the buttonless (watch-only) version, preemptively set our result since we won't
- // have another chance to do so before the activity closes.
- if (!mPickerConfig.showOkCancelButtons) {
- setSuccessResultWithRingtone(
- mRingtonePickerViewModel.getCurrentlySelectedRingtoneUri());
- }
+ @Override
+ protected void addNewRingtoneItem() {
// If external storage is available, add a button to install sounds from storage.
if (resolvesMediaFilePicker()
&& Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- addNewSoundItem();
+ mRingtoneListViewAdapter.addTitleForAddRingtoneItem(
+ RingtonePickerViewModel.getAddNewItemTextByType(mPickerConfig.ringtoneType));
}
-
- // Enable context menu in ringtone items
- registerForContextMenu(recyclerView);
- }
-
- /**
- * Re-query RingtoneManager for the most recent set of installed ringtones. May move the
- * selected item position to match the new position of the chosen sound.
- * <p>
- * This should only need to happen after adding or removing a ringtone.
- */
- private void requeryForAdapter() {
- // Refresh and set a new cursor, closing the old one.
- mRingtonePickerViewModel.init(mPickerConfig);
- mRingtoneAdapter = createRingtoneAdapter();
- mSoundRecyclerView.setAdapter(mRingtoneAdapter);
- prepareRecyclerView(mSoundRecyclerView);
-
- // Update selected item location.
- int selectedPosition = POS_UNKNOWN;
- for (int i = 0; i < mRingtoneAdapter.getItemCount(); i++) {
- if (mRingtoneAdapter.getItemId(i) == mRingtonePickerViewModel.getSelectedItemId()) {
- selectedPosition = i;
- break;
- }
- }
- if (mPickerConfig.hasSilentItem && selectedPosition == POS_UNKNOWN) {
- selectedPosition = mRingtonePickerViewModel.getSilentItemPosition();
- }
- setSelectedItem(selectedPosition);
- }
-
- private void playRingtone(int position) {
- mRingtonePickerViewModel.setSampleItemPosition(position);
- mRingtonePickerViewModel.playRingtone(mRingtonePickerViewModel.getSampleItemPosition(),
- mPickerConfig.uriForDefaultItem, mPickerConfig.audioAttributesFlags);
}
private boolean resolvesMediaFilePicker() {
@@ -275,58 +119,4 @@
new String[]{"audio/*", "application/ogg"});
return chooseFile;
}
-
- private void setSuccessResultWithRingtone(Uri ringtoneUri) {
- requireActivity().setResult(Activity.RESULT_OK,
- new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri));
- }
-
- private int getSelectedItem() {
- return mRingtonePickerViewModel.getSelectedItemPosition();
- }
-
- private void setSelectedItem(int pos) {
- Objects.requireNonNull(mRingtoneAdapter);
- mRingtonePickerViewModel.setSelectedItemPosition(pos);
- mRingtoneAdapter.setSelectedItem(pos);
- mRingtonePickerViewModel.setSelectedItemId(mRingtoneAdapter.getItemId(pos));
- mSoundRecyclerView.scrollToPosition(pos);
- }
-
- /**
- * Adds a fixed item to the fixed items list . A fixed item is one that is not from
- * the RingtoneManager.
- *
- * @param textResId The resource ID of the text for the item.
- * @return The position of the inserted item.
- */
- private int addFixedItem(int textResId) {
- mRingtoneAdapter.addTitleForFixedItem(textResId);
- return mRingtonePickerViewModel.incrementAndGetFixedItemCount();
- }
-
- private int addDefaultRingtoneItem() {
- int defaultRingtoneItemPos = addFixedItem(
- RingtonePickerViewModel.getDefaultRingtoneItemTextByType(
- mPickerConfig.ringtoneType));
- mRingtonePickerViewModel.setDefaultItemPosition(defaultRingtoneItemPos);
- return defaultRingtoneItemPos;
- }
-
- private int addSilentItem() {
- int silentItemPos = addFixedItem(com.android.internal.R.string.ringtone_silent);
- mRingtonePickerViewModel.setSilentItemPosition(silentItemPos);
- return silentItemPos;
- }
-
- private void addNewSoundItem() {
- mRingtoneAdapter.addTitleForAddRingtoneItem(
- RingtonePickerViewModel.getAddNewItemTextByType(mPickerConfig.ringtoneType));
- }
-
- private RingtoneAdapter createRingtoneAdapter() {
- LocalizedCursor cursor = new LocalizedCursor(
- mRingtonePickerViewModel.getRingtoneCursor(), getResources(), COLUMN_LABEL);
- return new RingtoneAdapter(cursor, mRingtoneSelectionListener, mWorkRingtoneProvider);
- }
}
diff --git a/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java b/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java
index 63140d2..50ea9d7 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java
@@ -23,7 +23,6 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.media.RingtoneManager;
-import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -42,8 +41,6 @@
import org.jetbrains.annotations.NotNull;
-import java.util.Objects;
-
/**
* A dialog fragment with a sound and/or vibration tab based on the picker type.
* <ul>
@@ -59,6 +56,17 @@
private RingtonePickerViewModel mRingtonePickerViewModel;
+ private final ViewPager2.OnPageChangeCallback mOnPageChangeCallback =
+ new ViewPager2.OnPageChangeCallback() {
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ super.onPageScrollStateChanged(state);
+ if (state == ViewPager2.SCROLL_STATE_IDLE) {
+ mRingtonePickerViewModel.onStop(/* isChangingConfigurations= */ false);
+ }
+ }
+ };
+
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -77,8 +85,7 @@
dialogBuilder
.setPositiveButton(getString(com.android.internal.R.string.ok),
(dialog, whichButton) -> {
- setSuccessResultWithRingtone(
- mRingtonePickerViewModel.getCurrentlySelectedRingtoneUri());
+ setSuccessResultWithSelectedRingtone();
requireActivity().finish();
})
.setNegativeButton(getString(com.android.internal.R.string.cancel),
@@ -110,9 +117,10 @@
}
}
- private void setSuccessResultWithRingtone(Uri ringtoneUri) {
+ private void setSuccessResultWithSelectedRingtone() {
requireActivity().setResult(Activity.RESULT_OK,
- new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri));
+ new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI,
+ mRingtonePickerViewModel.getSelectedRingtoneUri()));
}
/**
@@ -123,10 +131,8 @@
*/
private View buildTabbedView(@NonNull LayoutInflater inflater) {
View view = inflater.inflate(R.layout.fragment_tabbed_dialog, null, false);
- TabLayout tabLayout = view.findViewById(R.id.tabLayout);
- ViewPager2 viewPager = view.findViewById(R.id.masterViewPager);
- Objects.requireNonNull(tabLayout);
- Objects.requireNonNull(viewPager);
+ TabLayout tabLayout = view.requireViewById(R.id.tabLayout);
+ ViewPager2 viewPager = view.requireViewById(R.id.masterViewPager);
ViewPagerAdapter adapter = new ViewPagerAdapter(requireActivity());
addFragments(adapter);
@@ -137,6 +143,7 @@
}
viewPager.setAdapter(adapter);
+ viewPager.registerOnPageChangeCallback(mOnPageChangeCallback);
new TabLayoutMediator(tabLayout, viewPager,
(tab, position) -> tab.setText(adapter.getTitle(position))).attach();
diff --git a/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java b/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java
index 356b9ae..7412c19 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java
@@ -16,14 +16,37 @@
package com.android.soundpicker;
-import androidx.fragment.app.Fragment;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.lifecycle.ViewModelProvider;
+
+import org.jetbrains.annotations.NotNull;
/**
- * A fragment that will display a picker used to select vibration.
+ * A fragment that displays a picker used to select vibration or silent (no vibration).
*/
-public class VibrationPickerFragment extends Fragment {
+public class VibrationPickerFragment extends BasePickerFragment {
- public VibrationPickerFragment() {
- super(R.layout.fragment_vibration_picker);
+ @Override
+ public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
+ mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get(
+ RingtonePickerViewModel.class);
+ super.onViewCreated(view, savedInstanceState);
+ }
+
+ @Override
+ protected RingtoneListHandler getRingtoneListHandler() {
+ return mRingtonePickerViewModel.getVibrationListHandler();
+ }
+
+ @Override
+ protected void addRingtoneAsync() {
+ // no-op
+ }
+
+ @Override
+ protected void addNewRingtoneItem() {
+ // no-op
}
}
diff --git a/packages/SoundPicker/tests/Android.bp b/packages/SoundPicker/tests/Android.bp
index dcd7b98..c38426f 100644
--- a/packages/SoundPicker/tests/Android.bp
+++ b/packages/SoundPicker/tests/Android.bp
@@ -27,6 +27,7 @@
"androidx.test.core",
"androidx.test.rules",
"androidx.test.ext.junit",
+ "androidx.test.ext.truth",
"mockito-target-minus-junit4",
"guava-android-testlib",
"SoundPickerLib",
diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java b/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
new file mode 100644
index 0000000..80e71e200
--- /dev/null
+++ b/packages/SoundPicker/tests/src/com/android/soundpicker/RingtoneListHandlerTest.java
@@ -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.soundpicker;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.database.Cursor;
+import android.media.RingtoneManager;
+import android.net.Uri;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class RingtoneListHandlerTest {
+
+ private static final Uri DEFAULT_URI = Uri.parse("media://custom/ringtone/default_uri");
+ private static final Uri RINGTONE_URI = Uri.parse("media://custom/ringtone/uri");
+ private static final int SILENT_RINGTONE_POSITION = 0;
+ private static final int DEFAULT_RINGTONE_POSITION = 1;
+ private static final int RINGTONE_POSITION = 2;
+
+ @Mock
+ private RingtoneManager mMockRingtoneManager;
+ @Mock
+ private Cursor mMockCursor;
+
+ private RingtoneListHandler mRingtoneListHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ RingtoneListHandler.Config mRingtoneListConfig = createRingtoneListConfig();
+
+ mRingtoneListHandler = new RingtoneListHandler();
+
+ // Add silent and default options to the list.
+ mRingtoneListHandler.addSilentItem();
+ mRingtoneListHandler.addDefaultItem();
+
+ mRingtoneListHandler.init(mRingtoneListConfig, mMockRingtoneManager, mMockCursor);
+ }
+
+ @Test
+ public void testGetRingtoneCursor_returnsTheCorrectRingtoneCursor() {
+ assertThat(mRingtoneListHandler.getRingtoneCursor()).isEqualTo(mMockCursor);
+ }
+
+ @Test
+ public void testGetRingtoneUri_returnsTheCorrectRingtoneUri() {
+ Uri expectedUri = RINGTONE_URI;
+ when(mMockRingtoneManager.getRingtoneUri(eq(0))).thenReturn(expectedUri);
+
+ // Request 3rd item from list.
+ Uri actualUri = mRingtoneListHandler.getRingtoneUri(RINGTONE_POSITION);
+ assertThat(actualUri).isEqualTo(expectedUri);
+ }
+
+ @Test
+ public void testGetRingtoneUri_withSelectedItemUnknown_returnsTheCorrectRingtoneUri() {
+ Uri uri = mRingtoneListHandler.getRingtoneUri(RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+ assertThat(uri).isEqualTo(RingtoneListHandler.SILENT_URI);
+ }
+
+ @Test
+ public void testGetRingtoneUri_withSelectedItemDefaultPosition_returnsTheCorrectRingtoneUri() {
+ Uri actualUri = mRingtoneListHandler.getRingtoneUri(DEFAULT_RINGTONE_POSITION);
+ assertThat(actualUri).isEqualTo(DEFAULT_URI);
+ }
+
+ @Test
+ public void testGetRingtoneUri_withSelectedItemSilentPosition_returnsTheCorrectRingtoneUri() {
+ Uri uri = mRingtoneListHandler.getRingtoneUri(SILENT_RINGTONE_POSITION);
+ assertThat(uri).isEqualTo(RingtoneListHandler.SILENT_URI);
+ }
+
+ @Test
+ public void testGetCurrentlySelectedRingtoneUri_returnsTheCorrectRingtoneUri() {
+ mRingtoneListHandler.setSelectedItemPosition(RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+ Uri actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
+ assertThat(actualUri).isEqualTo(RingtoneListHandler.SILENT_URI);
+
+ mRingtoneListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
+ actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
+ assertThat(actualUri).isEqualTo(DEFAULT_URI);
+
+ mRingtoneListHandler.setSelectedItemPosition(SILENT_RINGTONE_POSITION);
+ actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
+ assertThat(actualUri).isEqualTo(RingtoneListHandler.SILENT_URI);
+
+ when(mMockRingtoneManager.getRingtoneUri(eq(0))).thenReturn(RINGTONE_URI);
+ mRingtoneListHandler.setSelectedItemPosition(RINGTONE_POSITION);
+ actualUri = mRingtoneListHandler.getSelectedRingtoneUri();
+ assertThat(actualUri).isEqualTo(RINGTONE_URI);
+ }
+
+ @Test
+ public void testGetRingtonePosition_returnsTheCorrectRingtonePosition() {
+ when(mMockRingtoneManager.getRingtonePosition(RINGTONE_URI)).thenReturn(0);
+
+ int actualPosition = mRingtoneListHandler.getRingtonePosition(RINGTONE_URI);
+
+ assertThat(actualPosition).isEqualTo(RINGTONE_POSITION);
+
+ }
+
+ @Test
+ public void testFixedItems_onlyAddsItemsOnceAndInOrder() {
+ // Clear fixed items before testing the add methods.
+ mRingtoneListHandler.resetFixedItems();
+
+ assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
+ RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+ assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
+ RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+
+ mRingtoneListHandler.addSilentItem();
+ mRingtoneListHandler.addDefaultItem();
+ mRingtoneListHandler.addSilentItem();
+ mRingtoneListHandler.addDefaultItem();
+
+ assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
+ SILENT_RINGTONE_POSITION);
+ assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
+ DEFAULT_RINGTONE_POSITION);
+ }
+
+ @Test
+ public void testResetFixedItems_resetsSilentAndDefaultItemPositions() {
+ assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
+ SILENT_RINGTONE_POSITION);
+ assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
+ DEFAULT_RINGTONE_POSITION);
+
+ mRingtoneListHandler.resetFixedItems();
+
+ assertThat(mRingtoneListHandler.getSilentItemPosition()).isEqualTo(
+ RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+ assertThat(mRingtoneListHandler.getDefaultItemPosition()).isEqualTo(
+ RingtoneListHandler.ITEM_POSITION_UNKNOWN);
+ }
+
+ private RingtoneListHandler.Config createRingtoneListConfig() {
+ return new RingtoneListHandler.Config(/* hasDefaultItem= */ true,
+ /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true,
+ /* existingUri= */ DEFAULT_URI);
+ }
+}
diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java b/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
index 659aae8..cde6c76 100644
--- a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
+++ b/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java
@@ -17,12 +17,14 @@
package com.android.soundpicker;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
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.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -54,13 +56,10 @@
@RunWith(AndroidJUnit4.class)
public class RingtonePickerViewModelTest {
- private static final Uri DEFAULT_URI = Uri.parse("https://www.google.com/login.html");
+ private static final Uri DEFAULT_URI = Uri.parse("media://custom/ringtone/default_uri");
+ private static final Uri RINGTONE_URI = Uri.parse("media://custom/ringtone/uri");
private static final int RINGTONE_TYPE_UNKNOWN = -1;
- private static final int POS_UNKNOWN = -1;
- private static final int NO_ATTRIBUTES_FLAGS = 0;
- private static final int SILENT_RINGTONE_POSITION = 0;
private static final int DEFAULT_RINGTONE_POSITION = 1;
- private static final int RINGTONE_POSITION = 2;
@Mock
private RingtoneManagerFactory mMockRingtoneManagerFactory;
@@ -69,35 +68,54 @@
@Mock
private RingtoneManager mMockRingtoneManager;
@Mock
- private Cursor mMockCursor;
- @Mock
private ListeningExecutorServiceFactory mMockListeningExecutorServiceFactory;
+ @Mock
+ private Cursor mMockCursor;
+ private RingtoneListHandler mSoundListHandler;
+ private RingtoneListHandler mVibrationListHandler;
private ExecutorService mMainThreadExecutor;
private ListeningExecutorService mBackgroundThreadExecutor;
private Ringtone mMockDefaultRingtone;
private Ringtone mMockRingtone;
private RingtonePickerViewModel mViewModel;
+ private RingtoneListHandler.Config mSoundListConfig;
+ private RingtoneListHandler.Config mVibrationListConfig;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mMockRingtoneManagerFactory.create()).thenReturn(mMockRingtoneManager);
+ mSoundListHandler = new RingtoneListHandler();
+ mVibrationListHandler = new RingtoneListHandler();
+ mSoundListConfig = createRingtoneListConfig();
+ mVibrationListConfig = createRingtoneListConfig();
mMockDefaultRingtone = createMockRingtone();
mMockRingtone = createMockRingtone();
- when(mMockRingtoneFactory.create(DEFAULT_URI)).thenReturn(mMockDefaultRingtone);
- when(mMockRingtoneManager.getRingtone(anyInt())).thenReturn(mMockRingtone);
+ when(mMockRingtoneManagerFactory.create()).thenReturn(mMockRingtoneManager);
+ when(mMockRingtoneFactory.create(DEFAULT_URI,
+ AudioAttributes.FLAG_AUDIBILITY_ENFORCED)).thenReturn(mMockDefaultRingtone);
+ when(mMockRingtoneManager.getRingtoneUri(anyInt())).thenReturn(RINGTONE_URI);
+ when(mMockRingtoneManager.getCursor()).thenReturn(mMockCursor);
mMainThreadExecutor = TestingExecutors.sameThreadScheduledExecutor();
mBackgroundThreadExecutor = TestingExecutors.sameThreadScheduledExecutor();
when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
mBackgroundThreadExecutor);
mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
- mMockListeningExecutorServiceFactory);
- mViewModel.setSilentItemPosition(SILENT_RINGTONE_POSITION);
- mViewModel.setDefaultItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.setSampleItemPosition(RINGTONE_POSITION);
+ mMockListeningExecutorServiceFactory, mSoundListHandler,
+ mVibrationListHandler);
+
+ // Add silent and default options to the sound list.
+ mSoundListHandler.addSilentItem();
+ mSoundListHandler.addDefaultItem();
+
+ // Add silent and default options to the vibration list.
+ mVibrationListHandler.addSilentItem();
+ mVibrationListHandler.addDefaultItem();
+
+ mSoundListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
+ mVibrationListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
}
@After
@@ -112,59 +130,86 @@
@Test
public void testInitRingtoneManager_whenTypeIsUnknown_createManagerButDoNotSetType() {
- mViewModel.init(createPickerConfig(RINGTONE_TYPE_UNKNOWN));
+ mViewModel.init(createPickerConfig(RINGTONE_TYPE_UNKNOWN), mSoundListConfig,
+ mVibrationListConfig);
verify(mMockRingtoneManagerFactory).create();
verify(mMockRingtoneManager, never()).setType(anyInt());
+ assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+ assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
}
@Test
public void testInitRingtoneManager_whenTypeIsNotUnknown_createManagerAndSetType() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION));
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION), mSoundListConfig,
+ mVibrationListConfig);
verify(mMockRingtoneManagerFactory).create();
verify(mMockRingtoneManager).setType(RingtoneManager.TYPE_NOTIFICATION);
+ assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+ assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
+ }
+
+ @Test
+ public void testInitRingtoneManager_bothListConfigsAreNull_onlyRecreateRingtoneManager() {
+ mViewModel.init(
+ createPickerConfig(RingtoneManager.TYPE_NOTIFICATION),
+ /* soundListConfig= */ null, /* vibrationListConfig= */ null);
+
+ verify(mMockRingtoneManagerFactory).create();
+ verify(mMockRingtoneManager).setType(RingtoneManager.TYPE_NOTIFICATION);
+ assertNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+ assertNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
+ }
+
+ @Test
+ public void testReinitialize_bothListConfigsInitialized_recreateManagerAndReinitHandlers() {
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION), mSoundListConfig,
+ mVibrationListConfig);
+ mViewModel.reinit();
+
+ verify(mMockRingtoneManagerFactory, times(2)).create();
+ verify(mMockRingtoneManager, times(2)).setType(RingtoneManager.TYPE_NOTIFICATION);
+ assertNotNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+ assertNotNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
+ }
+
+ @Test
+ public void testReinitialize_bothListConfigsAlreadyNull_onlyRecreateRingtoneManager() {
+ mViewModel.init(
+ createPickerConfig(RingtoneManager.TYPE_NOTIFICATION),
+ /* soundListConfig= */ null, /* vibrationListConfig= */ null);
+ mViewModel.reinit();
+
+ verify(mMockRingtoneManagerFactory, times(2)).create();
+ verify(mMockRingtoneManager, times(2)).setType(RingtoneManager.TYPE_NOTIFICATION);
+ assertNull(mViewModel.getSoundListHandler().getRingtoneListConfig());
+ assertNull(mViewModel.getVibrationListHandler().getRingtoneListConfig());
}
@Test
public void testGetStreamType_returnsTheCorrectStreamType() {
when(mMockRingtoneManager.inferStreamType()).thenReturn(AudioManager.STREAM_ALARM);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
assertEquals(mViewModel.getRingtoneStreamType(), AudioManager.STREAM_ALARM);
}
@Test
- public void testGetRingtoneCursor_returnsTheCorrectRingtoneCursor() {
- when(mMockRingtoneManager.getCursor()).thenReturn(mMockCursor);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- assertEquals(mViewModel.getRingtoneCursor(), mMockCursor);
- }
-
- @Test
- public void testGetRingtoneUri_returnsTheCorrectRingtoneUri() {
- Uri expectedUri = DEFAULT_URI;
- when(mMockRingtoneManager.getRingtoneUri(anyInt())).thenReturn(expectedUri);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- Uri actualUri = mViewModel.getRingtoneUri(DEFAULT_RINGTONE_POSITION);
- assertEquals(actualUri, expectedUri);
- }
-
- @Test
public void testOnPause_withChangingConfigurationTrue_doNotStopPlayingRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
- verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+ mViewModel.playRingtone();
+ verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
mViewModel.onPause(/* isChangingConfigurations= */ true);
- verify(mMockRingtone, never()).stop();
+ verify(mMockDefaultRingtone, never()).stop();
}
@Test
public void testOnPause_withChangingConfigurationFalse_stopPlayingRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+ mViewModel.playRingtone();
verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
mViewModel.onPause(/* isChangingConfigurations= */ false);
verify(mMockDefaultRingtone).stop();
@@ -172,24 +217,24 @@
@Test
public void testOnViewModelRecreated_previousRingtoneCanStillBeStopped() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSampleItemPosition(RINGTONE_POSITION);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
Ringtone mockRingtone1 = createMockRingtone();
Ringtone mockRingtone2 = createMockRingtone();
- when(mMockRingtoneManager.getRingtone(anyInt())).thenReturn(mockRingtone1, mockRingtone2);
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
+
+ when(mMockRingtoneFactory.create(any(), anyInt())).thenReturn(mockRingtone1, mockRingtone2);
+ mViewModel.playRingtone();
verifyRingtonePlayCalledAndMockPlayingState(mockRingtone1);
// Fake a scenario where the activity is destroyed and recreated due to a config change.
// This will result in a new view model getting created.
mViewModel.onStop(/* isChangingConfigurations= */ true);
verify(mockRingtone1, never()).stop();
mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
- mMockListeningExecutorServiceFactory);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSampleItemPosition(RINGTONE_POSITION);
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
+ mMockListeningExecutorServiceFactory, mSoundListHandler,
+ mVibrationListHandler);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+ mViewModel.playRingtone();
verifyRingtonePlayCalledAndMockPlayingState(mockRingtone2);
verify(mockRingtone1).stop();
verify(mockRingtone2, never()).stop();
@@ -197,10 +242,9 @@
@Test
public void testOnStop_withChangingConfigurationTrueAndDefaultRingtonePlaying_saveRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+ mViewModel.playRingtone();
verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
mViewModel.onStop(/* isChangingConfigurations= */ true);
assertEquals(RingtonePickerViewModel.sPlayingRingtone, mMockDefaultRingtone);
@@ -208,239 +252,66 @@
@Test
public void testOnStop_withChangingConfigurationTrueAndCurrentRingtonePlaying_saveRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSampleItemPosition(RINGTONE_POSITION);
- mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
- verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+ mViewModel.playRingtone();
+ verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
mViewModel.onStop(/* isChangingConfigurations= */ true);
- assertEquals(RingtonePickerViewModel.sPlayingRingtone, mMockRingtone);
+ assertEquals(RingtonePickerViewModel.sPlayingRingtone, mMockDefaultRingtone);
}
@Test
public void testOnStop_withChangingConfigurationTrueAndNoPlayingRingtone_saveNothing() {
- mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION);
mViewModel.onStop(/* isChangingConfigurations= */ true);
assertNull(RingtonePickerViewModel.sPlayingRingtone);
}
@Test
public void testOnStop_withChangingConfigurationFalse_stopPlayingRingtone() {
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+
+ mViewModel.playRingtone();
verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
mViewModel.onStop(/* isChangingConfigurations= */ false);
verify(mMockDefaultRingtone).stop();
}
@Test
- public void testGetCurrentlySelectedRingtoneUri_checkedItemIsUnknown_returnsNull() {
- mViewModel.setSelectedItemPosition(POS_UNKNOWN);
- Uri uri = mViewModel.getCurrentlySelectedRingtoneUri();
- assertNull(uri);
+ public void testGetCurrentlySelectedRingtoneUri_returnsTheCorrectRingtoneUri() {
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+
+ assertEquals(DEFAULT_URI, mViewModel.getSelectedRingtoneUri());
}
@Test
- public void testGetCurrentlySelectedRingtoneUri_checkedItemIsDefaultPos_returnsDefaultUri() {
- Uri expectedUri = DEFAULT_URI;
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
- Uri actualUri = mViewModel.getCurrentlySelectedRingtoneUri();
- assertEquals(actualUri, expectedUri);
- }
+ public void testPlayRingtone_playTheCorrectRingtone() {
+ mSoundListHandler.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
- @Test
- public void testGetCurrentlySelectedRingtoneUri_checkedItemIsSilentPos_returnsNull() {
- mViewModel.setSelectedItemPosition(SILENT_RINGTONE_POSITION);
- Uri uri = mViewModel.getCurrentlySelectedRingtoneUri();
- assertNull(uri);
- }
-
- @Test
- public void testCancelPendingAsyncTasks_correctlyCancelsPendingTasks()
- throws IOException {
- FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
- when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
- TestingExecutors.noOpScheduledExecutor());
- when(mMockRingtoneManager.addCustomExternalRingtone(DEFAULT_URI,
- RingtoneManager.TYPE_NOTIFICATION)).thenReturn(DEFAULT_URI);
- mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
- mMockListeningExecutorServiceFactory);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.addRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION, mockCallback,
- mMainThreadExecutor);
- verify(mockCallback, never()).onFailure(any());
- // Calling cancelPendingAsyncTasks should cancel the pending task. Cancelling an async
- // task invokes the onFailure method in the callable.
- mViewModel.cancelPendingAsyncTasks();
- verify(mockCallback).onFailure(any());
- verify(mockCallback, never()).onSuccess(any());
-
- }
-
- @Test
- public void testAddRingtoneAsync_cancelPreviousTaskBeforeStartingNewOne()
- throws IOException {
- FutureCallback<Uri> mockCallback1 = mock(FutureCallback.class);
- FutureCallback<Uri> mockCallback2 = mock(FutureCallback.class);
-
- when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
- TestingExecutors.noOpScheduledExecutor());
- when(mMockRingtoneManager.addCustomExternalRingtone(DEFAULT_URI,
- RingtoneManager.TYPE_NOTIFICATION)).thenReturn(DEFAULT_URI);
- mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
- mMockListeningExecutorServiceFactory);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.addRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION, mockCallback1,
- mMainThreadExecutor);
- verify(mockCallback1, never()).onFailure(any());
- // We call addRingtoneAsync again to cancel the previous task and start a new one.
- // Cancelling an async task invokes the onFailure method in the callable.
- mViewModel.addRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION, mockCallback2,
- mMainThreadExecutor);
- verify(mockCallback1).onFailure(any());
- verify(mockCallback1, never()).onSuccess(any());
- verifyNoMoreInteractions(mockCallback2);
- }
-
- @Test
- public void testAddRingtoneAsync_whenAddRingtoneIsSuccessful_successCallbackIsInvoked()
- throws IOException {
- Uri expectedUri = DEFAULT_URI;
- FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
- when(mMockRingtoneManager.addCustomExternalRingtone(DEFAULT_URI,
- RingtoneManager.TYPE_NOTIFICATION)).thenReturn(expectedUri);
-
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.addRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION, mockCallback,
- mMainThreadExecutor);
-
- verify(mMockRingtoneManager).addCustomExternalRingtone(DEFAULT_URI,
- RingtoneManager.TYPE_NOTIFICATION);
- verify(mockCallback).onSuccess(expectedUri);
- verify(mockCallback, never()).onFailure(any());
- }
-
- @Test
- public void testAddRingtoneAsync_whenAddRingtoneFailed_failureCallbackIsInvoked()
- throws IOException {
- FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
-
- when(mMockRingtoneManager.addCustomExternalRingtone(any(), anyInt())).thenThrow(
- IOException.class);
-
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.addRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION, mockCallback,
- mMainThreadExecutor);
-
- verify(mockCallback).onFailure(any(IOException.class));
- verify(mockCallback, never()).onSuccess(any());
- }
-
- @Test
- public void testGetCurrentlySelectedRingtoneUri_checkedItemRingtonePos_returnsTheCorrectUri() {
- Uri expectedUri = DEFAULT_URI;
- when(mMockRingtoneManager.getRingtoneUri(RINGTONE_POSITION)).thenReturn(expectedUri);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.setSelectedItemPosition(RINGTONE_POSITION);
- Uri actualUri = mViewModel.getCurrentlySelectedRingtoneUri();
-
- verify(mMockRingtoneManager).getRingtoneUri(RINGTONE_POSITION);
- assertEquals(actualUri, expectedUri);
+ mViewModel.playRingtone();
+ verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
}
@Test
public void testPlayRingtone_stopsPreviouslyRunningRingtone() {
// Start playing the first ringtone
- mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+ mViewModel.playRingtone();
verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
// Start playing the second ringtone
- mViewModel.setSampleItemPosition(RINGTONE_POSITION);
- mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
+ when(mMockRingtoneFactory.create(DEFAULT_URI,
+ AudioAttributes.FLAG_AUDIBILITY_ENFORCED)).thenReturn(mMockRingtone);
+ mViewModel.playRingtone();
verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone);
verify(mMockDefaultRingtone).stop();
}
@Test
- public void testPlayRingtone_samplePosEqualToSilentPos_onlyStopPlayingRingtone() {
- mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
- verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-
- mViewModel.setSampleItemPosition(SILENT_RINGTONE_POSITION);
- mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
- verify(mMockDefaultRingtone).stop();
- // This will be invoked on the first ringtone we play, but not on the second one.
- verify(mMockRingtoneFactory).create(any());
- verify(mMockRingtoneManager, never()).getRingtone(anyInt());
- verify(mMockRingtone, never()).play();
-
- }
-
- @Test
- public void testPlayRingtone_samplePosEqualToDefaultPos_playDefaultRingtone() {
- mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
-
- when(mMockRingtoneManager.inferStreamType()).thenReturn(AudioManager.STREAM_ALARM);
-
- mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
- verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone);
-
- verify(mMockDefaultRingtone).setStreamType(AudioManager.STREAM_ALARM);
- verify(mMockDefaultRingtone).play();
- }
-
- @Test
- public void testPlayRingtone_samplePosNotEqualToDefaultPos_playRingtone() {
- mViewModel.setSampleItemPosition(RINGTONE_POSITION);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
-
- mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED);
- verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone);
- verify(mMockRingtone).setAudioAttributes(
- audioAttributes(AudioAttributes.USAGE_NOTIFICATION_RINGTONE,
- AudioAttributes.FLAG_AUDIBILITY_ENFORCED));
- verify(mMockRingtone).play();
- }
-
- @Test
- public void testPlayRingtone_withNoAttributeFlags_doNotUpdateRingtoneAttributesFlags() {
- mViewModel.setSampleItemPosition(RINGTONE_POSITION);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
-
- mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI,
- NO_ATTRIBUTES_FLAGS);
- verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone);
- verify(mMockRingtone, never()).setAudioAttributes(any());
- verify(mMockRingtone).play();
- }
-
- @Test
- public void testGetRingtonePosition_returnsTheCorrectRingtonePosition() {
- int expectedPosition = 1;
- when(mMockRingtoneManager.getRingtonePosition(any())).thenReturn(expectedPosition);
- mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE));
- int actualPosition = mViewModel.getRingtonePosition(DEFAULT_URI);
-
- assertEquals(actualPosition, expectedPosition);
- }
-
- @Test
public void testDefaultItemUri_withNotificationIntent_returnDefaultNotificationUri() {
Uri uri = RingtonePickerViewModel.getDefaultItemUriByType(
RingtoneManager.TYPE_NOTIFICATION);
@@ -537,6 +408,89 @@
assertEquals(R.string.ringtone_default, defaultRingtoneItemText);
}
+ @Test
+ public void testCancelPendingAsyncTasks_correctlyCancelsPendingTasks()
+ throws IOException {
+ FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
+
+ when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
+ TestingExecutors.noOpScheduledExecutor());
+
+ mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
+ mMockListeningExecutorServiceFactory, mSoundListHandler,
+ mVibrationListHandler);
+ mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+ mockCallback, mMainThreadExecutor);
+ verify(mockCallback, never()).onFailure(any());
+ // Calling cancelPendingAsyncTasks should cancel the pending task. Cancelling an async
+ // task invokes the onFailure method in the callable.
+ mViewModel.cancelPendingAsyncTasks();
+ verify(mockCallback).onFailure(any());
+ verify(mockCallback, never()).onSuccess(any());
+
+ }
+
+ @Test
+ public void testAddRingtoneAsync_cancelPreviousTaskBeforeStartingNewOne()
+ throws IOException {
+ FutureCallback<Uri> mockCallback1 = mock(FutureCallback.class);
+ FutureCallback<Uri> mockCallback2 = mock(FutureCallback.class);
+
+ when(mMockListeningExecutorServiceFactory.createSingleThreadExecutor()).thenReturn(
+ TestingExecutors.noOpScheduledExecutor());
+
+ mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory,
+ mMockListeningExecutorServiceFactory, mSoundListHandler,
+ mVibrationListHandler);
+ mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+ mockCallback1, mMainThreadExecutor);
+ verify(mockCallback1, never()).onFailure(any());
+ // We call addRingtoneAsync again to cancel the previous task and start a new one.
+ // Cancelling an async task invokes the onFailure method in the callable.
+ mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+ mockCallback2, mMainThreadExecutor);
+ verify(mockCallback1).onFailure(any());
+ verify(mockCallback1, never()).onSuccess(any());
+ verifyNoMoreInteractions(mockCallback2);
+ }
+
+ @Test
+ public void testAddRingtoneAsync_whenAddRingtoneIsSuccessful_successCallbackIsInvoked()
+ throws IOException {
+ Uri expectedUri = DEFAULT_URI;
+ FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
+
+ when(mMockRingtoneManager.addCustomExternalRingtone(DEFAULT_URI,
+ RingtoneManager.TYPE_NOTIFICATION)).thenReturn(expectedUri);
+
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+
+ mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+ mockCallback, mMainThreadExecutor);
+
+ verify(mockCallback).onSuccess(expectedUri);
+ verify(mockCallback, never()).onFailure(any());
+ }
+
+ @Test
+ public void testAddRingtoneAsync_whenAddRingtoneFailed_failureCallbackIsInvoked()
+ throws IOException {
+ FutureCallback<Uri> mockCallback = mock(FutureCallback.class);
+
+ when(mMockRingtoneManager.addCustomExternalRingtone(any(), anyInt())).thenThrow(
+ IOException.class);
+
+ mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE), mSoundListConfig,
+ mVibrationListConfig);
+
+ mViewModel.addSoundRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION,
+ mockCallback, mMainThreadExecutor);
+
+ verify(mockCallback).onFailure(any(IOException.class));
+ verify(mockCallback, never()).onSuccess(any());
+ }
+
private Ringtone createMockRingtone() {
Ringtone mockRingtone = mock(Ringtone.class);
when(mockRingtone.getAudioAttributes()).thenReturn(
@@ -558,12 +512,23 @@
.build();
}
- private RingtonePickerViewModel.PickerConfig createPickerConfig(int ringtoneType) {
- return new RingtonePickerViewModel.PickerConfig("Phone ringtone", /* userId= */ 1,
- ringtoneType, /* hasDefaultItem= */ true,
- /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true,
- /* audioAttributesFlags= */0, /* existingUri= */ Uri.parse(""),
- /* showOkCancelButtons= */ true,
+ private RingtonePickerViewModel.Config createPickerConfig(int ringtoneType,
+ int audioAttributes) {
+ return new RingtonePickerViewModel.Config("Phone ringtone", /* userId= */ 1,
+ ringtoneType, /* showOkCancelButtons= */ true,
+ audioAttributes, RingtonePickerViewModel.PickerType.RINGTONE_PICKER);
+ }
+
+ private RingtonePickerViewModel.Config createPickerConfig(int ringtoneType) {
+ return new RingtonePickerViewModel.Config("Phone ringtone", /* userId= */ 1,
+ ringtoneType, /* showOkCancelButtons= */ true,
+ AudioAttributes.FLAG_AUDIBILITY_ENFORCED,
RingtonePickerViewModel.PickerType.RINGTONE_PICKER);
}
+
+ private RingtoneListHandler.Config createRingtoneListConfig() {
+ return new RingtoneListHandler.Config(/* hasDefaultItem= */ true,
+ /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true,
+ /* existingUri= */ Uri.parse(""));
+ }
}
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 5fdf354..bba926d 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -23,6 +23,7 @@
brycelee@google.com
brzezinski@google.com
caitlinshk@google.com
+cameronyee@google.com
chandruis@google.com
chrisgollner@google.com
cinek@google.com
@@ -44,6 +45,7 @@
jjaggi@google.com
jonmiranda@google.com
joshtrask@google.com
+juansmartinez@google.com
juliacr@google.com
juliatuttle@google.com
justinkoh@google.com
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 42a5c61..7a5a382 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -53,20 +53,6 @@
]
},
{
- "name": "SystemUIGoogleScreenshotTests",
- "options": [
- {
- "exclude-annotation": "org.junit.Ignore"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "android.platform.test.annotations.Postsubmit"
- }
- ]
- },
- {
// TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
"name": "SystemUIGoogleBiometricsScreenshotTests",
"options": [
@@ -145,21 +131,5 @@
}
]
}
- ],
- "postsubmit": [
- {
- "name": "SystemUIGoogleScreenshotTests",
- "options": [
- {
- "exclude-annotation": "org.junit.Ignore"
- },
- {
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
- "include-annotation": "android.platform.test.annotations.Postsubmit"
- }
- ]
- }
]
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 8e79e3c..38b99cc 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -70,6 +70,9 @@
* If a new layout change happens while an animation is already in progress, the animation
* is updated to continue from the current values to the new end state.
*
+ * A set of [excludedViews] can be passed. If any dependent view from [rootView] matches an
+ * entry in this set, changes to that view will not be animated.
+ *
* The animator continues to respond to layout changes until [stopAnimating] is called.
*
* Successive calls to this method override the previous settings ([interpolator] and
@@ -82,9 +85,16 @@
fun animate(
rootView: View,
interpolator: Interpolator = DEFAULT_INTERPOLATOR,
- duration: Long = DEFAULT_DURATION
+ duration: Long = DEFAULT_DURATION,
+ excludedViews: Set<View> = emptySet()
): Boolean {
- return animate(rootView, interpolator, duration, ephemeral = false)
+ return animate(
+ rootView,
+ interpolator,
+ duration,
+ ephemeral = false,
+ excludedViews = excludedViews
+ )
}
/**
@@ -95,16 +105,24 @@
fun animateNextUpdate(
rootView: View,
interpolator: Interpolator = DEFAULT_INTERPOLATOR,
- duration: Long = DEFAULT_DURATION
+ duration: Long = DEFAULT_DURATION,
+ excludedViews: Set<View> = emptySet()
): Boolean {
- return animate(rootView, interpolator, duration, ephemeral = true)
+ return animate(
+ rootView,
+ interpolator,
+ duration,
+ ephemeral = true,
+ excludedViews = excludedViews
+ )
}
private fun animate(
rootView: View,
interpolator: Interpolator,
duration: Long,
- ephemeral: Boolean
+ ephemeral: Boolean,
+ excludedViews: Set<View> = emptySet()
): Boolean {
if (
!occupiesSpace(
@@ -119,7 +137,7 @@
}
val listener = createUpdateListener(interpolator, duration, ephemeral)
- addListener(rootView, listener, recursive = true)
+ addListener(rootView, listener, recursive = true, excludedViews = excludedViews)
return true
}
@@ -921,8 +939,11 @@
private fun addListener(
view: View,
listener: View.OnLayoutChangeListener,
- recursive: Boolean = false
+ recursive: Boolean = false,
+ excludedViews: Set<View> = emptySet()
) {
+ if (excludedViews.contains(view)) return
+
// Make sure that only one listener is active at a time.
val previousListener = view.getTag(R.id.tag_layout_listener)
if (previousListener != null && previousListener is View.OnLayoutChangeListener) {
@@ -933,7 +954,12 @@
view.setTag(R.id.tag_layout_listener, listener)
if (view is ViewGroup && recursive) {
for (i in 0 until view.childCount) {
- addListener(view.getChildAt(i), listener, recursive = true)
+ addListener(
+ view.getChildAt(i),
+ listener,
+ recursive = true,
+ excludedViews = excludedViews
+ )
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index ca91b8a..38b751c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -17,15 +17,19 @@
package com.android.systemui.notifications.ui.composable
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
@Composable
@@ -34,10 +38,26 @@
) {
// TODO(b/272779828): implement.
Column(
- modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+ modifier =
+ modifier
+ .fillMaxWidth()
+ .defaultMinSize(minHeight = 300.dp)
+ .clip(RoundedCornerShape(32.dp))
+ .background(MaterialTheme.colorScheme.surface)
+ .padding(16.dp),
) {
- Text("Notifications", modifier = Modifier.align(Alignment.CenterHorizontally))
+ Text(
+ text = "Notifications",
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
Spacer(modifier = Modifier.weight(1f))
- Text("Shelf", modifier = Modifier.align(Alignment.CenterHorizontally))
+ Text(
+ text = "Shelf",
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = MaterialTheme.typography.titleSmall,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
index 665d6dd..1bb341c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
@@ -17,15 +17,19 @@
package com.android.systemui.qs.footer.ui.compose
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
@Composable
@@ -34,10 +38,26 @@
) {
// TODO(b/272780058): implement.
Column(
- modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp).padding(4.dp),
+ modifier =
+ modifier
+ .fillMaxWidth()
+ .defaultMinSize(minHeight = 300.dp)
+ .clip(RoundedCornerShape(32.dp))
+ .background(MaterialTheme.colorScheme.primary)
+ .padding(16.dp),
) {
- Text("Quick settings", modifier = Modifier.align(Alignment.CenterHorizontally))
+ Text(
+ text = "Quick settings",
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.onPrimary,
+ )
Spacer(modifier = Modifier.weight(1f))
- Text("QS footer actions", modifier = Modifier.align(Alignment.CenterHorizontally))
+ Text(
+ text = "QS footer actions",
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ style = MaterialTheme.typography.titleSmall,
+ color = MaterialTheme.colorScheme.onPrimary,
+ )
}
}
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 20e1751..27358f5 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
@@ -16,18 +16,19 @@
package com.android.systemui.shade.ui.composable
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.material3.Button
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -62,12 +63,7 @@
override fun Content(
containerName: String,
modifier: Modifier,
- ) {
- ShadeScene(
- viewModel = viewModel,
- modifier = modifier,
- )
- }
+ ) = ShadeScene(viewModel, modifier)
private fun destinationScenes(
up: SceneKey,
@@ -84,23 +80,15 @@
viewModel: ShadeSceneViewModel,
modifier: Modifier = Modifier,
) {
- // TODO(b/280887022): implement the real UI.
-
- Box(modifier = modifier) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.align(Alignment.Center)
- ) {
- Text("Shade", style = MaterialTheme.typography.headlineMedium)
- Row(
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- ) {
- Button(
- onClick = { viewModel.onContentClicked() },
- ) {
- Text("Open some content")
- }
- }
- }
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ modifier =
+ Modifier.fillMaxSize()
+ .clickable(onClick = { viewModel.onContentClicked() })
+ .padding(horizontal = 16.dp, vertical = 48.dp)
+ ) {
+ QuickSettings(modifier = modifier.height(160.dp))
+ Notifications(modifier = modifier.weight(1f))
}
}
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 18753fd..006fc09 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -422,9 +422,6 @@
-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallFlags.kt
-packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt
-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/ConnectivitySlots.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
-packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
-packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
-packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -696,7 +693,6 @@
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index c0b69c1..25f77ea 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -172,7 +172,6 @@
public String expandedAccessibilityClassName;
public SlashState slash;
public boolean handlesLongClick = true;
- public boolean showRippleEffect = true;
@Nullable
public Drawable sideViewCustomDrawable;
public String spec;
@@ -217,7 +216,6 @@
|| !Objects.equals(other.dualTarget, dualTarget)
|| !Objects.equals(other.slash, slash)
|| !Objects.equals(other.handlesLongClick, handlesLongClick)
- || !Objects.equals(other.showRippleEffect, showRippleEffect)
|| !Objects.equals(other.sideViewCustomDrawable, sideViewCustomDrawable);
other.spec = spec;
other.icon = icon;
@@ -234,7 +232,6 @@
other.isTransient = isTransient;
other.slash = slash != null ? slash.copy() : null;
other.handlesLongClick = handlesLongClick;
- other.showRippleEffect = showRippleEffect;
other.sideViewCustomDrawable = sideViewCustomDrawable;
return changed;
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
index 57b3acd..66c54f2 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
@@ -21,6 +21,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyguard_lock_padding"
+ android:importantForAccessibility="no"
android:ellipsize="marquee"
android:focusable="true"
android:gravity="center"
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
deleted file mode 100644
index 9304ff7..0000000
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ /dev/null
@@ -1,136 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2019 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<!-- Layout for media recommendations inside QSPanel carousel -->
-<!-- See media_recommendation_expanded.xml and media_recommendation_collapsed.xml for the
- constraints. -->
-<com.android.systemui.util.animation.TransitionLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/media_recommendations"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:forceHasOverlappingRendering="false"
- android:background="@drawable/qs_media_background"
- android:theme="@style/MediaPlayer">
-
- <!-- This view just ensures the full media player is a certain height. -->
- <View
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_expanded" />
-
- <com.android.internal.widget.CachingIconView
- android:id="@+id/recommendation_card_icon"
- android:layout_width="@dimen/qs_media_app_icon_size"
- android:layout_height="@dimen/qs_media_app_icon_size"
- android:minWidth="@dimen/qs_media_app_icon_size"
- android:minHeight="@dimen/qs_media_app_icon_size"
- android:layout_marginStart="@dimen/qs_media_padding"
- android:layout_marginTop="@dimen/qs_media_rec_icon_top_margin"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
- <FrameLayout
- android:id="@+id/media_cover1_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- >
- <ImageView
- android:id="@+id/media_cover1"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:minWidth="@dimen/qs_media_rec_album_size"
- android:minHeight="@dimen/qs_media_rec_album_size"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- android:adjustViewBounds="true"
- android:background="@drawable/bg_smartspace_media_item"
- style="@style/MediaPlayer.Recommendation.Album"
- android:clipToOutline="true"
- android:scaleType="centerCrop"/>
- </FrameLayout>
-
- <TextView
- android:id="@+id/media_title1"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- />
-
- <TextView
- android:id="@+id/media_subtitle1"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- />
-
- <FrameLayout
- android:id="@+id/media_cover2_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- >
- <ImageView
- android:id="@+id/media_cover2"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:minWidth="@dimen/qs_media_rec_album_size"
- android:minHeight="@dimen/qs_media_rec_album_size"
- android:adjustViewBounds="true"
- android:background="@drawable/bg_smartspace_media_item"
- style="@style/MediaPlayer.Recommendation.Album"
- android:clipToOutline="true"
- android:scaleType="centerCrop"/>
- </FrameLayout>
-
- <TextView
- android:id="@+id/media_title2"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- />
-
- <TextView
- android:id="@+id/media_subtitle2"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- />
-
- <FrameLayout
- android:id="@+id/media_cover3_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- >
- <ImageView
- android:id="@+id/media_cover3"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:minWidth="@dimen/qs_media_rec_album_size"
- android:minHeight="@dimen/qs_media_rec_album_size"
- android:adjustViewBounds="true"
- android:background="@drawable/bg_smartspace_media_item"
- style="@style/MediaPlayer.Recommendation.Album"
- android:clipToOutline="true"
- android:scaleType="centerCrop"/>
- </FrameLayout>
-
- <TextView
- android:id="@+id/media_title3"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- />
-
- <TextView
- android:id="@+id/media_subtitle3"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- />
-
- <include
- layout="@layout/media_long_press_menu" />
-
-</com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 2fde947..a336252 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -76,6 +76,13 @@
android:layout_height="match_parent"
android:visibility="invisible" />
+ <!-- Shared container for the notification stack. Can be positioned by either
+ the keyguard_root_view or notification_panel -->
+ <com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+ android:id="@+id/shared_notification_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<include layout="@layout/brightness_mirror_container" />
<com.android.systemui.scrim.ScrimView
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 5bd2184..e0c25e3 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tik om te bekyk"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Kon nie skermopname stoor nie"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Kon nie skermopname begin nie"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Bekyk tans volskerm"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Swiep van bo af as jy wil uitgaan."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Het dit"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Terug"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Tuis"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Kieslys"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Wanneer jy deel, opneem of uitsaai, het Android toegang tot enigiets wat op jou skerm sigbaar is of op jou toestel gespeel word. Wees dus versigtig met dinge soos wagwoorde, betalingbesonderhede, boodskappe, foto’s, en oudio en video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Wanneer jy ’n app deel, opneem of uitsaai, het Android toegang tot enigiets wat in daardie app gewys of gespeel word. Wees dus versigtig met dinge soos wagwoorde, betalingbesonderhede, boodskappe, foto’s, en oudio en video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Begin"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Deur jou IT-admin geblokkeer"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skermskote is deur toestelbeleid gedeaktiveer"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Vee alles uit"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index f2eb766..b8ac5fc 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ለመመልከት መታ ያድርጉ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"የማያ ገጽ ቀረጻን ማስቀመጥ ላይ ስህተት"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"የማያ ገፅ ቀረጻን መጀመር ላይ ስህተት"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ሙሉ ገፅ በማሳየት ላይ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ለመውጣት፣ ከላይ ወደታች ጠረግ ያድርጉ።"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ገባኝ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ተመለስ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"መነሻ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ምናሌ"</string>
@@ -412,6 +415,14 @@
<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>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ጀምር"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"በእርስዎ የአይቲ አስተዳዳሪ ታግዷል"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"የማያ ገፅ ቀረጻ በመሣሪያ መመሪያ ተሰናክሏል"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ሁሉንም አጽዳ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 9a74144..e465773 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"انقر لعرض التسجيل."</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"حدث خطأ أثناء حفظ تسجيل محتوى الشاشة."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"حدث خطأ في بدء تسجيل الشاشة"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"جارٍ العرض بملء الشاشة"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"للخروج، مرر بسرعة من أعلى إلى أسفل."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"حسنًا"</string>
<string name="accessibility_back" msgid="6530104400086152611">"رجوع"</string>
<string name="accessibility_home" msgid="5430449841237966217">"الرئيسية"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"القائمة"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"أثناء المشاركة أو التسجيل أو البثّ، يمكن لنظام Android الوصول إلى كل المحتوى المعروض على شاشتك أو الذي يتم تشغيله على جهازك، لذا يُرجى توخي الحذر بشأن المعلومات، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"أثناء مشاركة محتوى تطبيق أو تسجيله أو بثّه، يمكن لنظام Android الوصول إلى كل المحتوى المعروض أو الذي يتم تشغيله في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن المعلومات مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"بدء"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"حظر مشرف تكنولوجيا المعلومات هذه الميزة"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ميزة \"تصوير الشاشة\" غير مفعَّلة بسبب سياسة الجهاز."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"محو الكل"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 856989c..372cab8 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"চাবলৈ টিপক"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ৰেকৰ্ড কৰা স্ক্ৰীন ছেভ কৰোঁতে আসোঁৱাহ হৈছে"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"স্ক্রীন ৰেকৰ্ড কৰা আৰম্ভ কৰোঁতে আসোঁৱাহ হৈছে"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"পূৰ্ণ স্ক্ৰীনত চাই আছে"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"বাহিৰ হ’বলৈ ওপৰৰ পৰা তললৈ ছোৱাইপ কৰক।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"বুজি পালোঁ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"উভতি যাওক"</string>
<string name="accessibility_home" msgid="5430449841237966217">"গৃহ পৃষ্ঠাৰ বুটাম"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"মেনু"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান হোৱা যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা, ফট’ আৰু অডিঅ’ আৰু ভিডিঅ’ৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ Androidৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা, ফট’ আৰু অডিঅ’ আৰু ভিডিঅ’ৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"আৰম্ভ কৰক"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"আপোনাৰ আইটি প্ৰশাসকে অৱৰোধ কৰিছে"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ডিভাইচ সম্পৰ্কীয় নীতিয়ে স্ক্ৰীন কেপশ্বাৰ কৰাটো অক্ষম কৰিছে"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 1c14b04e..ba87178 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Baxmaq üçün toxunun"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran çəkimini yadda saxlayarkən xəta oldu"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ekranın yazılması ilə bağlı xəta"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Tam ekrana baxış"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Çıxmaq üçün yuxarıdan aşağı sürüşdürün."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Anladım"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Geri"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ana səhifə"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Paylaşım, qeydəalma və ya yayım zamanı Android-in ekranda görünən, yaxud cihazda oxudulan məlumatlara girişi olur. Parol, ödəniş detalları, mesaj, foto, habelə audio və video kimi məlumatlarla bağlı diqqətli olun."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Tətbiq paylaşdıqda, qeydə aldıqda və ya yayımladıqda Android-in həmin tətbiqdə göstərilən, yaxud oxudulan məlumatlara girişi olur. Parol, ödəniş detalları, mesaj, foto, habelə audio və video kimi məlumatlarla bağlı diqqətli olun."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Başlayın"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"İT admininiz tərəfindən bloklanıb"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekran çəkimi cihaz siyasəti ilə deaktiv edilib"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hamısını silin"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index c00cd6a..258ae1d 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite da biste pregledali"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Greška pri čuvanju snimka ekrana"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Greška pri pokretanju snimanja ekrana"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Prikazuje se ceo ekran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Da biste izašli, prevucite nadole odozgo."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Važi"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Nazad"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Početna"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meni"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada delite, snimate ili prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada delite, snimate ili prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokira IT administrator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje ekrana je onemogućeno smernicama za uređaj"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 95eebad..5f9d63c 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Націсніце для прагляду"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Памылка захавання запісу экрана"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Памылка пачатку запісу экрана"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Прагляд у поўнаэкранным рэжыме"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Для выхаду правядзіце зверху ўніз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Зразумела"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"На Галоўную старонку"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Калі адбываецца абагульванне, запіс ці трансляцыя, Android мае доступ да ўсяго змесціва, якое паказваецца на экране ці прайграецца на прыладзе. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў, фота, відэа і аўдыя."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Калі адбываецца абагульванне, запіс ці трансляцыя змесціва праграмы, Android мае доступ да ўсяго змесціва, якое паказваецца ці прайграецца ў праграме. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў, фота, відэа і аўдыя."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Пачаць"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблакіравана вашым ІТ-адміністратарам"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Здыманне экрана адключана згодна з палітыкай прылады"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Ачысціць усё"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 567a22b..f2a8dbf 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Докоснете за преглед"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при запазването на записа на екрана"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"При стартирането на записа на екрана възникна грешка"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Изглед на цял екран"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"За изход плъзнете пръст надолу от горната част."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Разбрах"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Начало"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Когато споделяте, записвате или предавате, Android има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви. Затова бъдете внимателни с неща като пароли, подробности за начини на плащане, съобщения, снимки, аудио и видео."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Когато споделяте, записвате или предавате дадено приложение, Android има достъп до всичко, което се показва или възпроизвежда в него. Затова бъдете внимателни с неща като пароли, подробности за начини на плащане, съобщения, снимки, аудио и видео."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Стартиране"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано от системния ви администратор"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Заснемането на екрана е деактивирано от правило за устройството"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 38308e2..836b0ed 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"দেখতে ট্যাপ করুন"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"স্ক্রিন রেকর্ডিং সেভ করার সময় কোনও সমস্যা হয়েছে"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"স্ক্রিন রেকর্ডিং শুরু করার সময় সমস্যা হয়েছে"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ফুল-স্ক্রিনে দেখা হচ্ছে"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"বেরিয়ে যেতে উপর থেকে নিচের দিকে সোয়াইপ করুন।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"বুঝেছি"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ফিরুন"</string>
<string name="accessibility_home" msgid="5430449841237966217">"হোম"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"মেনু"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"আপনি শেয়ার, রেকর্ড বা কাস্ট করার সময়, স্ক্রিনে দৃশ্যমান বা ডিভাইসে চালানো সব কিছুই Android অ্যাক্সেস করতে পারবে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ে সতর্ক থাকুন।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"আপনি কোনও অ্যাপ শেয়ার, রেকর্ড বা কাস্ট করার সময়, সেই অ্যাপে দেখা যায় বা চালানো হয় এমন সব কিছু Android অ্যাক্সেস করতে পারবে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ে সতর্ক থাকুন।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"শুরু করুন"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"আপনার আইটি অ্যাডমিন ব্লক করেছেন"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ডিভাইস নীতির কারণে স্ক্রিন ক্যাপচার করার প্রসেস বন্ধ করা আছে"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"সব মুছে দিন"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index eaa0d9d..db902ef 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite da vidite"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Greška prilikom pohranjivanja snimka ekrana"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Greška pri pokretanju snimanja ekrana"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Prikazuje se cijeli ekran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Da izađete, prevucite odozgo nadolje."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Razumijem"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Nazad"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Dugme za početnu stranicu"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Dugme Meni"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada dijelite, snimate ili emitirate, Android ima pristup svemu što je vidljivo na ekranu ili što se reproducira na uređaju. Stoga budite oprezni s informacijama kao što su lozinke, podaci o plaćanju, poruke, fotografije, zvukovi i videozapisi."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada dijelite, snimate ili emitirate aplikaciju, Android ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga budite oprezni s informacijama kao što su lozinke, podaci o plaćanju, poruke, fotografije, zvukovi i videozapisi."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokirao je vaš IT administrator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje ekrana je onemogućeno pravilima uređaja"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Očisti sve"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 6416543..7eefa4f 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toca per veure-la"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"S\'ha produït un error en desar la gravació de la pantalla"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"S\'ha produït un error en iniciar la gravació de pantalla"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Mode de pantalla completa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Per sortir, llisca cap avall des de la part superior."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entesos"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Enrere"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Inici"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quan comparteixes, graves o emets contingut, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges, les fotos i l\'àudio i el vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quan comparteixes, graves o emets contingut, Android té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges, les fotos i l\'àudio i el vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Inicia"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquejat per l\'administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Les captures de pantalla estan desactivades per la política de dispositius"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Esborra-ho tot"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 56ae4b8..4a070fb 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Klepnutím nahrávku zobrazíte"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Při ukládání záznamu obrazovky došlo k chybě"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Při spouštění nahrávání obrazovky došlo k chybě"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Zobrazení celé obrazovky"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Režim ukončíte přejetím prstem shora dolů."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Rozumím"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Zpět"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Domů"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Během sdílení, nahrávání nebo odesílání má Android přístup k veškerému obsahu, který je viditelný na obrazovce nebo se přehrává v zařízení. Buďte proto opatrní s věcmi, jako jsou hesla, platební údaje, zprávy, fotografie, zvuk a video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Během sdílení, nahrávání nebo odesílání aplikace má Android přístup k veškerému obsahu, který je v dané aplikaci zobrazen nebo přehráván. Buďte proto opatrní s věcmi, jako jsou hesla, platební údaje, zprávy, fotografie, zvuk a video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začít"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokováno administrátorem IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Záznam obrazovky je zakázán zásadami zařízení"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Smazat vše"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 0ce9932..2893b1e 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tryk for at se"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Skærmoptagelsen kunne ikke gemmes"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Skærmoptagelsen kunne ikke startes"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visning i fuld skærm"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Stryg ned fra toppen for at afslutte."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK, det er forstået"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Tilbage"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Hjem"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Når du deler, optager eller caster, har Android adgang til alt, der er synligt på din skærm eller afspilles på din enhed. Vær derfor forsigtig med ting såsom adgangskoder, betalingsoplysninger, beskeder, billeder, lyd og video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Når du deler, optager eller caster en app, har Android adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med ting såsom adgangskoder, betalingsoplysninger, beskeder, billeder, lyd og video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokeret af din it-administrator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screenshots er deaktiveret af enhedspolitikken"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index ef76528..b527541 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Zum Ansehen tippen"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Fehler beim Speichern der Bildschirmaufzeichnung"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Fehler beim Start der Bildschirmaufzeichnung"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Vollbildmodus wird aktiviert"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Zum Beenden von oben nach unten wischen"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ok"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Zurück"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Startbildschirm"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Beim Teilen, Aufnehmen oder Streamen hat Android Zugriff auf alle Inhalte, die auf deinem Bildschirm sichtbar sind oder von deinem Gerät wiedergegeben werden. Sei also vorsichtig mit Informationen wie Passwörtern, Zahlungsdetails, Nachrichten, Fotos sowie Audio- und Videoinhalten."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Beim Teilen, Aufnehmen oder Streamen einer App hat Android Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder von ihr wiedergegeben werden. Sei also vorsichtig mit Informationen wie Passwörtern, Zahlungsdetails, Nachrichten, Fotos sowie Audio- und Videoinhalten."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Starten"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Vom IT-Administrator blockiert"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Bildschirmaufnahme ist durch die Geräterichtlinien deaktiviert"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 2c4b332..e23cbb8 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Πατήστε για προβολή"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Σφάλμα κατά την αποθήκευση της εγγραφής οθόνης"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Σφάλμα κατά την έναρξη της εγγραφής οθόνης"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Προβολή σε πλήρη οθόνη"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Για έξοδο, σύρετε προς τα κάτω από το επάνω μέρος."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Το κατάλαβα"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Πίσω"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Αρχική οθόνη"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Μενού"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Όταν κάνετε κοινή χρήση, εγγραφή ή μετάδοση, το Android έχει πρόσβαση σε οτιδήποτε είναι ορατό στην οθόνη σας ή αναπαράγεται στη συσκευή σας. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα, τις φωτογραφίες, τον ήχο και το βίντεο."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Όταν κάνετε κοινή χρήση, εγγραφή ή μετάδοση μιας εφαρμογής, το Android έχει πρόσβαση σε οτιδήποτε είναι ορατό ή αναπαράγεται στη συγκεκριμένη εφαρμογή. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα, τις φωτογραφίες, τον ήχο και το βίντεο."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Έναρξη"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Η κοινοποίηση τίθεται σε παύση κατά την εναλλαγή μεταξύ εφαρμογών"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Εναλλακτικά, κοινοποιήστε την εφαρμογή"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Επιστροφή"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Εναλλαγή μεταξύ εφαρμογών"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Αποκλείστηκε από τον διαχειριστή IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Η καταγραφή οθόνης έχει απενεργοποιηθεί από την πολιτική χρήσης συσκευής."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Διαγραφή όλων"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 03d2a51..8098739 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index f328508..d208382 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording, or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording, or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Sharing pauses when you switch apps"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Share this app instead"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Switch back"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"App switch"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 03d2a51..8098739 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 03d2a51..8098739 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index ed958d8..8a321e2 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tap to view"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Error saving screen recording"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error starting screen recording"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Viewing full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"To exit, swipe down from the top."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Got it"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Back"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"When you’re sharing, recording, or casting, Android has access to anything visible on your screen or played on your device. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"When you’re sharing, recording, or casting an app, Android has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Start"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"Sharing pauses when you switch apps"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Share this app instead"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Switch back"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"App switch"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 91b6be6..a6edfd5 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Presiona para ver"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Se produjo un error al guardar la grabación de pantalla"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Error al iniciar la grabación de pantalla"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualización en pantalla completa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para salir, desliza el dedo hacia abajo desde la parte superior."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atrás"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Página principal"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cuando compartas, grabes o transmitas contenido, Android podrá acceder a todo lo que sea visible en la pantalla o que reproduzcas en el dispositivo. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cuando compartas, grabes o transmitas una app, Android podrá acceder a todo el contenido que se muestre o que reproduzcas en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueada por tu administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La captura de pantalla está inhabilitada debido a la política del dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Cerrar todo"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 178c8b1..2fb76cd 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toca para verla"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"No se ha podido guardar la grabación de pantalla"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"No se ha podido empezar a grabar la pantalla"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Modo de pantalla completa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para salir, desliza el dedo de arriba abajo."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atrás"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Inicio"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cuando compartes, grabas o 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_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cuando compartes, grabas o 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_generic_permission_dialog_continue" msgid="8640381403048097116">"Empezar"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"El uso compartido se detiene al cambiar de aplicación"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Compartir esta aplicación"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Volver"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Cambio de aplicación"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueado por tu administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Las capturas de pantalla están inhabilitadas debido a la política de dispositivos"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
@@ -1030,7 +1037,7 @@
<string name="status_before_loading" msgid="1500477307859631381">"El contenido se mostrará en breve"</string>
<string name="missed_call" msgid="4228016077700161689">"Llamada perdida"</string>
<string name="messages_count_overflow_indicator" msgid="7850934067082006043">"<xliff:g id="NUMBER">%d</xliff:g>+"</string>
- <string name="people_tile_description" msgid="8154966188085545556">"Consulta los mensajes recientes, las llamadas perdidas y los cambios de estado"</string>
+ <string name="people_tile_description" msgid="8154966188085545556">"Consulta mensajes recientes, llamadas perdidas y cambios de estado"</string>
<string name="people_tile_title" msgid="6589377493334871272">"Conversación"</string>
<string name="paused_by_dnd" msgid="7856941866433556428">"Pausado por No molestar"</string>
<string name="new_notification_text_content_description" msgid="2915029960094389291">"<xliff:g id="NAME">%1$s</xliff:g> ha enviado un mensaje: <xliff:g id="NOTIFICATION">%2$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index b2470f7..b300e78 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Puudutage kuvamiseks"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Viga ekraanisalvestise salvestamisel"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Viga ekraanikuva salvestamise alustamisel"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Kuvamine täisekraanil"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Väljumiseks pühkige ülevalt alla."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Selge"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Tagasi"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Kodu"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menüü"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kui jagate, salvestate või kannate üle, on Androidil juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge ettevaatlik selliste andmetega nagu paroolid, makseteave, sõnumid, fotod ning heli ja video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kui jagate, salvestate või kannate rakendust üle, on Androidil juurdepääs kõigele, mida selles rakenduses kuvatakse või esitatakse. Seega olge paroolide, makseteabe, sõnumite, fotode, heli ja videoga ettevaatlik."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Alusta"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokeeris teie IT-administraator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekraanikuva jäädvustamine on seadmereeglitega keelatud"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tühjenda kõik"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 33fee8d..8fd391c 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Sakatu ikusteko"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Errore bat gertatu da pantaila-grabaketa gordetzean"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Errore bat gertatu da pantaila grabatzen hastean"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Pantaila osoko ikuspegia"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Irteteko, pasatu hatza goitik behera."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ados"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atzera"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Hasiera"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menua"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Edukia partekatzen, grabatzen edo igortzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztia atzi dezake Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin, argazkiekin, audioekin eta bideoekin, besteak beste."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Aplikazio bat partekatzen, grabatzen edo igortzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztia atzi dezake Android-ek. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin, argazkiekin, audioekin eta bideoekin, besteak beste."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Hasi"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IKT saileko administratzaileak blokeatu du"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Pantaila-kapturak egiteko aukera desgaituta dago, gailu-gidalerroei jarraikiz"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Garbitu guztiak"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 97f6930..4d3b3f0 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"برای مشاهده ضربه بزنید"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"خطا در ذخیرهسازی ضبط صفحهنمایش"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"خطا هنگام شروع ضبط صفحهنمایش"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"درحال مشاهده در حالت تمامصفحه"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"برای خروج، از بالای صفحه تند بهپایین بکشید."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"متوجهام"</string>
<string name="accessibility_back" msgid="6530104400086152611">"برگشت"</string>
<string name="accessibility_home" msgid="5430449841237966217">"صفحهٔ اصلی"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"منو"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"وقتی درحال همرسانی، ضبط، یا پخش محتوا هستید، Android به همه محتوایی که در صفحهتان نمایان است یا در دستگاهتان پخش میشود دسترسی دارد. درنتیجه مراقب چیزهایی مثل گذرواژهها، جزئیات پرداخت، پیامها، عکسها، و صدا و تصویر باشید."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"وقتی درحال همرسانی، ضبط، یا پخش محتوای برنامهای هستید، Android به همه محتوایی که در آن برنامه نمایان است یا پخش میشود دسترسی دارد. درنتیجه مراقب چیزهایی مثل گذرواژهها، جزئیات پرداخت، پیامها، عکسها، و صدا و تصویر باشید."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"شروع"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"سرپرست فناوری اطلاعات آن را مسدود کرده است"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"«ضبط صفحهنمایش» بهدلیل خطمشی دستگاه غیرفعال است"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"پاک کردن همه موارد"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index abf52fe..f3b95de 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Napauta näyttääksesi"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Virhe näyttötallenteen tallentamisessa"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Virhe näytön tallennuksen aloituksessa"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Koko näytön tilassa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Sulje palkki pyyhkäisemällä alas ruudun ylälaidasta."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Selvä"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Takaisin"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Aloitus"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Valikko"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kun jaat, tallennat tai striimaat, Android saa pääsyn kaikkeen näytölläsi näkyvään tai laitteellasi toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kun jaat, tallennat tai striimaat, Android saa pääsyn kaikkeen sovelluksella näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Aloita"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT-järjestelmänvalvojasi estämä"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Kuvakaappaus on poistettu käytöstä laitekäytännön perusteella"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tyhjennä kaikki"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 4f94d1f..8a74f8a 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Touchez pour afficher"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erreur d\'enregistrement de l\'écran"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Une erreur s\'est produite lors du démarrage de l\'enregistrement d\'écran"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Affichage plein écran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Pour quitter, balayez vers le bas à partir du haut."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Précédent"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Domicile"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Lorsque vous partagez, enregistrez ou diffusez, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages, les photos et les contenus audio et vidéo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lorsque vous partagez, enregistrez ou diffusez une application, Android a accès à tout ce qui est visible sur votre écran ou lu sur cette application. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages, les photos et les contenus audio et vidéo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Commencer"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquée par votre administrateur informatique"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La fonctionnalité de capture d\'écran est désactivée par l\'application Device Policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 0ede09a..5fe8f55 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Appuyez pour afficher"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erreur lors de l\'enregistrement de l\'écran"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Erreur lors du démarrage de l\'enregistrement de l\'écran"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Affichage en plein écran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Pour quitter, balayez l\'écran du haut vers le bas."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Retour"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Accueil"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Lorsque vous partagez, enregistrez ou castez, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention aux éléments tels que les mots de passe, détails de mode de paiement, messages, photos et contenus audio et vidéo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lorsque vous partagez, enregistrez ou castez une appli, Android a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention aux éléments tels que les mots de passe, détails de mode de paiement, messages et contenus audio et vidéo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Commencer"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqué par votre administrateur informatique"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La capture d\'écran est désactivée conformément aux règles relatives à l\'appareil"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 1446755..804aeca 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toca para ver o contido"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Produciuse un erro ao gardar a gravación da pantalla"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Produciuse un erro ao iniciar a gravación da pantalla"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Vendo pantalla completa"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para saír, pasa o dedo cara abaixo desde a parte superior."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendido"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Volver"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Inicio"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menú"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Cando compartes, gravas ou emites contido, Android ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado con determinada información, como os contrasinais, os detalles de pago, as mensaxes e as fotos, así como o contido de audio e de vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Cando compartes, gravas ou emites unha aplicación, Android ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado con determinada información, como os contrasinais, os detalles de pago, as mensaxes e as fotos, así como o contido de audio e de vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"O teu administrador de TI bloqueou esta aplicación"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A política do dispositivo desactivou a opción de capturar a pantalla"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todo"</string>
@@ -1040,7 +1051,7 @@
<string name="battery_state_unknown_notification_title" msgid="8464703640483773454">"Produciuse un problema ao ler o medidor da batería"</string>
<string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Toca para obter máis información"</string>
<string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"Sen alarmas postas"</string>
- <string name="accessibility_bouncer" msgid="5896923685673320070">"introducir bloqueo de pantalla"</string>
+ <string name="accessibility_bouncer" msgid="5896923685673320070">"introducir o bloqueo de pantalla"</string>
<string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Sensor de impresión dixital"</string>
<string name="accessibility_authenticate_hint" msgid="798914151813205721">"autenticar"</string>
<string name="accessibility_enter_hint" msgid="2617864063504824834">"poñer o dispositivo"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 89390b1..cfd5a36 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"જોવા માટે ટૅપ કરો"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"સ્ક્રીન રેકોર્ડિંગ સાચવવામાં ભૂલ આવી"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"સ્ક્રીનને રેકૉર્ડ કરવાનું શરૂ કરવામાં ભૂલ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"પૂર્ણ સ્ક્રીન પર જુઓ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"બહાર નીકળવા માટે, ટોચ પરથી નીચે સ્વાઇપ કરો."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"સમજાઈ ગયું"</string>
<string name="accessibility_back" msgid="6530104400086152611">"પાછળ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"હોમ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"મેનુ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"જ્યારે તમે શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તમારી સ્ક્રીન પર દેખાતી હોય કે તમારા ડિવાઇસ પર ચલાવવામાં આવતી હોય તેવી બધી વસ્તુનો ઍક્સેસ Android પાસે હોય છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ, ફોટા અને ડિવાઇસ પર વાગી રહેલા ઑડિયો તથા વીડિયો જેવી બાબતોને લઈને સાવચેત રહો."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"જ્યારે તમે કોઈ ઍપ શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તે ઍપ પર બતાવવામાં કે ચલાવવામાં આવતી હોય તેવી બધી વસ્તુનો ઍક્સેસ Android પાસે હોય છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ, ફોટા અને ડિવાઇસ પર વાગી રહેલા ઑડિયો તથા વીડિયો જેવી બાબતોને લઈને સાવચેત રહો."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"શરૂ કરો"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"તમારા IT ઍડમિન દ્વારા બ્લૉક કરાયેલી"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ડિવાઇસ પૉલિસી અનુસાર સ્ક્રીન કૅપ્ચર કરવાની સુવિધા બંધ કરવામાં આવી છે"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"બધુ સાફ કરો"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index fb017da..194321a 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"देखने के लिए टैप करें"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रीन रिकॉर्डिंग सेव करते समय गड़बड़ी हुई"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रीन को रिकॉर्ड करने में गड़बड़ी आ रही है"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"फ़ुल स्क्रीन मोड पर देखा जा रहा है"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"बाहर निकलने के लिए, सबसे ऊपर से नीचे की ओर स्वाइप करें."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ठीक है"</string>
<string name="accessibility_back" msgid="6530104400086152611">"वापस जाएं"</string>
<string name="accessibility_home" msgid="5430449841237966217">"होम"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"मेन्यू"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"शेयर, रिकॉर्ड या कास्ट करते समय, Android के पास स्क्रीन पर दिख रहे कॉन्टेंट या डिवाइस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज, फ़ोटो, और डिवाइस पर चल रहे ऑडियो और वीडियो को लेकर सावधानी बरतें."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"किसी ऐप्लिकेशन को शेयर, रिकॉर्ड या कास्ट करते समय, Android के पास उस ऐप्लिकेशन पर दिख रहे कॉन्टेंट या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज, फ़ोटो, और डिवाइस पर चल रहे ऑडियो और वीडियो को लेकर सावधानी बरतें."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"शुरू करें"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"आपके आईटी एडमिन ने स्क्रीन कैप्चर करने की सुविधा पर रोक लगाई है"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिवाइस से जुड़ी नीति के तहत स्क्रीन कैप्चर करने की सुविधा बंद है"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सभी को हटाएं"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index b7a7cdd..7fbc100 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Dodirnite za prikaz"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Pogreška prilikom spremanja snimke zaslona"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Pogreška prilikom pokretanja snimanja zaslona"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Gledanje preko cijelog zaslona"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Za izlaz prijeđite prstom od vrha prema dolje."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Shvaćam"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Natrag"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Početna"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Izbornik"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kad dijelite, snimate ili emitirate, Android ima pristup svemu što je vidljivo na vašem zaslonu ili se reproducira na vašem uređaju. Stoga pazite na stvari kao što su zaporke, podaci o plaćanju, poruke, fotografije te audio i videozapisi."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kad dijelite, snimate ili emitirate aplikaciju, Android ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na stvari kao što su zaporke, podaci o plaćanju, poruke, fotografije te audio i videozapisi."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokirao vaš IT administrator"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje zaslona onemogućeno je u skladu s pravilima za uređaje"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši sve"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 6a96fcc..1c46c44 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Koppintson a megtekintéshez"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Hiba történt a képernyőrögzítés mentése során"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Hiba a képernyőrögzítés indításakor"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Megtekintése teljes képernyőn"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Kilépéshez csúsztassa ujját fentről lefelé."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Értem"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Vissza"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Főoldal"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Amikor Ön megosztást, rögzítést vagy átküldést végez, az Android a képernyőn látható vagy az eszközön lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel, a fotókkal, valamint a hang- és videófelvételekkel."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Amikor Ön megoszt, rögzít vagy átküld egy alkalmazást, az Android az adott alkalmazásban látható vagy lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel, a fotókkal, valamint a hang- és videófelvételekkel."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Indítás"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Rendszergazda által letiltva"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A képernyőfelvételt eszközszabályzat tiltja"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Az összes törlése"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index d91b3dd2..afd8b66 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Հպեք՝ դիտելու համար"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Չհաջողվեց պահել էկրանի տեսագրությունը"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Չհաջողվեց սկսել տեսագրումը"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Լիաէկրան դիտում"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Դուրս գալու համար վերևից սահահարվածեք դեպի ներքև:"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Պարզ է"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Հետ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Տուն"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Ցանկ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք էկրանը, Android-ին հասանելի է լինում այն ամենը, ինչ տեսանելի է ձեր էկրանին և նվագարկվում է ձեր սարքում։ Ուստի ուշադիր եղեք այնպիսի բաների հետ, ինչպիսիք են գաղտնաբառերը, վճարային տվյալները, հաղորդագրությունները, լուսանկարները, աուդիո և վիդեո բովանդակությունը։"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք որևէ հավելվածի էկրանը, Android-ին հասանելի է լինում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Ուստի ուշադիր եղեք այնպիսի բաների հետ, ինչպիսիք են գաղտնաբառերը, վճարային տվյալները, հաղորդագրությունները, լուսանկարները, աուդիո և վիդեո բովանդակությունը։"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Սկսել"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Արգելափակվել է ձեր ՏՏ ադմինիստրատորի կողմից"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Էկրանի տեսագրումն անջատված է սարքի կանոնների համաձայն"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Մաքրել բոլորը"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 550b048..f5564ba 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Ketuk untuk melihat"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Terjadi error saat menyimpan rekaman layar"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Terjadi error saat memulai perekaman layar"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Melihat layar penuh"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Untuk keluar, geser layar ke bawah dari atas."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Mengerti"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Kembali"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Utama"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Jika Anda membagikan, merekam, atau mentransmisikan, Android akan memiliki akses ke semua hal yang ditampilkan di layar atau yang diputar di perangkat Anda. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, foto, audio, dan video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Jika Anda membagikan, merekam, atau mentransmisikan suatu aplikasi, Android akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, foto, audio, dan video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Mulai"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Diblokir oleh admin IT Anda"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Pengambilan screenshot dinonaktifkan oleh kebijakan perangkat"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hapus semua"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 86455c0..647d1c4 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Ýttu til að skoða"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Villa við að vista skjáupptöku"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Villa við að hefja upptöku skjás"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Notar allan skjáinn"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Strjúktu niður frá efri brún til að hætta."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ég skil"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Til baka"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Heim"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Valmynd"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Þegar þú deilir, tekur upp eða varpar hefur Android aðgang að öllu sem sést á skjánum eða spilast í tækinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð, myndir, hljóð og myndskeið."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Þegar þú deilir, tekur upp eða varpar forriti hefur Android aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð, myndir, hljóð og myndskeið."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Byrja"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Útilokað af kerfisstjóra"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Slökkt er á skjáupptöku í tækjareglum"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 17ec328..3b60f42 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tocca per visualizzare"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Errore durante il salvataggio della registrazione dello schermo"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Errore durante l\'avvio della registrazione dello schermo"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualizzazione a schermo intero"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Per uscire, scorri dall\'alto verso il basso."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Indietro"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando condividi, registri o trasmetti, Android ha accesso a qualsiasi elemento visibile sul tuo schermo o in riproduzione sul tuo dispositivo. Presta quindi attenzione a password, dettagli sui pagamenti, messaggi, foto, audio e video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando condividi, registri o trasmetti un\'app, Android ha accesso a qualsiasi elemento visualizzato o riprodotto sull\'app. Presta quindi attenzione a password, dettagli sui pagamenti, messaggi, foto, audio e video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Inizia"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloccata dall\'amministratore IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"L\'acquisizione schermo è disattivata dai criteri relativi ai dispositivi"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Cancella tutto"</string>
@@ -510,14 +521,14 @@
<string name="stream_accessibility" msgid="3873610336741987152">"Accessibilità"</string>
<string name="volume_ringer_status_normal" msgid="1339039682222461143">"Attiva suoneria"</string>
<string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Attiva vibrazione"</string>
- <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Disattiva suoneria"</string>
+ <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Silenzia"</string>
<string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Tocca per riattivare l\'audio."</string>
<string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Tocca per attivare la vibrazione. L\'audio dei servizi di accessibilità può essere disattivato."</string>
<string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Tocca per disattivare l\'audio. L\'audio dei servizi di accessibilità può essere disattivato."</string>
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Tocca per attivare la vibrazione."</string>
<string name="volume_stream_content_description_mute_a11y" msgid="5743548478357238156">"%1$s. Tocca per disattivare l\'audio."</string>
<string name="volume_ringer_change" msgid="3574969197796055532">"Tocca per cambiare la modalità della suoneria"</string>
- <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"disattiva l\'audio"</string>
+ <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenzia"</string>
<string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"riattiva l\'audio"</string>
<string name="volume_ringer_hint_vibrate" msgid="6211609047099337509">"vibrazione"</string>
<string name="volume_dialog_title" msgid="6502703403483577940">"Controlli del volume %s"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 5b317cb..121e5be 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"יש להקיש כדי להציג"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"שגיאה בשמירה של הקלטת המסך"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"שגיאה בהפעלה של הקלטת המסך"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"צפייה במסך מלא"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"כדי לצאת, פשוט מחליקים אצבע מלמעלה למטה."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"הבנתי"</string>
<string name="accessibility_back" msgid="6530104400086152611">"חזרה"</string>
<string name="accessibility_home" msgid="5430449841237966217">"בית"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"תפריט"</string>
@@ -412,6 +415,14 @@
<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>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"התחלה"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"האפשרות נחסמה על ידי אדמין ב-IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"צילום המסך מושבת בגלל מדיניות המכשיר"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ניקוי הכול"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 56a78e2..383d9f4 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"タップすると表示されます"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"画面の録画の保存中にエラーが発生しました"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"画面の録画中にエラーが発生しました"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"全画面表示"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"終了するには、上から下にスワイプします。"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"戻る"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ホーム"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"メニュー"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"共有、録画、キャスト中は、画面に表示される内容やデバイスで再生される内容に Android がアクセスできるため、パスワード、お支払いの詳細、メッセージ、写真、音声、動画などの情報にご注意ください。"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"アプリの共有、録画、キャスト中は、そのアプリで表示または再生される内容に Android がアクセスできるため、パスワード、お支払いの詳細、メッセージ、写真、音声、動画などの情報にご注意ください。"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 管理者によりブロックされました"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"デバイス ポリシーに基づき、画面のキャプチャが無効になりました"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"すべて消去"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index f19b6b6..943b2885 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"შეეხეთ სანახავად"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ეკრანის ჩანაწერის შენახვისას შეცდომა მოხდა"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ეკრანის ჩაწერის დაწყებისას წარმოიქმნა შეცდომა"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"მიმდინარეობს სრულ ეკრანზე ნახვა"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"გასვლისთვის გადაფურცლეთ ზემოდან ქვემოთ."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"გასაგებია"</string>
<string name="accessibility_back" msgid="6530104400086152611">"უკან"</string>
<string name="accessibility_home" msgid="5430449841237966217">"საწყისი"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"მენიუ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"გაზიარებისას, ჩაწერისას ან ტრანსლირებისას, Android-ს წვდომა აქვს ყველაფერზე, რაც ჩანს თქვენს ეკრანზე ან უკრავს თქვენს მოწყობილობაზე. ამიტომ იყავით ფრთხილად ისეთ ინფორმაციასთან, როგორიცაა პაროლები, გადახდის დეტალები, შეტყობინებები, ფოტოები, აუდიო და ვიდეო."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"აპის გაზიარებისას, ჩაწერისას ან ტრანსლირებისას, Android-ს წვდომა აქვს ყველაფერზე, რაც ჩანს ან იკვრება აპში. ამიტომ იყავით ფრთხილად ისეთ ინფორმაციასთან, როგორიცაა პაროლები, გადახდის დეტალები, შეტყობინებები, ფოტოები, აუდიო და ვიდეო."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"დაწყება"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"დაბლოკილია თქვენი IT-ადმინისტრატორის მიერ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ეკრანის აღბეჭდვა გამორთულია მოწყობილობის წესების თანახმად"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ყველას გასუფთავება"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index c7d30e7..a80dcfb 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Көру үшін түртіңіз."</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Экран жазбасын сақтау кезінде қате шықты."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Экрандағы бейнені жазу кезінде қате шықты."</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Толық экранда көру"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Шығу үшін жоғарыдан төмен қарай сырғытыңыз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Түсінікті"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Артқа"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Үй"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Mәзір"</string>
@@ -301,8 +304,8 @@
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Күн шыққанға дейін"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Қосылу уақыты: <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> дейін"</string>
- <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"Ұйқы уақытында"</string>
- <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends" msgid="1790772410777123685">"Ұйқы уақыты аяқталғанға дейін"</string>
+ <string name="quick_settings_dark_mode_secondary_label_on_at_bedtime" msgid="2274300599408864897">"Ұйқы режимінде"</string>
+ <string name="quick_settings_dark_mode_secondary_label_until_bedtime_ends" msgid="1790772410777123685">"Ұйқы режимі аяқталғанға дейін"</string>
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC өшірулі"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC қосулы"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Бөлісу, жазу не трансляциялау кезінде Android жүйесі экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізген кезде сақ болыңыз."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Қолданба экранын бөлісу, жазу не трансляциялау кезінде Android жүйесі онда көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізген кезде сақ болыңыз."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Бастау"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Әкімшіңіз бөгеген"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Құрылғы саясатына байланысты экранды түсіру өшірілді."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Барлығын тазарту"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 375b79d..702b9cb 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ចុចដើម្បីមើល"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"មានបញ្ហាក្នុងការរក្សាទុកការថតវីដេអូអេក្រង់"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"មានបញ្ហាក្នុងការចាប់ផ្ដើមថតអេក្រង់"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"កំពុងមើលពេញអេក្រង់"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ដើម្បីចាកចេញ សូមអូសពីលើចុះក្រោម។"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"យល់ហើយ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ថយក្រោយ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"គេហទំព័រ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ម៉ឺនុយ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬភ្ជាប់, Android មានសិទ្ធិចូលប្រើអ្វីៗដែលអាចមើលឃើញនៅលើអេក្រង់របស់អ្នក ឬចាក់នៅលើឧបករណ៍របស់អ្នក។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះអ្វីៗដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ រូបថត ព្រមទាំងសំឡេង និងវីដេអូ។"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬភ្ជាប់កម្មវិធី, Android មានសិទ្ធិចូលប្រើអ្វីៗដែលបង្ហាញ ឬចាក់នៅលើកម្មវិធីនោះ។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះអ្វីៗដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ រូបថត ព្រមទាំងសំឡេង និងវីដេអូ។"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ចាប់ផ្ដើម"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"បានទប់ស្កាត់ដោយអ្នកគ្រប់គ្រងផ្នែកព័ត៌មានវិទ្យារបស់អ្នក"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ការថតអេក្រង់ត្រូវបានបិទដោយគោលការណ៍ឧបករណ៍"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"សម្អាតទាំងអស់"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 437f406..544af6a 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ವೀಕ್ಷಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೇವ್ ಮಾಡುವಾಗ ದೋಷ ಎದುರಾಗಿದೆ"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಪ್ರಾರಂಭಿಸುವಾಗ ದೋಷ ಕಂಡುಬಂದಿದೆ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ಪೂರ್ಣ ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ವೀಕ್ಷಿಸಲಾಗುತ್ತಿದೆ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ನಿರ್ಗಮಿಸಲು, ಮೇಲಿನಿಂದ ಕೆಳಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ಅರ್ಥವಾಯಿತು"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ಹಿಂದೆ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ಮುಖಪುಟ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ಮೆನು"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ನೀವು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡಿಂಗ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಕ್ಯಾಸ್ಟ್ ಮಾಡುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಮಾಡುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಆದ್ದರಿಂದ ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು, ಫೋಟೋಗಳು ಹಾಗೂ ಆಡಿಯೊ ಮತ್ತು ವೀಡಿಯೊದಂತಹ ವಿಷಯಗಳ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡಿಂಗ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಕ್ಯಾಸ್ಟ್ ಮಾಡುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್ನಲ್ಲಿ ತೋರಿಸುವ ಅಥವಾ ಪ್ಲೇ ಮಾಡುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ Android ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಆದ್ದರಿಂದ ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು, ಫೋಟೋಗಳು ಹಾಗೂ ಆಡಿಯೊ ಮತ್ತು ವೀಡಿಯೊದಂತಹ ವಿಷಯಗಳ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ಪ್ರಾರಂಭಿಸಿ"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ನಿಮ್ಮ IT ನಿರ್ವಾಹಕರು ನಿರ್ಬಂಧಿಸಿದ್ದಾರೆ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ಸಾಧನ ನೀತಿಯಿಂದ ಸ್ಕ್ರೀನ್ ಕ್ಯಾಪ್ಚರಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 9916951..bd58c0f 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"탭하여 보기"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"화면 녹화 저장 중에 오류가 발생했습니다."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"화면 녹화 시작 중 오류 발생"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"전체 화면 모드"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"종료하려면 위에서 아래로 스와이프합니다."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"확인"</string>
<string name="accessibility_back" msgid="6530104400086152611">"뒤로"</string>
<string name="accessibility_home" msgid="5430449841237966217">"홈"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"메뉴"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"공유, 녹화 또는 전송 중에 Android가 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"앱을 공유, 녹화 또는 전송할 때는 Android가 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"시작"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 관리자에 의해 차단됨"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"기기 정책에 의해 화면 캡처가 사용 중지되었습니다."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index aaec2cd..054aa2e 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Көрүү үчүн таптаңыз"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Экран тартылган жок"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Экранды жаздырууну баштоодо ката кетти"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Толук экран режимин көрүү"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Чыгуу үчүн экранды ылдый сүрүп коюңуз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Түшүндүм"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Артка"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Үйгө"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Бөлүшүп, жаздырып же тышкы экранга чыгарып жатканда Android экраныңыздагы бардык маалыматты же түзмөктө ойнотулуп жаткан нерселерди көрө алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү, сүрөттөрдү, аудио жана видеону көрсөтүп албаңыз."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Колдонмону бөлүшүп, жаздырып же тышкы экранга чыгарганда Android ал колдонмодо көрсөтүлүп жана ойнотулуп жаткан нерселерди көрө алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү, сүрөттөрдү, аудио жана видеону көрсөтүп албаңыз."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Баштоо"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT администраторуңуз бөгөттөп койгон"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Түзмөк саясаты экрандагыны тартып алууну өчүрүп койгон"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Баарын тазалап салуу"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 0306fc9..66bd9e3 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ແຕະເພື່ອເບິ່ງ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ເກີດຂໍ້ຜິດພາດໃນການບັນທຶກໜ້າຈໍ"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ເກີດຄວາມຜິດພາດໃນການບັນທຶກໜ້າຈໍ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ກຳລັງເບິ່ງເຕັມຈໍ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ເພື່ອອອກ, ໃຫ້ປັດລົງຈາກເທິງສຸດ."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ເຂົ້າໃຈແລ້ວ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ກັບຄືນ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ໜ້າທຳອິດ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ເມນູ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ເມື່ອທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານ, Android ຈະມີສິດເຂົ້າເຖິງທຸກສິ່ງທີ່ປາກົດຢູ່ໜ້າຈໍຂອງທ່ານ ຫຼື ຫຼິ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ດັ່ງນັ້ນ, ໃຫ້ລະມັດລະວັງສິ່ງຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ, ຮູບພາບ ພ້ອມທັງສຽງ ແລະ ວິດີໂອ."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ເມື່ອທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານແອັບ, Android ຈະມີສິດເຂົ້າເຖິງທຸກສິ່ງທີ່ສະແດງ ຫຼື ຫຼິ້ນຢູ່ແອັບດັ່ງກ່າວ. ດັ່ງນັ້ນ, ໃຫ້ລະມັດລະວັງສິ່ງຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ, ຮູບພາບ ພ້ອມທັງສຽງ ແລະ ວິດີໂອ."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ເລີ່ມ"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ຖືກບລັອກໄວ້ໂດຍຜູ້ເບິ່ງແຍງລະບົບໄອທີຂອງທ່ານ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ການຖ່າຍຮູບໜ້າຈໍຖືກປິດການນຳໃຊ້ໄວ້ໂດຍນະໂຍບາຍອຸປະກອນ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ລຶບລ້າງທັງໝົດ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 8548a24..c93506f 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Palieskite, kad peržiūrėtumėte"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Išsaugant ekrano įrašą įvyko klaida"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Pradedant ekrano vaizdo įrašymą iškilo problema"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Peržiūrima viso ekrano režimu"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Jei norite išeiti, perbraukite žemyn iš viršaus."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Supratau"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atgal"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Pagrindinis"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meniu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kai bendrinate, įrašote ar perduodate turinį, „Android“ gali pasiekti viską, kas rodoma ekrane ar leidžiama įrenginyje. Todėl būkite atsargūs naudodami slaptažodžius, išsamią mokėjimo metodo informaciją, pranešimus, nuotraukas ir garso bei vaizdo įrašus."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kai bendrinate, įrašote ar perduodate programą, „Android“ gali pasiekti viską, kas rodoma ar leidžiama programoje. Todėl būkite atsargūs naudodami slaptažodžius, išsamią mokėjimo metodo informaciją, pranešimus, nuotraukas ir garso bei vaizdo įrašus."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pradėti"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Užblokavo jūsų IT administratorius"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekrano fiksavimo funkcija išjungta vadovaujantis įrenginio politika"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Viską išvalyti"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 79f73b6..dcd54c4 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Pieskarieties, lai skatītu"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Saglabājot ekrāna ierakstu, radās kļūda."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Sākot ierakstīt ekrāna saturu, radās kļūda."</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Skatīšanās pilnekrāna režīmā"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Lai izietu, no augšdaļas velciet lejup."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Labi"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Atpakaļ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Sākums"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Izvēlne"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kopīgošanas, ierakstīšanas vai apraides laikā Android var piekļūt visam, kas tiek rādīts jūsu ekrānā vai atskaņots jūsu ierīcē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem, fotoattēliem un audio un video saturu."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Lietotnes kopīgošanas, ierakstīšanas vai apraides laikā Android var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem, fotoattēliem un audio un video saturu."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Sākt"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloķējis jūsu IT administrators"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ierīces politika ir atspējojusi ekrānuzņēmumu izveidi"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Dzēst visu"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index b8829c6..e83bf81 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Допрете за прегледување"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при зачувувањето на снимката од екранот"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Грешка при почетокот на снимањето на екранот"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Се прикажува на цел екран"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"За да излезете, повлечете одозгора надолу."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Сфатив"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Почетна страница"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Мени"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Кога споделувате, снимате или емитувате, Android има пристап до сѐ што е видливо на вашиот екран или пуштено на вашиот уред. Затоа, бидете внимателни со работи како лозинки, детали за плаќање, пораки, фотографии и аудио и видео."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Кога споделувате, снимате или емитувате апликација, Android има пристап до сѐ што се прикажува или пушта на таа апликација. Затоа, бидете внимателни со работи како лозинки, детали за плаќање, пораки, фотографии и аудио и видео."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Започни"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано од IT-администраторот"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Снимањето на екранот е оневозможено со правила на уредот"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Избриши сѐ"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index f8c14e6..c8fbde8 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"കാണാൻ ടാപ്പ് ചെയ്യുക"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"സ്ക്രീൻ റെക്കോർഡിംഗ് സംരക്ഷിക്കുന്നതിൽ പിശക്"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"സ്ക്രീൻ റെക്കോർഡിംഗ് ആരംഭിക്കുന്നതിൽ പിശക്"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"പൂർണ്ണ സ്ക്രീനിൽ കാണുന്നു"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"പുറത്തുകടക്കാൻ, മുകളിൽ നിന്ന് താഴോട്ട് സ്വൈപ്പ് ചെയ്യുക."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"മനസ്സിലായി"</string>
<string name="accessibility_back" msgid="6530104400086152611">"മടങ്ങുക"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ഹോം"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"മെനു"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, Android-ന് നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, ഓഡിയോ, വീഡിയോ എന്നിവ പോലുള്ള കാര്യങ്ങൾ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ഒരു ആപ്പ് പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, Android-ന് ആ ആപ്പിൽ കാണിക്കുന്ന അല്ലെങ്കിൽ പ്ലേ ചെയ്യുന്ന എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, ഓഡിയോ, വീഡിയോ എന്നിവ പോലുള്ള കാര്യങ്ങൾ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ആരംഭിക്കുക"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"നിങ്ങളുടെ ഐടി അഡ്മിൻ ബ്ലോക്ക് ചെയ്തു"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ഉപകരണ നയം, സ്ക്രീൻ ക്യാപ്ചർ ചെയ്യൽ പ്രവർത്തനരഹിതമാക്കി"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്ക്കുക"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index ceb8782..15745ff 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Харахын тулд товшино уу"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Дэлгэцийн бичлэгийг хадгалахад алдаа гарлаа"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Дэлгэцийн бичлэгийг эхлүүлэхэд алдаа гарлаа"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Бүтэн дэлгэцээр үзэж байна"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Гарахаар бол дээрээс нь доош нь чирнэ үү."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ойлголоо"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Буцах"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Гэрийн"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Цэс"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Таныг хуваалцаж, бичлэг хийж эсвэл дамжуулж байх үед Android таны дэлгэцэд харуулсан эсвэл төхөөрөмжид тань тоглуулсан аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж, зураг, аудио болон видео зэрэг зүйлд болгоомжтой хандаарай."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Таныг хуваалцаж, бичлэг хийж эсвэл дамжуулж байх үед Android тухайн аппад харуулсан эсвэл тоглуулсан аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж, зураг, аудио болон видео зэрэг бусад зүйлд болгоомжтой хандаарай."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Эхлүүлэх"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Таны IT админ блоклосон"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Төхөөрөмжийн бодлогоор дэлгэцийн зураг авахыг идэвхгүй болгосон"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Бүгдийг арилгах"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index ba45d53..a52d217 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"पाहण्यासाठी टॅप करा"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रीन रेकॉर्डिंग सेव्ह करताना एरर आली"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रीन रेकॉर्डिंग सुरू करताना एरर आली"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"पूर्ण स्क्रीनवर पाहत आहात"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"बाहेर पडण्यासाठी, वरून खाली स्वाइप करा."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"समजले"</string>
<string name="accessibility_back" msgid="6530104400086152611">"मागे"</string>
<string name="accessibility_home" msgid="5430449841237966217">"होम"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"मेनू"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"तुम्ही शेअर, रेकॉर्ड किंवा कास्ट करत असताना, Android ला तुमच्या स्क्रीनवर दाखवलेल्या किंवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज, फोटो आणि ऑडिओ व व्हिडिओ यांसारख्या गोष्टींबाबत सावधगिरी बाळगा."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"तुम्ही एखादे अॅप शेअर, रेकॉर्ड किंवा कास्ट करत असताना, Android ला त्या अॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज, फोटो आणि ऑडिओ व व्हिडिओ यांसारख्या गोष्टींबाबत सावधगिरी बाळगा."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"सुरुवात करा"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"तुमच्या आयटी ॲडमिनने ब्लॉक केले आहे"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिव्हाइस धोरणाने स्क्रीन कॅप्चर करणे बंद केले आहे"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index e28ea01..3da42a5 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Ketik untuk lihat"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Ralat semasa menyimpan rakaman skrin"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ralat semasa memulakan rakaman skrin"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Melihat skrin penuh"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Untuk keluar, leret ke bawah dari bahagian atas."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Kembali"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Rumah"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Apabila anda membuat perkongsian, rakaman atau penghantaran, Android boleh mengakses apa-apa sahaja yang boleh dilihat pada skrin anda atau dimainkan pada peranti anda. Oleh hal yang demikian, berhati-hati dengan perkara seperti kata laluan, butiran pembayaran, mesej, foto dan audio serta video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Apabila anda berkongsi, merakam atau menghantar apl, Android boleh mengakses apa-apa sahaja yang ditunjukan atau dimainkan pada apl tersebut. Oleh hal yang demikian, berhati-hati dengan perkara seperti kata laluan, butiran pembayaran, mesej, foto dan audio serta video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Mula"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Disekat oleh pentadbir IT anda"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Tangkapan skrin dilumpuhkan oleh dasar peranti"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Kosongkan semua"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 5bcd5a0..c258862 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ကြည့်ရှုရန် တို့ပါ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ဖန်သားပြင်ရိုက်ကူးမှုကို သိမ်းရာတွင် အမှားရှိသည်"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ဖန်သားပြင် ရိုက်ကူးမှု စတင်ရာတွင် အမှားအယွင်းရှိနေသည်"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ဖန်သားပြင်အပြည့် ကြည့်နေသည်"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ထွက်ရန် အပေါ်မှ အောက်သို့ ပွတ်ဆွဲပါ။"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"နားလည်ပြီ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"နောက်သို့"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ပင်မစာမျက်နှာ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"မီနူး"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"မျှဝေ၊ ရုပ်သံဖမ်း (သို့) ကာစ်လုပ်သည့်အခါ Android သည် သင့်ဖန်သားပြင်တွင် မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့ အရာများကို ဂရုစိုက်ပါ။"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"အက်ပ်တစ်ခုဖြင့် မျှဝေ၊ ရုပ်သံဖမ်း (သို့) ကာစ်လုပ်သည့်အခါ Android သည် ယင်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့အရာများကို ဂရုစိုက်ပါ။"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"စတင်ရန်"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"သင်၏ IT စီမံခန့်ခွဲသူက ပိတ်ထားသည်"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ကိရိယာ မူဝါဒက ဖန်သားပြင်ပုံဖမ်းခြင်းကို ပိတ်ထားသည်"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံးရှင်းရန်"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 755ee81..9277d52 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Trykk for å se"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Feil ved lagring av skjermopptaket"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Feil ved start av skjermopptaket"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visning i fullskjerm"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Sveip ned fra toppen for å avslutte."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Skjønner"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Tilbake"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Startside"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meny"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Når du deler, tar opp eller caster noe, har Android tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med for eksempel passord, betalingsopplysninger, meldinger, bilder, lyd og video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Når du deler, tar opp eller caster en app, har Android tilgang til alt som vises eller spilles av i den aktuelle appen. Derfor bør du være forsiktig med for eksempel passord, betalingsopplysninger, meldinger, bilder, lyd og video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Begynn"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokkert av IT-administratoren"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skjermdumper er deaktivert av enhetsinnstillingene"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Fjern alt"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index d1445e1..9f9bf85 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"हेर्नका लागि ट्याप गर्नुहोस्"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"स्क्रिन रेकर्डिङ सेभ गर्ने क्रममा त्रुटि भयो"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"स्क्रिन रेकर्ड गर्न थाल्ने क्रममा त्रुटि भयो"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"पूरा पर्दा हेर्दै"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"बाहिर निस्कन, माथिबाट तल स्वाइप गर्नुहोस्।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"बुझेँ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"पछाडि"</string>
<string name="accessibility_home" msgid="5430449841237966217">"गृह"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"मेनु"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा Android ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिने सबै कुरा हेर्न तथा प्रयोग गर्न सक्छ। त्यसैले पासवर्ड, भुक्तानीसम्बन्धी विवरण, म्यासेज, फोटो र अडियो तथा भिडियो जस्ता कुरा हेर्दा वा प्ले गर्दा सावधानी अपनाउनुहोला।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"तपाईंले कुनै एप सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा Android ले उक्त एपमा देखाइने वा प्ले गरिने सबै कुरा हेर्न तथा प्रयोग गर्न सक्छ। त्यसैले पासवर्ड, भुक्तानीसम्बन्धी विवरण, म्यासेज, फोटो र अडियो तथा भिडियो जस्ता कुरा हेर्दा वा प्ले गर्दा सावधानी अपनाउनुहोला।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"सुरु गर्नुहोस्"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"तपाईंका IT एड्मिनले ब्लक गर्नुभएको छ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिभाइसको नीतिका कारण स्क्रिन क्याप्चर गर्ने सुविधा अफ गरिएको छ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सबै हटाउनुहोस्"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 932889b..5c8ba84 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tik om te bekijken"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Fout bij opslaan van schermopname"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Fout bij starten van schermopname"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Volledig scherm wordt getoond"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Swipe omlaag vanaf de bovenkant van het scherm om af te sluiten."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Terug"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Startscherm"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Als je deelt, opneemt of cast, heeft Android toegang tot alles dat zichtbaar is op je scherm of wordt afgespeeld op je apparaat. Wees daarom voorzichtig met bijvoorbeeld wachtwoorden, betalingsgegevens, berichten, foto\'s, en audio en video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Als je deelt, opneemt of cast, heeft Android toegang tot alles dat wordt getoond of afgespeeld in die app. Wees daarom voorzichtig met bijvoorbeeld wachtwoorden, betalingsgegevens, berichten, foto\'s, en audio en video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Starten"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Geblokkeerd door je IT-beheerder"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Schermopname staat uit vanwege apparaatbeleid"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Alles wissen"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 8061fd6..e41e7c2 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ଦେଖିବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ସ୍କ୍ରିନ ରେକର୍ଡିଂ ସେଭ କରିବାରେ ତ୍ରୁଟି"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ସ୍କ୍ରିନ୍ ରେକର୍ଡିଂ ଆରମ୍ଭ କରିବାରେ ତ୍ରୁଟି"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନରେ ଦେଖିବା"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ବାହାରି ଯିବା ପାଇଁ, ଶୀର୍ଷରୁ ତଳକୁ ସ୍ୱାଇପ କରନ୍ତୁ।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ବୁଝିଗଲି"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ଫେରନ୍ତୁ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ହୋମ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ମେନୁ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ, ଫଟୋ ଏବଂ ଅଡିଓ ଓ ଭିଡିଓ ପରି ବିଷୟଗୁଡ଼ିକ ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ଆପଣ ଏକ ଆପ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, ସେହି ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ Androidର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ, ଫଟୋ ଏବଂ ଅଡିଓ ଓ ଭିଡିଓ ପରି ବିଷୟଗୁଡ଼ିକ ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ଆରମ୍ଭ କରନ୍ତୁ"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ଆପଣଙ୍କ IT ଆଡମିନଙ୍କ ଦ୍ୱାରା ବ୍ଲକ କରାଯାଇଛି"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ଡିଭାଇସ ନୀତି ଦ୍ୱାରା ସ୍କ୍ରିନ କେପଚରିଂକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ସବୁ ଖାଲି କରନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 0f3ef6a..cc63775 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"ਦੇਖਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਨੂੰ ਰੱਖਿਅਤ ਕਰਨ ਵੇਲੇ ਗੜਬੜ ਹੋ ਗਈ"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਵੇਲੇ ਗੜਬੜ ਹੋਈ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ਪੂਰੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦੇਖੋ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ਬਾਹਰ ਜਾਣ ਲਈ, ਉਪਰੋਂ ਹੇਠਾਂ ਸਵਾਈਪ ਕਰੋ।"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ਸਮਝ ਲਿਆ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ਪਿੱਛੇ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ਘਰ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"ਮੀਨੂ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, Android ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਵਾਸਤੇ ਸਾਵਧਾਨ ਰਹੋ।"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, Android ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ਸ਼ੁਰੂ ਕਰੋ"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ਤੁਹਾਡੇ ਆਈ.ਟੀ. ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਬਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ਡੀਵਾਈਸ ਨੀਤੀ ਦੇ ਕਾਰਨ ਸਕ੍ਰੀਨ ਕੈਪਚਰ ਕਰਨਾ ਬੰਦ ਹੈ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 757ffcd..21018f9 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Kliknij, aby wyświetlić"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Podczas zapisywania nagrania ekranu wystąpił błąd"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Błąd podczas rozpoczynania rejestracji zawartości ekranu"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Włączony pełny ekran"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Aby wyjść, przesuń palcem z góry na dół."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Wróć"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ekran główny"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Podczas udostępniania, nagrywania lub przesyłania treści Android ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Podczas udostępniania, nagrywania lub przesyłania treści Android ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Rozpocznij"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Zablokowane przez administratora IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Zrzuty ekranu są wyłączone zgodnie z zasadami dotyczącymi urządzeń"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Usuń wszystkie"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 4ee6bd2..82015ea 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao salvar a gravação da tela"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Erro ao iniciar a gravação de tela"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização em tela cheia"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize de cima para baixo."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendi"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Voltar"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando você compartilha, grava ou transmite a tela, o Android tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando você compartilha, grava ou transmite um app, o Android tem acesso a todas as informações visíveis ou reproduzidas nele. Tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Início"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Ação bloqueada pelo administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de tela foi desativada pela política do dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Remover tudo"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index f16b027..45853b9 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao guardar a gravação de ecrã"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ocorreu um erro ao iniciar a gravação do ecrã."</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização de ecrã inteiro"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize rapidamente para baixo a partir da parte superior."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Anterior"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,10 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando está a partilhar, gravar ou transmitir conteúdo, o Android tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com, por exemplo, palavras-passe, detalhes de pagamento, mensagens, fotos, áudio e vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando está a partilhar, gravar ou transmitir uma app, o Android tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com, por exemplo, palavras-passe, detalhes de pagamento, mensagens, fotos, áudio e vídeo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Iniciar"</string>
+ <string name="media_projection_task_switcher_text" msgid="590885489897412359">"A partilha é pausada quando muda de app"</string>
+ <string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Partilhar antes esta app"</string>
+ <string name="media_projection_task_switcher_action_back" msgid="5324164224147845282">"Voltar"</string>
+ <string name="media_projection_task_switcher_notification_channel" msgid="7613206306777814253">"Mudança de app"</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueado pelo administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de ecrã está desativada pela política do dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 4ee6bd2..82015ea 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Toque para ver"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Erro ao salvar a gravação da tela"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Erro ao iniciar a gravação de tela"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visualização em tela cheia"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Para sair, deslize de cima para baixo."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Entendi"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Voltar"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Página inicial"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Quando você compartilha, grava ou transmite a tela, o Android tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Quando você compartilha, grava ou transmite um app, o Android tem acesso a todas as informações visíveis ou reproduzidas nele. Tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Início"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Ação bloqueada pelo administrador de TI"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de tela foi desativada pela política do dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Remover tudo"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index e57eb5d..bc6a5fd 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Atinge pentru a afișa"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Eroare la salvarea înregistrării ecranului"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Eroare la începerea înregistrării ecranului"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Vizualizare pe ecran complet"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Pentru a ieși, glisează de sus în jos."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Înapoi"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ecranul de pornire"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meniu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Când permiți accesul, înregistrezi sau proiectezi, Android are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Prin urmare, ai grijă cu parolele, detaliile de plată, mesajele, fotografiile și conținutul audio și video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Când permiți accesul, înregistrezi sau proiectezi o aplicație, Android are acces la orice se afișează pe ecran sau se redă în aplicație. Prin urmare, ai grijă cu informații cum ar fi parolele, detaliile de plată, mesajele, fotografiile și conținutul audio și video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Începe"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocată de administratorul IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Capturile de ecran sunt dezactivate de politica privind dispozitivele"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Șterge toate notificările"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 70f6e15..80b0d0f 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Нажмите, чтобы посмотреть."</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Не удалось сохранить запись видео с экрана."</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Не удалось начать запись видео с экрана."</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Полноэкранный режим"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Чтобы выйти, проведите по экрану сверху вниз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"ОК"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Главный экран"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Когда вы демонстрируете, транслируете экран или записываете видео с него, система Android получает доступ ко всему, что видно или воспроизводится на устройстве. Поэтому будьте осторожны с паролями, сведениями о способах оплаты, сообщениями, фотографиями, аудио- и видеозаписями."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Когда вы демонстрируете, записываете или транслируете экран приложения, система Android получает доступ ко всему, что видно или воспроизводится в нем. Поэтому будьте осторожны с паролями, сведениями о способах оплаты, сообщениями, фотографиями, аудио- и видеозаписями."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Начать"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблокировано вашим администратором"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Запись экрана отключена в соответствии с правилами для устройства."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index b4dc0f2..8989b7e 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"බැලීමට තට්ටු කරන්න"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"තිර පටිගත කිරීම සුරැකීමේ දෝෂයකි"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"තිර පටිගත කිරීම ආරම්භ කිරීමේ දෝෂයකි"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"මුළු තිරය බලමින්"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"ඉවත් වීමට, ඉහළ සිට පහළට ස්වයිප් කරන්න"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"වැටහුණි"</string>
<string name="accessibility_back" msgid="6530104400086152611">"ආපසු"</string>
<string name="accessibility_home" msgid="5430449841237966217">"මුල් පිටුව"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"මෙනුව"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"ඔබ බෙදා ගන්නා විට, පටිගත කරන විට, හෝ විකාශය කරන විට, Android හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්රවේශය ඇත. ඒ නිසා මුරපද, ගෙවීම් විස්තර, පණිවුඩ, ඡායාරූප, සහ ශ්රව්ය සහ දෘශ්ය වැනි දේවල් පිළිබඳ ප්රවේශම් වන්න."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"ඔබ යෙදුමක් බෙදා ගන්නා විට, පටිගත කරන විට හෝ විකාශය කරන විට, Android හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්රවේශය ඇත. ඒ නිසා මුරපද, ගෙවීම් විස්තර, පණිවුඩ, ඡායාරූප, සහ ශ්රව්ය සහ දෘශ්ය වැනි දේවල් පිළිබඳ ප්රවේශම් වන්න."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"අරඹන්න"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ඔබේ IT පරිපාලක විසින් අවහිර කර ඇත"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"උපාංග ප්රතිපත්තිය මගින් තිර ග්රහණය කිරීම අබල කර ඇත"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"සියල්ල හිස් කරන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 802bd7a..e743d56 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Zobrazte klepnutím"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Pri ukladaní nahrávky obrazovky sa vyskytla chyba"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Pri spustení nahrávania obrazovky sa vyskytla chyba"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Zobrazenie na celú obrazovku"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Ukončíte potiahnutím zhora nadol."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Dobre"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Späť"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Plocha"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Počas zdieľania, nahrávania alebo prenosu bude mať Android prístup k všetkému, čo sa zobrazuje na obrazovke alebo prehráva v zariadení. Preto zvýšte pozornosť v prípade položiek, ako sú heslá, platobné údaje, správy, fotky a zvuk či video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Počas zdieľania, nahrávania alebo prenosu v aplikácii bude mať Android prístup k všetkému zobrazovanému alebo prehrávaného obsahu v danej aplikácii. Preto zvýšte pozornosť v prípade položiek, ako sú heslá, platobné údaje, správy, fotky a zvuk či video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začať"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokované vaším správcom IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snímanie obrazovky je zakázané pravidlami pre zariadenie"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Vymazať všetko"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index a303e20..90d62cb 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Dotaknite se za ogled."</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Napaka pri shranjevanju posnetka zaslona"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Napaka pri začenjanju snemanja zaslona"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Vklopljen je celozaslonski način."</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Zaprete ga tako, da z vrha s prstom povlečete navzdol."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Razumem"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Nazaj"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Začetni zaslon"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meni"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Pri deljenju, snemanju ali predvajanju ima Android dostop do vsega, kar je prikazano na zaslonu ali se predvaja v napravi. Zato bodite previdni z gesli, podatki za plačilo, sporočili, fotografijami ter z zvokom in videom."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Pri deljenju, snemanju ali predvajanju aplikacije ima Android dostop do vsega, kar je prikazano ali predvajano v tej aplikaciji, zato bodite previdni z gesli, podatki za plačilo, sporočili, fotografijami ter z zvokom in videom."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Začni"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokiral skrbnik za IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Zajemanje zaslonske slike je onemogočil pravilnik za naprave."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši vse"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 4d0dcef..3677bdee 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Trokit për të parë"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Gabim gjatë ruajtjes së regjistrimit të ekranit"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Gabim gjatë nisjes së regjistrimit të ekranit"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Po shikon ekranin e plotë"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Për të dalë, rrëshqit nga lart poshtë."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"E kuptova"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Prapa"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Faqja bazë"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menyja"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kur ti ndan, regjistron ose transmeton, Android ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj, ki kujdes me gjërat si fjalëkalimet, detajet e pagesave, mesazhet, fotografitë, si dhe audion dhe videon."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kur ti ndan, regjistron ose transmeton një aplikacion, Android ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me gjërat si fjalëkalimet, detajet e pagesave, mesazhet, fotografitë, si dhe audion dhe videon."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Nis"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"U bllokua nga administratori yt i teknologjisë së informacionit"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Regjistrimi i ekranit është çaktivizuar nga politika e pajisjes."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Pastroji të gjitha"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 6227d5d..19d3065 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Додирните да бисте прегледали"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Грешка при чувању снимка екрана"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Грешка при покретању снимања екрана"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Приказује се цео екран"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Да бисте изашли, превуците надоле одозго."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Важи"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Почетна"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Мени"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Када делите, снимате или пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Када делите, снимате или пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Покрени"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокира ИТ администратор"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Снимање екрана је онемогућено смерницама за уређај"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Обриши све"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 7cf096f4..ae199e9 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Tryck för att visa"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Det gick inte att spara skärminspelningen"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Det gick inte att starta skärminspelningen"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Visar på fullskärm"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Svep nedåt från skärmens överkant för att avsluta."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Tillbaka"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Startsida"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Meny"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"När du delar, spelar in eller castar har Android åtkomst till allt som visas på skärmen eller spelas upp på enheten. Var försiktig med sådant som lösenord, betalningsuppgifter, meddelanden, foton och ljud och video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"När du delar, spelar in eller castar en app har Android åtkomst till allt som visas eller spelas upp i appen. Var försiktig med sådant som lösenord, betalningsuppgifter, meddelanden, foton och ljud och video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Börja"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blockeras av IT-administratören"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skärminspelning är inaktiverat av enhetspolicyn"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Rensa alla"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 892d369..abed50d 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Gusa ili uangalie"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Hitilafu imetokea wakati wa kuhifadhi rekodi ya skrini"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Hitilafu imetokea wakati wa kuanza kurekodi skrini"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Unatazama kwenye skrini nzima"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Ili uondoke, telezesha kidole kutoka juu hadi chini."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Nimeelewa"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Nyuma"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Nyumbani"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Unaposhiriki, kurekodi au kutuma, Android inaweza kufikia kitu chochote kitakachoonekana kwenye skrini yako au kuchezwa kwenye kifaa chako. Kwa hivyo kuwa mwangalifu na vitu kama vile manenosiri, maelezo ya malipo, ujumbe, picha na sauti na video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Unaposhiriki, kurekodi au kutuma programu, Android inaweza kufikia kitu chochote kitakachoonekana au kuchezwa kwenye programu hiyo. Kwa hivyo kuwa mwangalifu na vitu kama vile manenosiri, maelezo ya malipo, ujumbe, picha na sauti na video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Anza"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Umezuiwa na msimamizi wako wa TEHAMA"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Mchakato wa kurekodi skrini umezimwa na sera ya kifaa"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Futa zote"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 3378e13..8d930a5 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"பார்க்கத் தட்டவும்"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"ஸ்கிரீன் ரெக்கார்டிங்கைச் சேமிப்பதில் பிழை ஏற்பட்டது"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"ஸ்கிரீன் ரெக்கார்டிங்கைத் தொடங்குவதில் பிழை"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"முழுத் திரையில் காட்டுகிறது"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"வெளியேற, மேலிருந்து கீழே ஸ்வைப் செய்யவும்"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"புரிந்தது"</string>
<string name="accessibility_back" msgid="6530104400086152611">"பின்செல்"</string>
<string name="accessibility_home" msgid="5430449841237966217">"முகப்பு"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"மெனு"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ உங்கள் திரையில் காட்டப்படுகின்ற அல்லது சாதனத்தில் பிளே செய்யப்படுகின்ற அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"நீங்கள் ஓர் ஆப்ஸைப் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ அந்த ஆப்ஸில் காட்டப்படுகின்ற அல்லது பிளே செய்யப்படுகின்ற அனைத்தையும் Android அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"தொடங்கு"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"உங்கள் IT நிர்வாகி தடுத்துள்ளார்"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"\'திரையைப் படமெடுத்தல்\' சாதனக் கொள்கையின்படி முடக்கப்பட்டுள்ளது"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"எல்லாவற்றையும் அழி"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 9e50548..f90a435 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"చూడటానికి ట్యాప్ చేయండి"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"స్క్రీన్ రికార్డింగ్ను సేవ్ చేయడంలో ఎర్రర్ ఏర్పడింది"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"స్క్రీన్ రికార్డింగ్ ప్రారంభించడంలో ఎర్రర్ ఏర్పడింది"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"ఫుల్ స్క్రీన్లో చూస్తున్నారు"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"నిష్క్రమించడానికి, పై నుండి కిందికి స్వైప్ చేయండి."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"సరే"</string>
<string name="accessibility_back" msgid="6530104400086152611">"వెనుకకు"</string>
<string name="accessibility_home" msgid="5430449841237966217">"హోమ్"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"మెనూ"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"మీరు షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, మీ స్క్రీన్పై కనిపించే దేనికైనా లేదా మీ పరికరంలో ప్లే అయిన దేనికైనా Androidకు యాక్సెస్ ఉంటుంది. కాబట్టి పాస్వర్డ్లు, పేమెంట్ వివరాలు, మెసేజ్లు, ఫోటోలు, ఆడియో, ఇంకా వీడియో వంటి విషయాల్లో జాగ్రత్త వహించండి."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"మీరు ఏదైనా యాప్ను షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, ఆ యాప్లో చూపబడిన దేనికైనా లేదా ప్లే అయిన దేనికైనా Androidకు యాక్సెస్ ఉంటుంది. కాబట్టి పాస్వర్డ్లు, పేమెంట్ వివరాలు, మెసేజ్లు, ఫోటోలు, ఆడియో, ఇంకా వీడియో వంటి విషయాల్లో జాగ్రత్త వహించండి."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"ప్రారంభించండి"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"మీ IT అడ్మిన్ ద్వారా బ్లాక్ చేయబడింది"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"పరికర పాలసీ ద్వారా స్క్రీన్ క్యాప్చర్ చేయడం డిజేబుల్ చేయబడింది"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"అన్నీ క్లియర్ చేయండి"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 554324a..8043792 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"แตะเพื่อดู"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"เกิดข้อผิดพลาดในการบันทึกหน้าจอ"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"เกิดข้อผิดพลาดขณะเริ่มบันทึกหน้าจอ"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"กำลังดูแบบเต็มหน้าจอ"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"หากต้องการออก ให้เลื่อนลงจากด้านบน"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"รับทราบ"</string>
<string name="accessibility_back" msgid="6530104400086152611">"กลับ"</string>
<string name="accessibility_home" msgid="5430449841237966217">"หน้าแรก"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"เมนู"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"เมื่อกำลังแชร์ บันทึก หรือแคสต์ Android จะมีสิทธิ์เข้าถึงทุกสิ่งที่ปรากฏบนหน้าจอหรือเล่นอยู่ในอุปกรณ์ ดังนั้นโปรดระวังสิ่งต่างๆ อย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ รูปภาพ รวมถึงเสียงและวิดีโอ"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"เมื่อกำลังแชร์ บันทึก หรือแคสต์แอป Android จะมีสิทธิ์เข้าถึงทุกสิ่งที่แสดงหรือเล่นอยู่ในแอปดังกล่าว ดังนั้นโปรดระวังสิ่งต่างๆ อย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ รูปภาพ รวมถึงเสียงและวิดีโอ"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"เริ่ม"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ผู้ดูแลระบบไอทีบล็อกไว้"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"การจับภาพหน้าจอปิดใช้โดยนโยบายด้านอุปกรณ์"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ล้างทั้งหมด"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 2c632ec..835c864 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"I-tap para tingnan"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Nagka-error sa pag-save ng recording ng screen"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Nagkaroon ng error sa pagsisimula ng pag-record ng screen"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Panonood sa full screen"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Upang lumabas, mag-swipe mula sa itaas pababa."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Nakuha ko"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Bumalik"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Home"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kapag nagbabahagi, nagre-record, o nagka-cast ka, may access ang Android sa kahit anong nakikita sa iyong screen o pine-play sa device mo. Kaya mag-ingat sa mga bagay-bagay tulad ng mga password, detalye ng pagbabayad, mensahe, larawan, at audio at video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kapag nagbabahagi, nagre-record, o nagka-cast ka ng app, may access ang Android sa kahit anong ipinapakita o pine-play sa app na iyon. Kaya mag-ingat sa mga bagay-bagay tulad ng mga password, detalye ng pagbabayad, mensahe, larawan, at audio at video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Simulan"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Na-block ng iyong IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Naka-disable ang pag-screen capture ayon sa patakaran ng device"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"I-clear lahat"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 8430771..3f05efa 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Görüntülemek için dokunun"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran kaydı saklanırken hata oluştu"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ekran kaydı başlatılırken hata oluştu"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Tam ekran olarak görüntüleme"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Çıkmak için yukarıdan aşağıya doğru hızlıca kaydırın."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Anladım"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Geri"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ana sayfa"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menü"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Paylaşma, kaydetme veya yayınlama özelliğini kullandığınızda Android, ekranınızda gösterilen veya cihazınızda oynatılan her şeye erişebilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Bir uygulamayı paylaştığınızda, kaydettiğinizde veya yayınladığınızda Android, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Başlat"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"BT yöneticiniz tarafından engellendi"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekran görüntüsü alma, cihaz politikası tarafından devre dışı bırakıldı"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tümünü temizle"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index b3ee948..d4b9c34 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Натисніть, щоб переглянути"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Не вдалося зберегти запис відео з екрана"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Не вдалося почати запис екрана"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Перегляд на весь екран"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Щоб вийти, проведіть пальцем зверху вниз."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Назад"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Головна"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Меню"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Коли ви показуєте, записуєте або транслюєте екран, ОС Android отримує доступ до всього, що відображається на ньому чи відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, фотографіями, аудіо, відео, платіжною інформацією тощо."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Коли ви показуєте, записуєте або транслюєте додаток, ОС Android отримує доступ до всього, що відображається або відтворюється в ньому. Тому будьте уважні з паролями, повідомленнями, фотографіями, аудіо, відео, платіжною інформацією тощо."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Почати"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблоковано адміністратором"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Запис екрана вимкнено згідно з правилами для пристрою"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Очистити все"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 3f47187..acf1357 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"دیکھنے کے لیے تھپتھپائیں"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"اسکرین ریکارڈنگ محفوظ کرنے میں خرابی"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"اسکرین ریکارڈنگ شروع کرنے میں خرابی"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"فُل اسکرین میں دیکھ رہے ہیں"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"باہر نکلنے کیلئے اوپر سے نیچے کی طرف سوائپ کریں۔"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"سمجھ آ گئی"</string>
<string name="accessibility_back" msgid="6530104400086152611">"واپس جائیں"</string>
<string name="accessibility_home" msgid="5430449841237966217">"ہوم"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"مینیو"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو Android کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ لہذا، پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، تصاویر، ساتھ ہی آڈیو اور ویڈیو جیسی چیزوں کے سلسلے میں محتاط رہیں۔"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"جب آپ اشتراک، ریکارڈنگ یا کسی ایپ کو کاسٹ کر رہے ہوتے ہیں تو Android کو اس ایپ پر دکھائی گئی یا چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ لہذا، پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، تصاویر، ساتھ ہی آڈیو اور ویڈیو جیسی چیزوں کے سلسلے میں محتاط رہیں۔"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"شروع کریں"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"آپ کے IT منتظم نے مسدود کر دیا"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"اسکرین کو کیپچر کرنا آلہ کی پالیسی کے ذریعے غیر فعال ہے"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"سبھی کو صاف کریں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 499eb1b..2af9e92 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Koʻrish uchun bosing"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Ekran yozuvi saqlanmadi"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Ekranni yozib olish boshlanmadi"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Butun ekranli rejim"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Chiqish uchun tepadan pastga torting."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Orqaga"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Uyga"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menyu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Ulashish, yozib olish va translatsiya qilish vaqtida Android ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar, suratlar, audio va video chiqmasligi uchun ehtiyot boʻling."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Ilovani ulashish, yozib olish yoki translatsiya qilayotganingizda Android ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar, suratlar, audio va video chiqmasligi uchun ehtiyot boʻling."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Boshlash"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"AT administratoringiz tomonidan bloklangan"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekranni tasvirga olish qurilmadan foydalanish tartibi tomonidan faolsizlantirilgan"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hammasini tozalash"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 1d96b1e..791337d 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Nhấn để xem"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Có lỗi xảy ra khi lưu video ghi màn hình"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Lỗi khi bắt đầu ghi màn hình"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Xem toàn màn hình"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Để thoát, hãy vuốt từ trên cùng xuống dưới."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"OK"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Quay lại"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Trang chủ"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Menu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Khi bạn chia sẻ, ghi hoặc truyền, Android sẽ có quyền truy cập vào mọi nội dung xuất hiện trên màn hình hoặc phát trên thiết bị của bạn. Vì vậy, hãy thận trọng để không làm lộ thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Khi bạn chia sẻ, ghi hoặc truyền ứng dụng, Android sẽ có quyền truy cập vào mọi nội dung xuất hiện hoặc phát trên ứng dụng đó. Vì vậy, hãy thận trọng để không làm lộ các thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Bắt đầu"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bị quản trị viên CNTT chặn"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Tính năng chụp ảnh màn hình đã bị tắt theo chính sách thiết bị"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Xóa tất cả"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 0699551..bc289ee 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"点按即可查看"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"保存屏幕录制内容时出错"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"启动屏幕录制时出错"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"目前处于全屏模式"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"要退出,请从顶部向下滑动。"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"知道了"</string>
<string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
<string name="accessibility_home" msgid="5430449841237966217">"主屏幕"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"菜单"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"在分享内容时,Android 可以访问屏幕上显示或设备中播放的所有内容。因此,请务必小心操作,谨防密码、付款信息、消息、照片、音频和视频等内容遭到泄露。"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"在分享、录制或投放内容时,Android 可以访问通过此应用显示或播放的所有内容。因此,请务必小心操作,谨防密码、付款信息、消息、照片、音频和视频等内容遭到泄露。"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"开始"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"已被 IT 管理员禁止"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"设备政策已停用屏幕截图功能"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 7160b56..8db6dbd 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"輕按即可查看"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"儲存螢幕錄影時發生錯誤"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"開始錄影畫面時發生錯誤"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"開啟全螢幕"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"由頂部向下滑動即可退出。"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"知道了"</string>
<string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
<string name="accessibility_home" msgid="5430449841237966217">"首頁"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"選單"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"當你分享、錄影或投放時,Android 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此,請謹慎處理密碼、付款資料、訊息、相片、音訊和影片等。"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"當你分享、錄影或投放應用程式時,Android 可存取顯示在該應用程式中顯示或播放的所有內容。因此,請謹慎處理密碼、付款資料、訊息、相片、音訊和影片等。"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"已被你的 IT 管理員封鎖"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"螢幕截圖功能因裝置政策而停用"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 0deba33..9ba2aa9 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"輕觸即可查看"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"儲存螢幕錄影內容時發生錯誤"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"開始錄製螢幕畫面時發生錯誤"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"以全螢幕檢視"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"如要退出,請從畫面頂端向下滑動。"</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"知道了"</string>
<string name="accessibility_back" msgid="6530104400086152611">"返回"</string>
<string name="accessibility_home" msgid="5430449841237966217">"主畫面"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"選單"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"當你分享、錄製或投放內容時,Android 將可存取畫面上顯示的任何資訊或裝置播放的任何內容。因此,請謹慎處理密碼、付款資料、訊息、相片和影音內容等資訊。"</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"當你分享、錄製或投放內容時,Android 可存取應用程式中顯示的任何資訊或播放的任何內容。因此,請謹慎處理密碼、付款資料、訊息、相片和影音內容等資訊。"</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"開始"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 管理員已封鎖這項操作"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"根據裝置政策規定,螢幕畫面擷取功能已停用"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index eb8ee45..0e6bde2 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -118,6 +118,9 @@
<string name="screenrecord_save_text" msgid="3008973099800840163">"Thepha ukuze ubuke"</string>
<string name="screenrecord_save_error" msgid="5862648532560118815">"Iphutha lokulondoloza okokuqopha iskrini"</string>
<string name="screenrecord_start_error" msgid="2200660692479682368">"Iphutha lokuqala ukurekhoda isikrini"</string>
+ <string name="immersive_cling_title" msgid="8372056499315585941">"Ukubuka isikrini esigcwele"</string>
+ <string name="immersive_cling_description" msgid="6913958856085185775">"Ukuze uphume, swayiphela phansi kusuka phezulu."</string>
+ <string name="immersive_cling_positive" msgid="3076681691468978568">"Ngiyezwa"</string>
<string name="accessibility_back" msgid="6530104400086152611">"Emuva"</string>
<string name="accessibility_home" msgid="5430449841237966217">"Ekhaya"</string>
<string name="accessibility_menu" msgid="2701163794470513040">"Imenyu"</string>
@@ -412,6 +415,14 @@
<string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Uma wabelana, ukurekhoda, noma ukusakaza, i-Android inokufinyelela kunoma yini ebonakala esikrinini sakho noma okudlalwayo kudivayisi yakho. Ngakho-ke qaphela ngezinto ezifana namaphasiwedi, imininingwane yokukhokha, imilayezo, izithombe, nomsindo nevidiyo."</string>
<string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Uma wabelana, ukurekhoda, noma ukusakaza ku-app, i-Android inokufinyelela kunoma yini eboniswayo noma edlalwa kuleyo app. Ngakho-ke qaphela ngezinto ezfana namaphasiwedi, imininingwane yokukhokha, imilayezo, izithombe, nomsindo nevidiyo."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Qala"</string>
+ <!-- no translation found for media_projection_task_switcher_text (590885489897412359) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_switch (8682258717291921123) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_action_back (5324164224147845282) -->
+ <skip />
+ <!-- no translation found for media_projection_task_switcher_notification_channel (7613206306777814253) -->
+ <skip />
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Kuvinjelwe ngumlawuli wakho we-IT"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ukuthwebula isikrini kukhutshazwe yinqubomgomo yedivayisi"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Sula konke"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 47cd1e7..3366f4f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -663,9 +663,6 @@
<dimen name="restricted_padlock_pading">4dp</dimen>
- <!-- How far the expanded QS panel peeks from the header in collapsed state. -->
- <dimen name="qs_peek_height">0dp</dimen>
-
<!-- Padding between subtitles and the following text in the QSFooter dialog -->
<dimen name="qs_footer_dialog_subtitle_padding">20dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 134a7a9..3a2177a 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -211,6 +211,7 @@
<item type="id" name="keyguard_indication_area" />
<item type="id" name="keyguard_indication_text" />
<item type="id" name="keyguard_indication_text_bottom" />
+ <item type="id" name="nssl_guideline" />
<item type="id" name="lock_icon" />
<item type="id" name="lock_icon_bg" />
</resources>
diff --git a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
deleted file mode 100644
index b7d4b3a..0000000
--- a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ 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
- -->
-<ConstraintSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto" >
-
- <Constraint
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_collapsed"
- />
-
- <!-- Only the constraintBottom and marginBottom are different. The rest of the constraints are
- the same as the constraints in media_recommendations_expanded.xml. But, due to how
- ConstraintSets work, all the constraints need to be in the same place. So, the shared
- constraints can't be put in the shared layout file media_smartspace_recommendations.xml and
- the constraints are instead duplicated between here and media_recommendations_expanded.xml.
- Ditto for the other cover containers. -->
- <Constraint
- android:id="@+id/media_cover1_container"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_cover2_container"
- android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
- app:layout_constraintHorizontal_chainStyle="packed"
- app:layout_constraintHorizontal_bias="1.0"
- app:layout_constraintVertical_bias="0.5"
- />
-
- <Constraint
- android:id="@+id/media_title1"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_subtitle1"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_cover2_container"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/media_cover1_container"
- app:layout_constraintEnd_toStartOf="@id/media_cover3_container"
- android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
- app:layout_constraintVertical_bias="0.5"
- />
-
- <Constraint
- android:id="@+id/media_title2"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_subtitle2"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_cover3_container"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="parent"
- android:layout_marginEnd="@dimen/qs_media_padding"
- app:layout_constraintVertical_bias="0.5"
- />
-
- <Constraint
- android:id="@+id/media_title3"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/media_subtitle3"
- android:visibility="gone"
- />
-
-</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendation_expanded.xml b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
deleted file mode 100644
index ce25a7d..0000000
--- a/packages/SystemUI/res/xml/media_recommendation_expanded.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ 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
- -->
-<ConstraintSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- >
-
- <Constraint
- android:id="@+id/sizing_view"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_media_session_height_expanded"
- />
-
- <Constraint
- android:id="@+id/media_cover1_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@+id/media_title1"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_cover2_container"
- android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
- app:layout_constraintHorizontal_chainStyle="packed"
- app:layout_constraintVertical_chainStyle="packed"
- app:layout_constraintHorizontal_bias="1.0"
- app:layout_constraintVertical_bias="0.4"
- />
-
- <Constraint
- android:id="@+id/media_title1"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
- app:layout_constraintTop_toBottomOf="@+id/media_cover1_container"
- app:layout_constraintBottom_toTopOf="@+id/media_subtitle1"
- />
-
- <Constraint
- android:id="@+id/media_subtitle1"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- app:layout_constraintStart_toStartOf="@+id/media_cover1_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover1_container"
- app:layout_constraintTop_toBottomOf="@+id/media_title1"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- />
-
- <Constraint
- android:id="@+id/media_cover2_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/media_title2"
- app:layout_constraintStart_toEndOf="@id/media_cover1_container"
- app:layout_constraintEnd_toStartOf="@id/media_cover3_container"
- android:layout_marginEnd="@dimen/qs_media_rec_album_side_margin"
- app:layout_constraintVertical_chainStyle="packed"
- app:layout_constraintVertical_bias="0.4"
- />
-
- <Constraint
- android:id="@+id/media_title2"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
- app:layout_constraintTop_toBottomOf="@+id/media_cover2_container"
- app:layout_constraintBottom_toTopOf="@+id/media_subtitle2"
- />
-
- <Constraint
- android:id="@+id/media_subtitle2"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- app:layout_constraintStart_toStartOf="@+id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover2_container"
- app:layout_constraintTop_toBottomOf="@+id/media_title2"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- />
-
- <Constraint
- android:id="@+id/media_cover3_container"
- style="@style/MediaPlayer.Recommendation.AlbumContainer"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/media_title3"
- app:layout_constraintStart_toEndOf="@id/media_cover2_container"
- app:layout_constraintEnd_toEndOf="parent"
- android:layout_marginEnd="@dimen/qs_media_padding"
- app:layout_constraintVertical_chainStyle="packed"
- app:layout_constraintVertical_bias="0.4"
- />
-
- <Constraint
- android:id="@+id/media_title3"
- style="@style/MediaPlayer.Recommendation.Text.Title"
- app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
- app:layout_constraintTop_toBottomOf="@+id/media_cover3_container"
- app:layout_constraintBottom_toTopOf="@+id/media_subtitle3"
- />
-
- <Constraint
- android:id="@+id/media_subtitle3"
- style="@style/MediaPlayer.Recommendation.Text.Subtitle"
- app:layout_constraintStart_toStartOf="@+id/media_cover3_container"
- app:layout_constraintEnd_toEndOf="@+id/media_cover3_container"
- app:layout_constraintTop_toBottomOf="@+id/media_title3"
- app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="@dimen/qs_media_padding"
- />
-
-</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml b/packages/SystemUI/res/xml/media_recommendations_collapsed.xml
similarity index 100%
rename from packages/SystemUI/res/xml/media_recommendations_view_collapsed.xml
rename to packages/SystemUI/res/xml/media_recommendations_collapsed.xml
diff --git a/packages/SystemUI/res/xml/media_recommendations_view_expanded.xml b/packages/SystemUI/res/xml/media_recommendations_expanded.xml
similarity index 100%
rename from packages/SystemUI/res/xml/media_recommendations_view_expanded.xml
rename to packages/SystemUI/res/xml/media_recommendations_expanded.xml
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt
index 50e5466..1cb8e43 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt
@@ -12,12 +12,9 @@
) : FrameLayout(context, attrs) {
private var drawAlpha: Int = 255
- init {
- setLayerType(View.LAYER_TYPE_SOFTWARE, null)
- }
-
protected override fun onSetAlpha(alpha: Int): Boolean {
- drawAlpha = alpha
+ // Ignore alpha passed from View, prefer to compute it from set values
+ drawAlpha = (255 * this.alpha * transitionAlpha).toInt()
return true
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 2377057..d9b7bde 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -69,7 +69,7 @@
(long) (125 * KeyguardPatternView.DISAPPEAR_MULTIPLIER_LOCKED),
0.6f /* translationScale */,
0.45f /* delayScale */, AnimationUtils.loadInterpolator(
- mContext, android.R.interpolator.fast_out_linear_in));
+ mContext, android.R.interpolator.fast_out_linear_in));
mDisappearYTranslation = getResources().getDimensionPixelSize(
R.dimen.disappear_y_translation);
mYTrans = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
@@ -82,8 +82,10 @@
}
void onDevicePostureChanged(@DevicePostureInt int posture) {
- mLastDevicePosture = posture;
- updateMargins();
+ if (mLastDevicePosture != posture) {
+ mLastDevicePosture = posture;
+ updateMargins();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 38c07dc..2bdf46e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -104,8 +104,10 @@
}
void onDevicePostureChanged(@DevicePostureInt int posture) {
- mLastDevicePosture = posture;
- updateMargins();
+ if (mLastDevicePosture != posture) {
+ mLastDevicePosture = posture;
+ updateMargins();
+ }
}
private void updateMargins() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 5e84a57..35198de 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -164,13 +164,13 @@
import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent;
-import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.DetectionStatus;
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus;
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus;
import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.WeatherData;
@@ -1471,27 +1471,31 @@
private FaceAuthenticationListener mFaceAuthenticationListener =
new FaceAuthenticationListener() {
@Override
- public void onAuthenticationStatusChanged(@NonNull AuthenticationStatus status) {
- if (status instanceof AcquiredAuthenticationStatus) {
+ public void onAuthenticationStatusChanged(
+ @NonNull FaceAuthenticationStatus status
+ ) {
+ if (status instanceof AcquiredFaceAuthenticationStatus) {
handleFaceAcquired(
- ((AcquiredAuthenticationStatus) status).getAcquiredInfo());
- } else if (status instanceof ErrorAuthenticationStatus) {
- ErrorAuthenticationStatus error = (ErrorAuthenticationStatus) status;
+ ((AcquiredFaceAuthenticationStatus) status).getAcquiredInfo());
+ } else if (status instanceof ErrorFaceAuthenticationStatus) {
+ ErrorFaceAuthenticationStatus error =
+ (ErrorFaceAuthenticationStatus) status;
handleFaceError(error.getMsgId(), error.getMsg());
- } else if (status instanceof FailedAuthenticationStatus) {
+ } else if (status instanceof FailedFaceAuthenticationStatus) {
handleFaceAuthFailed();
- } else if (status instanceof HelpAuthenticationStatus) {
- HelpAuthenticationStatus helpMsg = (HelpAuthenticationStatus) status;
+ } else if (status instanceof HelpFaceAuthenticationStatus) {
+ HelpFaceAuthenticationStatus helpMsg =
+ (HelpFaceAuthenticationStatus) status;
handleFaceHelp(helpMsg.getMsgId(), helpMsg.getMsg());
- } else if (status instanceof SuccessAuthenticationStatus) {
+ } else if (status instanceof SuccessFaceAuthenticationStatus) {
FaceManager.AuthenticationResult result =
- ((SuccessAuthenticationStatus) status).getSuccessResult();
+ ((SuccessFaceAuthenticationStatus) status).getSuccessResult();
handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
}
}
@Override
- public void onDetectionStatusChanged(@NonNull DetectionStatus status) {
+ public void onDetectionStatusChanged(@NonNull FaceDetectionStatus status) {
handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric());
}
};
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 70b4371..a977966 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
@@ -64,14 +64,6 @@
val isUnlocked: StateFlow<Boolean>
/**
- * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
- * dismisses once the authentication challenge is completed. For example, completing a biometric
- * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
- * lock screen.
- */
- val isBypassEnabled: StateFlow<Boolean>
-
- /**
* Whether the auto confirm feature is enabled for the currently-selected user.
*
* Note that the length of the PIN is also important to take into consideration, please see
@@ -113,9 +105,6 @@
*/
suspend fun isLockscreenEnabled(): Boolean
- /** See [isBypassEnabled]. */
- fun setBypassEnabled(isBypassEnabled: Boolean)
-
/** Reports an authentication attempt. */
suspend fun reportAuthenticationAttempt(isSuccessful: Boolean)
@@ -157,7 +146,7 @@
private val lockPatternUtils: LockPatternUtils,
) : AuthenticationRepository {
- override val isUnlocked: StateFlow<Boolean> = keyguardRepository.isKeyguardUnlocked
+ override val isUnlocked = keyguardRepository.isKeyguardUnlocked
override suspend fun isLockscreenEnabled(): Boolean {
return withContext(backgroundDispatcher) {
@@ -166,9 +155,6 @@
}
}
- private val _isBypassEnabled = MutableStateFlow(false)
- override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled.asStateFlow()
-
override val isAutoConfirmEnabled: StateFlow<Boolean> =
userRepository.selectedUserInfo
.map { it.id }
@@ -225,10 +211,6 @@
}
}
- override fun setBypassEnabled(isBypassEnabled: Boolean) {
- _isBypassEnabled.value = isBypassEnabled
- }
-
override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
val selectedUserId = userRepository.selectedUserId
withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 3283e40..b482977 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -24,6 +24,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.keyguard.data.repository.KeyguardRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -51,6 +52,7 @@
private val repository: AuthenticationRepository,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val userRepository: UserRepository,
+ private val keyguardRepository: KeyguardRepository,
private val clock: SystemClock,
) {
/**
@@ -76,14 +78,6 @@
initialValue = true,
)
- /**
- * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
- * dismisses once the authentication challenge is completed. For example, completing a biometric
- * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
- * lock screen.
- */
- val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled
-
/** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling
@@ -156,6 +150,16 @@
}
/**
+ * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
+ * dismisses once the authentication challenge is completed. For example, completing a biometric
+ * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
+ * lock screen.
+ */
+ fun isBypassEnabled(): Boolean {
+ return keyguardRepository.isBypassEnabled()
+ }
+
+ /**
* Attempts to authenticate the user and unlock the device.
*
* If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method
@@ -218,11 +222,6 @@
return authenticationResult.isSuccessful
}
- /** See [isBypassEnabled]. */
- fun toggleBypassEnabled() {
- repository.setBypassEnabled(!repository.isBypassEnabled.value)
- }
-
/** Starts refreshing the throttling state every second. */
private suspend fun startThrottlingCountdown() {
cancelCountdown()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 7a2f244..9df56fc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -250,7 +250,7 @@
.setMessage(messageBody)
.setPositiveButton(android.R.string.ok, null)
.create();
- alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
alertDialog.show();
}
@@ -263,7 +263,7 @@
.setOnDismissListener(
dialog -> animateAway(AuthDialogCallback.DISMISSED_ERROR))
.create();
- alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
alertDialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 37ce444..083e21f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -17,12 +17,7 @@
import android.app.ActivityTaskManager
import android.content.Context
-import android.content.res.Configuration
-import android.graphics.Color
import android.graphics.PixelFormat
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
-import android.graphics.Rect
import android.hardware.biometrics.BiometricOverlayConstants
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
@@ -33,27 +28,23 @@
import android.hardware.fingerprint.ISidefpsController
import android.os.Handler
import android.util.Log
-import android.util.RotationUtils
import android.view.Display
import android.view.DisplayInfo
import android.view.Gravity
import android.view.LayoutInflater
import android.view.Surface
import android.view.View
-import android.view.View.AccessibilityDelegate
import android.view.ViewPropertyAnimator
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
-import android.view.accessibility.AccessibilityEvent
-import androidx.annotation.RawRes
import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieProperty
-import com.airbnb.lottie.model.KeyPath
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder
+import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -64,6 +55,7 @@
import com.android.systemui.util.traceSection
import java.io.PrintWriter
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -86,6 +78,7 @@
@Main private val mainExecutor: DelayableExecutor,
@Main private val handler: Handler,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val sideFpsOverlayViewModelFactory: Provider<SideFpsOverlayViewModel>,
@Application private val scope: CoroutineScope,
dumpManager: DumpManager
) : Dumpable {
@@ -250,105 +243,15 @@
private fun createOverlayForDisplay(@BiometricOverlayConstants.ShowReason reason: Int) {
val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
overlayView = view
- val display = context.display!!
- // b/284098873 `context.display.rotation` may not up-to-date, we use displayInfo.rotation
- display.getDisplayInfo(displayInfo)
- val offsets =
- sensorProps.getLocation(display.uniqueId).let { location ->
- if (location == null) {
- Log.w(TAG, "No location specified for display: ${display.uniqueId}")
- }
- location ?: sensorProps.location
- }
- overlayOffsets = offsets
-
- val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
- view.rotation =
- display.asSideFpsAnimationRotation(
- offsets.isYAligned(),
- getRotationFromDefault(displayInfo.rotation)
- )
- lottie.setAnimation(
- display.asSideFpsAnimation(
- offsets.isYAligned(),
- getRotationFromDefault(displayInfo.rotation)
- )
+ SideFpsOverlayViewBinder.bind(
+ view = view,
+ viewModel = sideFpsOverlayViewModelFactory.get(),
+ overlayViewParams = overlayViewParams,
+ reason = reason,
+ context = context,
)
- lottie.addLottieOnCompositionLoadedListener {
- // Check that view is not stale, and that overlayView has not been hidden/removed
- if (overlayView != null && overlayView == view) {
- updateOverlayParams(display, it.bounds)
- }
- }
orientationReasonListener.reason = reason
- lottie.addOverlayDynamicColor(context, reason)
-
- /**
- * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
- * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
- * in focus
- */
- view.setAccessibilityDelegate(
- object : AccessibilityDelegate() {
- override fun dispatchPopulateAccessibilityEvent(
- host: View,
- event: AccessibilityEvent
- ): Boolean {
- return if (
- event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- ) {
- true
- } else {
- super.dispatchPopulateAccessibilityEvent(host, event)
- }
- }
- }
- )
}
-
- @VisibleForTesting
- fun updateOverlayParams(display: Display, bounds: Rect) {
- val isNaturalOrientation = display.isNaturalOrientation()
- val isDefaultOrientation =
- if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation
- val size = windowManager.maximumWindowMetrics.bounds
-
- val displayWidth = if (isDefaultOrientation) size.width() else size.height()
- val displayHeight = if (isDefaultOrientation) size.height() else size.width()
- val boundsWidth = if (isDefaultOrientation) bounds.width() else bounds.height()
- val boundsHeight = if (isDefaultOrientation) bounds.height() else bounds.width()
-
- val sensorBounds =
- if (overlayOffsets.isYAligned()) {
- Rect(
- displayWidth - boundsWidth,
- overlayOffsets.sensorLocationY,
- displayWidth,
- overlayOffsets.sensorLocationY + boundsHeight
- )
- } else {
- Rect(
- overlayOffsets.sensorLocationX,
- 0,
- overlayOffsets.sensorLocationX + boundsWidth,
- boundsHeight
- )
- }
-
- RotationUtils.rotateBounds(
- sensorBounds,
- Rect(0, 0, displayWidth, displayHeight),
- getRotationFromDefault(display.rotation)
- )
-
- overlayViewParams.x = sensorBounds.left
- overlayViewParams.y = sensorBounds.top
-
- windowManager.updateViewLayout(overlayView, overlayViewParams)
- }
-
- private fun getRotationFromDefault(rotation: Int): Int =
- if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
}
private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorPropertiesInternal?
@@ -373,89 +276,12 @@
private fun ActivityTaskManager.topClass(): String =
getTasks(1).firstOrNull()?.topActivity?.className ?: ""
-@RawRes
-private fun Display.asSideFpsAnimation(yAligned: Boolean, rotationFromDefault: Int): Int =
- when (rotationFromDefault) {
- Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
- Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
- else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
- }
-
-private fun Display.asSideFpsAnimationRotation(yAligned: Boolean, rotationFromDefault: Int): Float =
- when (rotationFromDefault) {
- Surface.ROTATION_90 -> if (yAligned) 0f else 180f
- Surface.ROTATION_180 -> 180f
- Surface.ROTATION_270 -> if (yAligned) 180f else 0f
- else -> 0f
- }
-
private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
private fun Display.isNaturalOrientation(): Boolean =
rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
-private fun LottieAnimationView.addOverlayDynamicColor(
- context: Context,
- @BiometricOverlayConstants.ShowReason reason: Int
-) {
- fun update() {
- val isKeyguard = reason == REASON_AUTH_KEYGUARD
- if (isKeyguard) {
- val color =
- com.android.settingslib.Utils.getColorAttrDefaultColor(
- context,
- com.android.internal.R.attr.materialColorPrimaryFixed
- )
- val outerRimColor =
- com.android.settingslib.Utils.getColorAttrDefaultColor(
- context,
- com.android.internal.R.attr.materialColorPrimaryFixedDim
- )
- val chevronFill =
- com.android.settingslib.Utils.getColorAttrDefaultColor(
- context,
- com.android.internal.R.attr.materialColorOnPrimaryFixed
- )
- addValueCallback(KeyPath(".blue600", "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
- }
- addValueCallback(KeyPath(".blue400", "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(outerRimColor, PorterDuff.Mode.SRC_ATOP)
- }
- addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
- }
- } else {
- if (!isDarkMode(context)) {
- addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
- }
- }
- for (key in listOf(".blue600", ".blue400")) {
- addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(
- context.getColor(R.color.settingslib_color_blue400),
- PorterDuff.Mode.SRC_ATOP
- )
- }
- }
- }
- }
-
- if (composition != null) {
- update()
- } else {
- addLottieOnCompositionLoadedListener { update() }
- }
-}
-
-private fun isDarkMode(context: Context): Boolean {
- val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
- return darkMode == Configuration.UI_MODE_NIGHT_YES
-}
-
-@VisibleForTesting
-class OrientationReasonListener(
+public class OrientationReasonListener(
context: Context,
displayManager: DisplayManager,
handler: Handler,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index c43722f..efbde4c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -16,8 +16,11 @@
package com.android.systemui.biometrics.data.repository
+import android.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -30,10 +33,8 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
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.map
import kotlinx.coroutines.flow.shareIn
/**
@@ -43,22 +44,17 @@
*/
interface FingerprintPropertyRepository {
- /**
- * If the repository is initialized or not. Other properties are defaults until this is true.
- */
- val isInitialized: Flow<Boolean>
-
/** The id of fingerprint sensor. */
- val sensorId: StateFlow<Int>
+ val sensorId: Flow<Int>
/** The security strength of sensor (convenience, weak, strong). */
- val strength: StateFlow<SensorStrength>
+ val strength: Flow<SensorStrength>
/** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */
- val sensorType: StateFlow<FingerprintSensorType>
+ val sensorType: Flow<FingerprintSensorType>
/** The sensor location relative to each physical display. */
- val sensorLocations: StateFlow<Map<String, SensorLocationInternal>>
+ val sensorLocations: Flow<Map<String, SensorLocationInternal>>
}
@SysUISingleton
@@ -66,10 +62,10 @@
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- private val fingerprintManager: FingerprintManager
+ private val fingerprintManager: FingerprintManager?
) : FingerprintPropertyRepository {
- override val isInitialized: Flow<Boolean> =
+ private val props: Flow<FingerprintSensorPropertiesInternal> =
conflatedCallbackFlow {
val callback =
object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -77,45 +73,47 @@
sensors: List<FingerprintSensorPropertiesInternal>
) {
if (sensors.isNotEmpty()) {
- setProperties(sensors[0])
- trySendWithFailureLogging(true, TAG, "initialize properties")
+ trySendWithFailureLogging(sensors[0], TAG, "initialize properties")
+ } else {
+ trySendWithFailureLogging(
+ DEFAULT_PROPS,
+ TAG,
+ "initialize with default properties"
+ )
}
}
}
- fingerprintManager.addAuthenticatorsRegisteredCallback(callback)
- trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
+ fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
+ trySendWithFailureLogging(DEFAULT_PROPS, TAG, "initialize with default properties")
awaitClose {}
}
.shareIn(scope = applicationScope, started = SharingStarted.Eagerly, replay = 1)
- private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
- override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
-
- private val _strength: MutableStateFlow<SensorStrength> =
- MutableStateFlow(SensorStrength.CONVENIENCE)
- override val strength = _strength.asStateFlow()
-
- private val _sensorType: MutableStateFlow<FingerprintSensorType> =
- MutableStateFlow(FingerprintSensorType.UNKNOWN)
- override val sensorType = _sensorType.asStateFlow()
-
- private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
- MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
- override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
- _sensorLocations.asStateFlow()
-
- private fun setProperties(prop: FingerprintSensorPropertiesInternal) {
- _sensorId.value = prop.sensorId
- _strength.value = sensorStrengthIntToObject(prop.sensorStrength)
- _sensorType.value = sensorTypeIntToObject(prop.sensorType)
- _sensorLocations.value =
- prop.allLocations.associateBy { sensorLocationInternal ->
+ override val sensorId: Flow<Int> = props.map { it.sensorId }
+ override val strength: Flow<SensorStrength> =
+ props.map { sensorStrengthIntToObject(it.sensorStrength) }
+ override val sensorType: Flow<FingerprintSensorType> =
+ props.map { sensorTypeIntToObject(it.sensorType) }
+ override val sensorLocations: Flow<Map<String, SensorLocationInternal>> =
+ props.map {
+ it.allLocations.associateBy { sensorLocationInternal ->
sensorLocationInternal.displayId
}
- }
+ }
companion object {
private const val TAG = "FingerprintPropertyRepositoryImpl"
+ private val DEFAULT_PROPS =
+ FingerprintSensorPropertiesInternal(
+ -1 /* sensorId */,
+ SensorProperties.STRENGTH_CONVENIENCE,
+ 0 /* maxEnrollmentsPerUser */,
+ listOf<ComponentInfoInternal>(),
+ FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* halControlsIllumination */,
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
index aa85e5f3..37f39cb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
@@ -17,16 +17,24 @@
package com.android.systemui.biometrics.domain.interactor
import android.hardware.biometrics.SensorLocationInternal
-import android.util.Log
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
/** Business logic for SideFps overlay offsets. */
interface SideFpsOverlayInteractor {
+ /** The displayId of the current display. */
+ val displayId: Flow<String>
- /** Get the corresponding offsets based on different displayId. */
- fun getOverlayOffsets(displayId: String): SensorLocationInternal
+ /** The corresponding offsets based on different displayId. */
+ val overlayOffsets: Flow<SensorLocationInternal>
+
+ /** Update the displayId. */
+ fun changeDisplay(displayId: String?)
}
@SysUISingleton
@@ -35,14 +43,16 @@
constructor(private val fingerprintPropertyRepository: FingerprintPropertyRepository) :
SideFpsOverlayInteractor {
- override fun getOverlayOffsets(displayId: String): SensorLocationInternal {
- val offsets = fingerprintPropertyRepository.sensorLocations.value
- return if (offsets.containsKey(displayId)) {
- offsets[displayId]!!
- } else {
- Log.w(TAG, "No location specified for display: $displayId")
- offsets[""]!!
+ private val _displayId: MutableStateFlow<String> = MutableStateFlow("")
+ override val displayId: Flow<String> = _displayId.asStateFlow()
+
+ override val overlayOffsets: Flow<SensorLocationInternal> =
+ combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets ->
+ offsets[displayId] ?: SensorLocationInternal.DEFAULT
}
+
+ override fun changeDisplay(displayId: String?) {
+ _displayId.value = displayId ?: ""
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
new file mode 100644
index 0000000..0409519
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.binder
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Color
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.hardware.biometrics.BiometricOverlayConstants
+import android.view.View
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.model.KeyPath
+import com.android.systemui.R
+import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.launch
+
+/** Sub-binder for SideFpsOverlayView. */
+object SideFpsOverlayViewBinder {
+
+ /** Bind the view. */
+ @JvmStatic
+ fun bind(
+ view: View,
+ viewModel: SideFpsOverlayViewModel,
+ overlayViewParams: WindowManager.LayoutParams,
+ @BiometricOverlayConstants.ShowReason reason: Int,
+ @Application context: Context
+ ) {
+ val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+
+ val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+
+ viewModel.changeDisplay()
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.sideFpsAnimationRotation.collect { rotation ->
+ view.rotation = rotation
+ }
+ }
+
+ launch {
+ // TODO(b/221037350, wenhuiy): Create a separate ViewBinder for sideFpsAnimation
+ // in order to add scuba tests in the future.
+ viewModel.sideFpsAnimation.collect { animation ->
+ lottie.setAnimation(animation)
+ }
+ }
+
+ launch {
+ viewModel.sensorBounds.collect { sensorBounds ->
+ overlayViewParams.x = sensorBounds.left
+ overlayViewParams.y = sensorBounds.top
+
+ windowManager.updateViewLayout(view, overlayViewParams)
+ }
+ }
+
+ launch {
+ viewModel.overlayOffsets.collect { overlayOffsets ->
+ lottie.addLottieOnCompositionLoadedListener {
+ viewModel.updateSensorBounds(
+ it.bounds,
+ windowManager.maximumWindowMetrics.bounds,
+ overlayOffsets
+ )
+ }
+ }
+ }
+ }
+ }
+
+ lottie.addOverlayDynamicColor(context, reason)
+
+ /**
+ * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
+ * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
+ * in focus
+ */
+ view.accessibilityDelegate =
+ object : View.AccessibilityDelegate() {
+ override fun dispatchPopulateAccessibilityEvent(
+ host: View,
+ event: AccessibilityEvent
+ ): Boolean {
+ return if (
+ event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ ) {
+ true
+ } else {
+ super.dispatchPopulateAccessibilityEvent(host, event)
+ }
+ }
+ }
+ }
+}
+
+private fun LottieAnimationView.addOverlayDynamicColor(
+ context: Context,
+ @BiometricOverlayConstants.ShowReason reason: Int
+) {
+ fun update() {
+ val isKeyguard = reason == BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+ if (isKeyguard) {
+ val color = context.getColor(R.color.numpad_key_color_secondary) // match bouncer color
+ val chevronFill =
+ com.android.settingslib.Utils.getColorAttrDefaultColor(
+ context,
+ android.R.attr.textColorPrimaryInverse
+ )
+ for (key in listOf(".blue600", ".blue400")) {
+ addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
+ }
+ }
+ addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
+ }
+ } else if (!isDarkMode(context)) {
+ addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
+ }
+ } else if (isDarkMode(context)) {
+ for (key in listOf(".blue600", ".blue400")) {
+ addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(
+ context.getColor(R.color.settingslib_color_blue400),
+ PorterDuff.Mode.SRC_ATOP
+ )
+ }
+ }
+ }
+ }
+
+ if (composition != null) {
+ update()
+ } else {
+ addLottieOnCompositionLoadedListener { update() }
+ }
+}
+
+private fun isDarkMode(context: Context): Boolean {
+ val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+ return darkMode == Configuration.UI_MODE_NIGHT_YES
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
new file mode 100644
index 0000000..e938b4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.content.Context
+import android.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.util.RotationUtils
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.Surface
+import androidx.annotation.RawRes
+import com.android.systemui.R
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+
+/** View-model for SideFpsOverlayView. */
+class SideFpsOverlayViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val sideFpsOverlayInteractor: SideFpsOverlayInteractor,
+) {
+
+ private val isReverseDefaultRotation =
+ context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
+
+ private val _sensorBounds: MutableStateFlow<Rect> = MutableStateFlow(Rect())
+ val sensorBounds = _sensorBounds.asStateFlow()
+
+ val overlayOffsets: Flow<SensorLocationInternal> = sideFpsOverlayInteractor.overlayOffsets
+
+ /** Update the displayId. */
+ fun changeDisplay() {
+ sideFpsOverlayInteractor.changeDisplay(context.display!!.uniqueId)
+ }
+
+ /** Determine the rotation of the sideFps animation given the overlay offsets. */
+ val sideFpsAnimationRotation: Flow<Float> =
+ overlayOffsets.map { overlayOffsets ->
+ val display = context.display!!
+ val displayInfo: DisplayInfo = DisplayInfo()
+ // b/284098873 `context.display.rotation` may not up-to-date, we use
+ // displayInfo.rotation
+ display.getDisplayInfo(displayInfo)
+ val yAligned: Boolean = overlayOffsets.isYAligned()
+ when (getRotationFromDefault(displayInfo.rotation)) {
+ Surface.ROTATION_90 -> if (yAligned) 0f else 180f
+ Surface.ROTATION_180 -> 180f
+ Surface.ROTATION_270 -> if (yAligned) 180f else 0f
+ else -> 0f
+ }
+ }
+
+ /** Populate the sideFps animation from the overlay offsets. */
+ @RawRes
+ val sideFpsAnimation: Flow<Int> =
+ overlayOffsets.map { overlayOffsets ->
+ val display = context.display!!
+ val displayInfo: DisplayInfo = DisplayInfo()
+ // b/284098873 `context.display.rotation` may not up-to-date, we use
+ // displayInfo.rotation
+ display.getDisplayInfo(displayInfo)
+ val yAligned: Boolean = overlayOffsets.isYAligned()
+ when (getRotationFromDefault(displayInfo.rotation)) {
+ Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+ Surface.ROTATION_180 ->
+ if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+ else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
+ }
+ }
+
+ /**
+ * Calculate and update the bounds of the sensor based on the bounds of the overlay view, the
+ * maximum bounds of the window, and the offsets of the sensor location.
+ */
+ fun updateSensorBounds(
+ bounds: Rect,
+ maximumWindowBounds: Rect,
+ offsets: SensorLocationInternal
+ ) {
+ val isNaturalOrientation = context.display!!.isNaturalOrientation()
+ val isDefaultOrientation =
+ if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation
+
+ val displayWidth =
+ if (isDefaultOrientation) maximumWindowBounds.width() else maximumWindowBounds.height()
+ val displayHeight =
+ if (isDefaultOrientation) maximumWindowBounds.height() else maximumWindowBounds.width()
+ val boundsWidth = if (isDefaultOrientation) bounds.width() else bounds.height()
+ val boundsHeight = if (isDefaultOrientation) bounds.height() else bounds.width()
+
+ val sensorBounds =
+ if (offsets.isYAligned()) {
+ Rect(
+ displayWidth - boundsWidth,
+ offsets.sensorLocationY,
+ displayWidth,
+ offsets.sensorLocationY + boundsHeight
+ )
+ } else {
+ Rect(
+ offsets.sensorLocationX,
+ 0,
+ offsets.sensorLocationX + boundsWidth,
+ boundsHeight
+ )
+ }
+
+ val displayInfo: DisplayInfo = DisplayInfo()
+ context.display!!.getDisplayInfo(displayInfo)
+
+ RotationUtils.rotateBounds(
+ sensorBounds,
+ Rect(0, 0, displayWidth, displayHeight),
+ getRotationFromDefault(displayInfo.rotation)
+ )
+
+ _sensorBounds.value = sensorBounds
+ }
+
+ private fun getRotationFromDefault(rotation: Int): Int =
+ if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
+}
+
+private fun Display.isNaturalOrientation(): Boolean =
+ rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
+
+private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 2abdb84..e3e9b3a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -16,10 +16,10 @@
package com.android.systemui.bouncer.domain.interactor
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
@@ -35,8 +35,8 @@
private val keyguardStateController: KeyguardStateController,
private val bouncerRepository: KeyguardBouncerRepository,
private val biometricSettingsRepository: BiometricSettingsRepository,
- private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
private val systemClock: SystemClock,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
) {
var receivedDownTouch = false
val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
@@ -78,7 +78,7 @@
biometricSettingsRepository.isFingerprintEnrolled.value &&
biometricSettingsRepository.isStrongBiometricAllowed.value &&
biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
- !deviceEntryFingerprintAuthRepository.isLockedOut.value &&
+ !keyguardUpdateMonitor.isFingerprintLockedOut &&
!keyguardStateController.isUnlocked &&
!statusBarStateController.isDozing
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
index 0e22406..f3a07fc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt
@@ -16,15 +16,52 @@
package com.android.systemui.dreams
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.dagger.DreamLog
-import javax.inject.Inject
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
/** Logs dream-related stuff to a {@link LogBuffer}. */
-class DreamLogger @Inject constructor(@DreamLog private val buffer: LogBuffer) {
- /** Logs a debug message to the buffer. */
- fun d(tag: String, message: String) {
- buffer.log(tag, LogLevel.DEBUG, { str1 = message }, { message })
- }
+class DreamLogger(buffer: MessageBuffer, tag: String) : Logger(buffer, tag) {
+ fun logDreamOverlayEnabled(enabled: Boolean) =
+ d({ "Dream overlay enabled: $bool1" }) { bool1 = enabled }
+
+ fun logIgnoreAddComplication(reason: String, complication: String) =
+ d({ "Ignore adding complication, reason: $str1, complication: $str2" }) {
+ str1 = reason
+ str2 = complication
+ }
+
+ fun logIgnoreRemoveComplication(reason: String, complication: String) =
+ d({ "Ignore removing complication, reason: $str1, complication: $str2" }) {
+ str1 = reason
+ str2 = complication
+ }
+
+ fun logAddComplication(complication: String) =
+ d({ "Add dream complication: $str1" }) { str1 = complication }
+
+ fun logRemoveComplication(complication: String) =
+ d({ "Remove dream complication: $str1" }) { str1 = complication }
+
+ fun logOverlayActive(active: Boolean) = d({ "Dream overlay active: $bool1" }) { bool1 = active }
+
+ fun logLowLightActive(active: Boolean) =
+ d({ "Low light mode active: $bool1" }) { bool1 = active }
+
+ fun logHasAssistantAttention(hasAttention: Boolean) =
+ d({ "Dream overlay has Assistant attention: $bool1" }) { bool1 = hasAttention }
+
+ fun logStatusBarVisible(visible: Boolean) =
+ d({ "Dream overlay status bar visible: $bool1" }) { bool1 = visible }
+
+ fun logAvailableComplicationTypes(types: Int) =
+ d({ "Available complication types: $int1" }) { int1 = types }
+
+ fun logShouldShowComplications(showComplications: Boolean) =
+ d({ "Dream overlay should show complications: $bool1" }) { bool1 = showComplications }
+
+ fun logShowOrHideStatusBarItem(show: Boolean, type: String) =
+ d({ "${if (bool1) "Showing" else "Hiding"} dream status bar item: $int1" }) {
+ bool1 = show
+ str1 = type
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 484bf3d..01fb522 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -36,6 +36,9 @@
import com.android.systemui.dreams.dagger.DreamOverlayModule
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.DreamLog
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -65,12 +68,14 @@
private val mDreamInTranslationYDistance: Int,
@Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DURATION)
private val mDreamInTranslationYDurationMs: Long,
- private val mLogger: DreamLogger,
+ @DreamLog logBuffer: LogBuffer,
) {
companion object {
private const val TAG = "DreamOverlayAnimationsController"
}
+ private val logger = Logger(logBuffer, TAG)
+
private var mAnimator: Animator? = null
private lateinit var view: View
@@ -179,11 +184,11 @@
doOnEnd {
mAnimator = null
mOverlayStateController.setEntryAnimationsFinished(true)
- mLogger.d(TAG, "Dream overlay entry animations finished.")
+ logger.d("Dream overlay entry animations finished.")
}
- doOnCancel { mLogger.d(TAG, "Dream overlay entry animations canceled.") }
+ doOnCancel { logger.d("Dream overlay entry animations canceled.") }
start()
- mLogger.d(TAG, "Dream overlay entry animations started.")
+ logger.d("Dream overlay entry animations started.")
}
}
@@ -242,11 +247,11 @@
doOnEnd {
mAnimator = null
mOverlayStateController.setExitAnimationsRunning(false)
- mLogger.d(TAG, "Dream overlay exit animations finished.")
+ logger.d("Dream overlay exit animations finished.")
}
- doOnCancel { mLogger.d(TAG, "Dream overlay exit animations canceled.") }
+ doOnCancel { logger.d("Dream overlay exit animations canceled.") }
start()
- mLogger.d(TAG, "Dream overlay exit animations started.")
+ logger.d("Dream overlay exit animations started.")
}
mOverlayStateController.setExitAnimationsRunning(true)
return mAnimator as AnimatorSet
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index c2421dc..c9748f9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -28,6 +28,8 @@
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.dagger.DreamLog;
import com.android.systemui.statusbar.policy.CallbackController;
import java.util.ArrayList;
@@ -115,10 +117,10 @@
public DreamOverlayStateController(@Main Executor executor,
@Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled,
FeatureFlags featureFlags,
- DreamLogger dreamLogger) {
+ @DreamLog LogBuffer logBuffer) {
mExecutor = executor;
mOverlayEnabled = overlayEnabled;
- mLogger = dreamLogger;
+ mLogger = new DreamLogger(logBuffer, TAG);
mFeatureFlags = featureFlags;
if (mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)) {
mSupportedTypes = Complication.COMPLICATION_TYPE_NONE
@@ -126,7 +128,7 @@
} else {
mSupportedTypes = Complication.COMPLICATION_TYPE_NONE;
}
- mLogger.d(TAG, "Dream overlay enabled: " + mOverlayEnabled);
+ mLogger.logDreamOverlayEnabled(mOverlayEnabled);
}
/**
@@ -134,14 +136,13 @@
*/
public void addComplication(Complication complication) {
if (!mOverlayEnabled) {
- mLogger.d(TAG,
- "Ignoring adding complication due to overlay disabled: " + complication);
+ mLogger.logIgnoreAddComplication("overlay disabled", complication.toString());
return;
}
mExecutor.execute(() -> {
if (mComplications.add(complication)) {
- mLogger.d(TAG, "Added dream complication: " + complication);
+ mLogger.logAddComplication(complication.toString());
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
@@ -152,14 +153,13 @@
*/
public void removeComplication(Complication complication) {
if (!mOverlayEnabled) {
- mLogger.d(TAG,
- "Ignoring removing complication due to overlay disabled: " + complication);
+ mLogger.logIgnoreRemoveComplication("overlay disabled", complication.toString());
return;
}
mExecutor.execute(() -> {
if (mComplications.remove(complication)) {
- mLogger.d(TAG, "Removed dream complication: " + complication);
+ mLogger.logRemoveComplication(complication.toString());
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
@@ -305,7 +305,7 @@
* @param active {@code true} if overlay is active, {@code false} otherwise.
*/
public void setOverlayActive(boolean active) {
- mLogger.d(TAG, "Dream overlay active: " + active);
+ mLogger.logOverlayActive(active);
modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE);
}
@@ -314,7 +314,7 @@
* @param active {@code true} if low light mode is active, {@code false} otherwise.
*/
public void setLowLightActive(boolean active) {
- mLogger.d(TAG, "Low light mode active: " + active);
+ mLogger.logLowLightActive(active);
if (isLowLightActive() && !active) {
// Notify that we're exiting low light only on the transition from active to not active.
@@ -346,7 +346,7 @@
* @param hasAttention {@code true} if has the user's attention, {@code false} otherwise.
*/
public void setHasAssistantAttention(boolean hasAttention) {
- mLogger.d(TAG, "Dream overlay has Assistant attention: " + hasAttention);
+ mLogger.logHasAssistantAttention(hasAttention);
modifyState(hasAttention ? OP_SET_STATE : OP_CLEAR_STATE, STATE_HAS_ASSISTANT_ATTENTION);
}
@@ -355,7 +355,7 @@
* @param visible {@code true} if the status bar is visible, {@code false} otherwise.
*/
public void setDreamOverlayStatusBarVisible(boolean visible) {
- mLogger.d(TAG, "Dream overlay status bar visible: " + visible);
+ mLogger.logStatusBarVisible(visible);
modifyState(
visible ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE);
}
@@ -373,7 +373,7 @@
*/
public void setAvailableComplicationTypes(@Complication.ComplicationType int types) {
mExecutor.execute(() -> {
- mLogger.d(TAG, "Available complication types: " + types);
+ mLogger.logAvailableComplicationTypes(types);
mAvailableComplicationTypes = types;
mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
});
@@ -391,7 +391,7 @@
*/
public void setShouldShowComplications(boolean shouldShowComplications) {
mExecutor.execute(() -> {
- mLogger.d(TAG, "Should show complications: " + shouldShowComplications);
+ mLogger.logShouldShowComplications(shouldShowComplications);
mShouldShowComplications = shouldShowComplications;
mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
});
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 3a28408..a6401b6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -36,6 +36,8 @@
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.settings.UserTracker;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
@@ -161,7 +163,7 @@
DreamOverlayStatusBarItemsProvider statusBarItemsProvider,
DreamOverlayStateController dreamOverlayStateController,
UserTracker userTracker,
- DreamLogger dreamLogger) {
+ @DreamLog LogBuffer logBuffer) {
super(view);
mResources = resources;
mMainExecutor = mainExecutor;
@@ -177,7 +179,7 @@
mZenModeController = zenModeController;
mDreamOverlayStateController = dreamOverlayStateController;
mUserTracker = userTracker;
- mLogger = dreamLogger;
+ mLogger = new DreamLogger(logBuffer, TAG);
// Register to receive show/hide updates for the system status bar. Our custom status bar
// needs to hide when the system status bar is showing to ovoid overlapping status bars.
@@ -346,8 +348,8 @@
@Nullable String contentDescription) {
mMainExecutor.execute(() -> {
if (mIsAttached) {
- mLogger.d(TAG, (show ? "Showing" : "Hiding") + " dream status bar item: "
- + DreamOverlayStatusBarView.getLoggableStatusIconType(iconType));
+ mLogger.logShowOrHideStatusBarItem(
+ show, DreamOverlayStatusBarView.getLoggableStatusIconType(iconType));
mView.showIcon(iconType, show, contentDescription);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 83c1c71..0670ec3 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -61,10 +61,6 @@
// TODO(b/254512538): Tracking Bug
val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply")
- // TODO(b/279735475): Tracking Bug
- @JvmField
- val NEW_LIGHT_BAR_LOGIC = releasedFlag(279735475, "new_light_bar_logic")
-
/**
* This flag is server-controlled and should stay as [unreleasedFlag] since we never want to
* enable it on release builds.
@@ -72,9 +68,6 @@
val NOTIFICATION_MEMORY_LOGGING_ENABLED =
unreleasedFlag(119, "notification_memory_logging_enabled")
- // TODO(b/257315550): Tracking Bug
- val NO_HUN_FOR_OLD_WHEN = releasedFlag(118, "no_hun_for_old_when")
-
// TODO(b/260335638): Tracking Bug
@JvmField
val NOTIFICATION_INLINE_REPLY_ANIMATION =
@@ -445,10 +438,6 @@
// TODO(b/266157412): Tracking Bug
val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
- // TODO(b/266739309): Tracking Bug
- @JvmField
- val MEDIA_RECOMMENDATION_CARD_UPDATE = releasedFlag(914, "media_recommendation_card_update")
-
// TODO(b/267007629): Tracking Bug
val MEDIA_RESUME_PROGRESS = releasedFlag(915, "media_resume_progress")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index f59ad90..23f6fa6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -31,6 +31,9 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
@@ -40,7 +43,9 @@
@Inject
constructor(
private val keyguardRootView: KeyguardRootView,
+ private val sharedNotificationContainer: SharedNotificationContainer,
private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
+ private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
private val notificationShadeWindowView: NotificationShadeWindowView,
private val featureFlags: FeatureFlags,
private val indicationController: KeyguardIndicationController,
@@ -55,10 +60,28 @@
notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
bindIndicationArea(notificationPanel)
bindLockIconView(notificationPanel)
+ setupNotificationStackScrollLayout(notificationPanel)
+
keyguardLayoutManager.layoutViews()
keyguardLayoutManagerCommandListener.start()
}
+ fun setupNotificationStackScrollLayout(legacyParent: ViewGroup) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ // This moves the existing NSSL view to a different parent, as the controller is a
+ // singleton and recreating it has other bad side effects
+ val nssl =
+ legacyParent.requireViewById<View>(R.id.notification_stack_scroller).also {
+ (it.getParent() as ViewGroup).removeView(it)
+ }
+ sharedNotificationContainer.addNotificationStackScrollLayout(nssl)
+ SharedNotificationContainerBinder.bind(
+ sharedNotificationContainer,
+ sharedNotificationContainerViewModel
+ )
+ }
+ }
+
fun bindIndicationArea(legacyParent: ViewGroup) {
indicationAreaHandle?.dispose()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 9a32e94..ddd4a2b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -163,9 +163,11 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import dagger.Lazy;
@@ -290,6 +292,8 @@
public static final String SYS_BOOT_REASON_PROP = "sys.boot.reason.last";
public static final String REBOOT_MAINLINE_UPDATE = "reboot,mainline_update";
private final DreamOverlayStateController mDreamOverlayStateController;
+ private final JavaAdapter mJavaAdapter;
+ private final WallpaperRepository mWallpaperRepository;
/** The stream type that the lock sounds are tied to. */
private int mUiSoundsStreamType;
@@ -1323,6 +1327,8 @@
KeyguardTransitions keyguardTransitions,
InteractionJankMonitor interactionJankMonitor,
DreamOverlayStateController dreamOverlayStateController,
+ JavaAdapter javaAdapter,
+ WallpaperRepository wallpaperRepository,
Lazy<ShadeController> shadeControllerLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
@@ -1384,6 +1390,8 @@
mScreenOffAnimationController = screenOffAnimationController;
mInteractionJankMonitor = interactionJankMonitor;
mDreamOverlayStateController = dreamOverlayStateController;
+ mJavaAdapter = javaAdapter;
+ mWallpaperRepository = wallpaperRepository;
mActivityLaunchAnimator = activityLaunchAnimator;
mScrimControllerLazy = scrimControllerLazy;
@@ -1487,6 +1495,10 @@
com.android.internal.R.anim.lock_screen_behind_enter);
mWorkLockController = new WorkLockActivityController(mContext, mUserTracker);
+
+ mJavaAdapter.alwaysCollectFlow(
+ mWallpaperRepository.getWallpaperSupportsAmbientMode(),
+ this::setWallpaperSupportsAmbientMode);
}
// TODO(b/273443374) remove, temporary util to get a feature flag
@@ -3464,7 +3476,7 @@
* In case it does support it, we have to fade in the incoming app, otherwise we'll reveal it
* with the light reveal scrim.
*/
- public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
+ private void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
mWallpaperSupportsAmbientMode = supportsAmbientMode;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 1487408..a5ac7c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -67,9 +67,11 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import dagger.Lazy;
@@ -131,6 +133,8 @@
KeyguardTransitions keyguardTransitions,
InteractionJankMonitor interactionJankMonitor,
DreamOverlayStateController dreamOverlayStateController,
+ JavaAdapter javaAdapter,
+ WallpaperRepository wallpaperRepository,
Lazy<ShadeController> shadeController,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
@@ -172,6 +176,8 @@
keyguardTransitions,
interactionJankMonitor,
dreamOverlayStateController,
+ javaAdapter,
+ wallpaperRepository,
shadeController,
notificationShadeWindowController,
activityLaunchAnimator,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index 0b6c7c4..ff3e77c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -325,6 +325,9 @@
private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) :
LockPatternUtils.StrongAuthTracker(context) {
+ private val selectedUserId =
+ userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged()
+
// Backing field for onStrongAuthRequiredChanged
private val _authFlags =
MutableStateFlow(AuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId)))
@@ -336,15 +339,12 @@
)
val currentUserAuthFlags: Flow<AuthenticationFlags> =
- userRepository.selectedUserInfo
- .map { it.id }
- .distinctUntilChanged()
- .flatMapLatest { userId ->
- _authFlags
- .map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) }
- .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
- .onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) }
- }
+ selectedUserId.flatMapLatest { userId ->
+ _authFlags
+ .map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) }
+ .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
+ .onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) }
+ }
/** isStrongBiometricAllowed for the current user. */
val isStrongBiometricAllowed: Flow<Boolean> =
@@ -352,16 +352,17 @@
/** isNonStrongBiometricAllowed for the current user. */
val isNonStrongBiometricAllowed: Flow<Boolean> =
- userRepository.selectedUserInfo
- .map { it.id }
- .distinctUntilChanged()
+ selectedUserId
.flatMapLatest { userId ->
_nonStrongBiometricAllowed
.filter { it.first == userId }
.map { it.second }
- .onEach { Log.d(TAG, "isNonStrongBiometricAllowed changed for current user") }
+ .onEach {
+ Log.d(TAG, "isNonStrongBiometricAllowed changed for current user: $it")
+ }
.onStart { emit(isNonStrongBiometricAllowedAfterIdleTimeout(userId)) }
}
+ .and(isStrongBiometricAllowed)
private val currentUserId
get() = userRepository.getSelectedUserInfo().id
@@ -387,3 +388,6 @@
private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean =
(getKeyguardDisabledFeatures(null, userId) and policy) == 0
+
+private fun Flow<Boolean>.and(anotherFlow: Flow<Boolean>): Flow<Boolean> =
+ this.combine(anotherFlow) { a, b -> a && b }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index a3d1abe..6edf40f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -38,13 +38,13 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
@@ -88,10 +88,10 @@
val canRunFaceAuth: StateFlow<Boolean>
/** Provide the current status of face authentication. */
- val authenticationStatus: Flow<AuthenticationStatus>
+ val authenticationStatus: Flow<FaceAuthenticationStatus>
/** Provide the current status of face detection. */
- val detectionStatus: Flow<DetectionStatus>
+ val detectionStatus: Flow<FaceDetectionStatus>
/** Current state of whether face authentication is locked out or not. */
val isLockedOut: StateFlow<Boolean>
@@ -151,13 +151,13 @@
private var cancelNotReceivedHandlerJob: Job? = null
private var halErrorRetryJob: Job? = null
- private val _authenticationStatus: MutableStateFlow<AuthenticationStatus?> =
+ private val _authenticationStatus: MutableStateFlow<FaceAuthenticationStatus?> =
MutableStateFlow(null)
- override val authenticationStatus: Flow<AuthenticationStatus>
+ override val authenticationStatus: Flow<FaceAuthenticationStatus>
get() = _authenticationStatus.filterNotNull()
- private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
- override val detectionStatus: Flow<DetectionStatus>
+ private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null)
+ override val detectionStatus: Flow<FaceDetectionStatus>
get() = _detectionStatus.filterNotNull()
private val _isLockedOut = MutableStateFlow(false)
@@ -396,18 +396,18 @@
private val faceAuthCallback =
object : FaceManager.AuthenticationCallback() {
override fun onAuthenticationFailed() {
- _authenticationStatus.value = FailedAuthenticationStatus
+ _authenticationStatus.value = FailedFaceAuthenticationStatus
_isAuthenticated.value = false
faceAuthLogger.authenticationFailed()
onFaceAuthRequestCompleted()
}
override fun onAuthenticationAcquired(acquireInfo: Int) {
- _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
+ _authenticationStatus.value = AcquiredFaceAuthenticationStatus(acquireInfo)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
- val errorStatus = ErrorAuthenticationStatus(errorCode, errString.toString())
+ val errorStatus = ErrorFaceAuthenticationStatus(errorCode, errString.toString())
if (errorStatus.isLockoutError()) {
_isLockedOut.value = true
}
@@ -433,11 +433,11 @@
if (faceAcquiredInfoIgnoreList.contains(code)) {
return
}
- _authenticationStatus.value = HelpAuthenticationStatus(code, helpStr.toString())
+ _authenticationStatus.value = HelpFaceAuthenticationStatus(code, helpStr.toString())
}
override fun onAuthenticationSucceeded(result: FaceManager.AuthenticationResult) {
- _authenticationStatus.value = SuccessAuthenticationStatus(result)
+ _authenticationStatus.value = SuccessFaceAuthenticationStatus(result)
_isAuthenticated.value = true
faceAuthLogger.faceAuthSuccess(result)
onFaceAuthRequestCompleted()
@@ -482,7 +482,7 @@
private val detectionCallback =
FaceManager.FaceDetectionCallback { sensorId, userId, isStrong ->
faceAuthLogger.faceDetected()
- _detectionStatus.value = DetectionStatus(sensorId, userId, isStrong)
+ _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong)
}
private var cancellationInProgress = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 52234b3..9bec300 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -21,27 +21,29 @@
import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.Dumpable
import com.android.systemui.biometrics.AuthController
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
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.dump.DumpManager
-import java.io.PrintWriter
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
/** Encapsulates state about device entry fingerprint auth mechanism. */
interface DeviceEntryFingerprintAuthRepository {
/** Whether the device entry fingerprint auth is locked out. */
- val isLockedOut: StateFlow<Boolean>
+ val isLockedOut: Flow<Boolean>
/**
* Whether the fingerprint sensor is currently listening, this doesn't mean that the user is
@@ -53,6 +55,9 @@
* Fingerprint sensor type present on the device, null if fingerprint sensor is not available.
*/
val availableFpSensorType: Flow<BiometricType?>
+
+ /** Provide the current status of fingerprint authentication. */
+ val authenticationStatus: Flow<FingerprintAuthenticationStatus>
}
/**
@@ -69,16 +74,7 @@
val authController: AuthController,
val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Application scope: CoroutineScope,
- dumpManager: DumpManager,
-) : DeviceEntryFingerprintAuthRepository, Dumpable {
-
- init {
- dumpManager.registerDumpable(this)
- }
-
- override fun dump(pw: PrintWriter, args: Array<String?>) {
- pw.println("isLockedOut=${isLockedOut.value}")
- }
+) : DeviceEntryFingerprintAuthRepository {
override val availableFpSensorType: Flow<BiometricType?>
get() {
@@ -114,7 +110,7 @@
else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null
}
- override val isLockedOut: StateFlow<Boolean> =
+ override val isLockedOut: Flow<Boolean> =
conflatedCallbackFlow {
val sendLockoutUpdate =
fun() {
@@ -138,7 +134,7 @@
sendLockoutUpdate()
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
- .stateIn(scope, started = SharingStarted.Eagerly, initialValue = false)
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
override val isRunning: Flow<Boolean>
get() = conflatedCallbackFlow {
@@ -166,6 +162,93 @@
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
+ override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
+ get() = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onBiometricAuthenticated(
+ userId: Int,
+ biometricSourceType: BiometricSourceType,
+ isStrongBiometric: Boolean,
+ ) {
+
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ SuccessFingerprintAuthenticationStatus(
+ userId,
+ isStrongBiometric,
+ ),
+ )
+ }
+
+ override fun onBiometricError(
+ msgId: Int,
+ errString: String?,
+ biometricSourceType: BiometricSourceType,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ ErrorFingerprintAuthenticationStatus(
+ msgId,
+ errString,
+ ),
+ )
+ }
+
+ override fun onBiometricHelp(
+ msgId: Int,
+ helpString: String?,
+ biometricSourceType: BiometricSourceType,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ HelpFingerprintAuthenticationStatus(
+ msgId,
+ helpString,
+ ),
+ )
+ }
+
+ override fun onBiometricAuthFailed(
+ biometricSourceType: BiometricSourceType,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ FailFingerprintAuthenticationStatus,
+ )
+ }
+
+ override fun onBiometricAcquired(
+ biometricSourceType: BiometricSourceType,
+ acquireInfo: Int,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ AcquiredFingerprintAuthenticationStatus(
+ acquireInfo,
+ ),
+ )
+ }
+
+ private fun sendUpdateIfFingerprint(
+ biometricSourceType: BiometricSourceType,
+ authenticationStatus: FingerprintAuthenticationStatus
+ ) {
+ if (biometricSourceType != BiometricSourceType.FINGERPRINT) {
+ return
+ }
+
+ trySendWithFailureLogging(
+ authenticationStatus,
+ TAG,
+ "new fingerprint authentication status"
+ )
+ }
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+
companion object {
const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index d119920..7475c42 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -31,26 +31,31 @@
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.ScreenModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
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.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@@ -147,6 +152,9 @@
/** Observable for device wake/sleep state */
val wakefulness: StateFlow<WakefulnessModel>
+ /** Observable for device screen state */
+ val screenModel: StateFlow<ScreenModel>
+
/** Observable for biometric unlock modes */
val biometricUnlockState: Flow<BiometricUnlockModel>
@@ -162,6 +170,9 @@
/** Whether quick settings or quick-quick settings is visible. */
val isQuickSettingsVisible: Flow<Boolean>
+ /** Receive an event for doze time tick */
+ val dozeTimeTick: Flow<Unit>
+
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
@@ -171,6 +182,14 @@
*/
fun isKeyguardShowing(): Boolean
+ /**
+ * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically
+ * dismissed once the authentication challenge is completed. For example, completing a biometric
+ * authentication challenge via face unlock or fingerprint sensor can automatically bypass the
+ * lock screen.
+ */
+ fun isBypassEnabled(): Boolean
+
/** Sets whether the bottom area UI should animate the transition out of doze state. */
fun setAnimateDozingTransitions(animate: Boolean)
@@ -195,6 +214,8 @@
fun setIsDozing(isDozing: Boolean)
fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean)
+
+ fun dozeTimeTick()
}
/** Encapsulates application state for the keyguard. */
@@ -204,8 +225,10 @@
constructor(
statusBarStateController: StatusBarStateController,
wakefulnessLifecycle: WakefulnessLifecycle,
+ screenLifecycle: ScreenLifecycle,
biometricUnlockController: BiometricUnlockController,
private val keyguardStateController: KeyguardStateController,
+ private val keyguardBypassController: KeyguardBypassController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener,
private val dozeParameters: DozeParameters,
@@ -252,23 +275,17 @@
override val isAodAvailable: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
- object : DozeParameters.Callback {
- override fun onAlwaysOnChange() {
- trySendWithFailureLogging(
- dozeParameters.getAlwaysOn(),
- TAG,
- "updated isAodAvailable"
- )
- }
+ DozeParameters.Callback {
+ trySendWithFailureLogging(
+ dozeParameters.alwaysOn,
+ TAG,
+ "updated isAodAvailable"
+ )
}
dozeParameters.addCallback(callback)
// Adding the callback does not send an initial update.
- trySendWithFailureLogging(
- dozeParameters.getAlwaysOn(),
- TAG,
- "initial isAodAvailable"
- )
+ trySendWithFailureLogging(dozeParameters.alwaysOn, TAG, "initial isAodAvailable")
awaitClose { dozeParameters.removeCallback(callback) }
}
@@ -366,6 +383,13 @@
_isDozing.value = isDozing
}
+ private val _dozeTimeTick = MutableSharedFlow<Unit>()
+ override val dozeTimeTick = _dozeTimeTick.asSharedFlow()
+
+ override fun dozeTimeTick() {
+ _dozeTimeTick.tryEmit(Unit)
+ }
+
private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
@@ -464,6 +488,10 @@
return keyguardStateController.isShowing
}
+ override fun isBypassEnabled(): Boolean {
+ return keyguardBypassController.bypassEnabled
+ }
+
override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
val callback =
object : StatusBarStateController.StateListener {
@@ -551,6 +579,42 @@
initialValue = WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
)
+ override val screenModel: StateFlow<ScreenModel> =
+ conflatedCallbackFlow {
+ val observer =
+ object : ScreenLifecycle.Observer {
+ override fun onScreenTurningOn() {
+ dispatchNewState()
+ }
+ override fun onScreenTurnedOn() {
+ dispatchNewState()
+ }
+ override fun onScreenTurningOff() {
+ dispatchNewState()
+ }
+ override fun onScreenTurnedOff() {
+ dispatchNewState()
+ }
+
+ private fun dispatchNewState() {
+ trySendWithFailureLogging(
+ ScreenModel.fromScreenLifecycle(screenLifecycle),
+ TAG,
+ "updated screen state",
+ )
+ }
+ }
+
+ screenLifecycle.addObserver(observer)
+ awaitClose { screenLifecycle.removeObserver(observer) }
+ }
+ .stateIn(
+ scope,
+ // Use Eagerly so that we're always listening and never miss an event.
+ SharingStarted.Eagerly,
+ initialValue = ScreenModel.fromScreenLifecycle(screenLifecycle),
+ )
+
override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
fun sendFpLocation() {
trySendWithFailureLogging(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
index abe59b7..27e3a74 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
@@ -18,8 +18,8 @@
import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -40,10 +40,10 @@
private val _canRunFaceAuth = MutableStateFlow(false)
override val canRunFaceAuth: StateFlow<Boolean> = _canRunFaceAuth
- override val authenticationStatus: Flow<AuthenticationStatus>
+ override val authenticationStatus: Flow<FaceAuthenticationStatus>
get() = emptyFlow()
- override val detectionStatus: Flow<DetectionStatus>
+ override val detectionStatus: Flow<FaceDetectionStatus>
get() = emptyFlow()
private val _isLockedOut = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt
new file mode 100644
index 0000000..c849b84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractor.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.content.res.Resources
+import android.hardware.biometrics.BiometricSourceType
+import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.util.IndicationHelper
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+/**
+ * BiometricMessage business logic. Filters biometric error/acquired/fail/success events for
+ * authentication events that should never surface a message to the user at the current device
+ * state.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class BiometricMessageInteractor
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+ private val fingerprintPropertyRepository: FingerprintPropertyRepository,
+ private val indicationHelper: IndicationHelper,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) {
+ val fingerprintErrorMessage: Flow<BiometricMessage> =
+ fingerprintAuthRepository.authenticationStatus
+ .filter {
+ it is ErrorFingerprintAuthenticationStatus &&
+ !indicationHelper.shouldSuppressErrorMsg(FINGERPRINT, it.msgId)
+ }
+ .map {
+ val errorStatus = it as ErrorFingerprintAuthenticationStatus
+ BiometricMessage(
+ FINGERPRINT,
+ BiometricMessageType.ERROR,
+ errorStatus.msgId,
+ errorStatus.msg,
+ )
+ }
+
+ val fingerprintHelpMessage: Flow<BiometricMessage> =
+ fingerprintAuthRepository.authenticationStatus
+ .filter { it is HelpFingerprintAuthenticationStatus }
+ .filterNot { isPrimaryAuthRequired() }
+ .map {
+ val helpStatus = it as HelpFingerprintAuthenticationStatus
+ BiometricMessage(
+ FINGERPRINT,
+ BiometricMessageType.HELP,
+ helpStatus.msgId,
+ helpStatus.msg,
+ )
+ }
+
+ val fingerprintFailMessage: Flow<BiometricMessage> =
+ isUdfps().flatMapLatest { isUdfps ->
+ fingerprintAuthRepository.authenticationStatus
+ .filter { it is FailFingerprintAuthenticationStatus }
+ .filterNot { isPrimaryAuthRequired() }
+ .map {
+ BiometricMessage(
+ FINGERPRINT,
+ BiometricMessageType.FAIL,
+ BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+ if (isUdfps) {
+ resources.getString(
+ com.android.internal.R.string.fingerprint_udfps_error_not_match
+ )
+ } else {
+ resources.getString(
+ com.android.internal.R.string.fingerprint_error_not_match
+ )
+ },
+ )
+ }
+ }
+
+ private fun isUdfps() =
+ fingerprintPropertyRepository.sensorType.map {
+ it == FingerprintSensorType.UDFPS_OPTICAL ||
+ it == FingerprintSensorType.UDFPS_ULTRASONIC
+ }
+
+ private fun isPrimaryAuthRequired(): Boolean {
+ // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+ // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to
+ // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+ // check of whether non-strong biometric is allowed since strong biometrics can still be
+ // used.
+ return !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
+ }
+}
+
+data class BiometricMessage(
+ val source: BiometricSourceType,
+ val type: BiometricMessageType,
+ val id: Int,
+ val message: String?,
+)
+
+enum class BiometricMessageType {
+ HELP,
+ ERROR,
+ FAIL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
index 2efcd0c..0c898be 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
@@ -35,4 +35,8 @@
fun setLastTapToWakePosition(position: Point) {
keyguardRepository.setLastDozeTapToWakePosition(position)
}
+
+ fun dozeTimeTick() {
+ keyguardRepository.dozeTimeTick()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
index 74ef7a5..141b130 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -16,8 +16,8 @@
package com.android.systemui.keyguard.domain.interactor
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import kotlinx.coroutines.flow.Flow
/**
@@ -27,10 +27,10 @@
interface KeyguardFaceAuthInteractor {
/** Current authentication status */
- val authenticationStatus: Flow<AuthenticationStatus>
+ val authenticationStatus: Flow<FaceAuthenticationStatus>
/** Current detection status */
- val detectionStatus: Flow<DetectionStatus>
+ val detectionStatus: Flow<FaceDetectionStatus>
/** Can face auth be run right now */
fun canFaceAuthRun(): Boolean
@@ -72,8 +72,8 @@
*/
interface FaceAuthenticationListener {
/** Receive face authentication status updates */
- fun onAuthenticationStatusChanged(status: AuthenticationStatus)
+ fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
/** Receive status updates whenever face detection runs */
- fun onDetectionStatusChanged(status: DetectionStatus)
+ fun onDetectionStatusChanged(status: FaceDetectionStatus)
}
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 7fae752..1553525 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
@@ -70,6 +70,8 @@
val dozeAmount: Flow<Float> = repository.linearDozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
+ /** Receive an event for doze time tick */
+ val dozeTimeTick: Flow<Unit> = repository.dozeTimeTick
/** Whether Always-on Display mode is available. */
val isAodAvailable: Flow<Boolean> = repository.isAodAvailable
/** Doze transition information. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
index 5005b6c..10dd900 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -17,8 +17,8 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
@@ -31,9 +31,9 @@
*/
@SysUISingleton
class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor {
- override val authenticationStatus: Flow<AuthenticationStatus>
+ override val authenticationStatus: Flow<FaceAuthenticationStatus>
get() = emptyFlow()
- override val detectionStatus: Flow<DetectionStatus>
+ override val detectionStatus: Flow<FaceDetectionStatus>
get() = emptyFlow()
override fun canFaceAuthRun(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index d467225..6e7a20b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -30,8 +30,8 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.util.kotlin.pairwise
@@ -165,10 +165,10 @@
repository.cancel()
}
- private val _authenticationStatusOverride = MutableStateFlow<AuthenticationStatus?>(null)
+ private val faceAuthenticationStatusOverride = MutableStateFlow<FaceAuthenticationStatus?>(null)
/** Provide the status of face authentication */
override val authenticationStatus =
- merge(_authenticationStatusOverride.filterNotNull(), repository.authenticationStatus)
+ merge(faceAuthenticationStatusOverride.filterNotNull(), repository.authenticationStatus)
/** Provide the status of face detection */
override val detectionStatus = repository.detectionStatus
@@ -176,13 +176,13 @@
private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
if (repository.isLockedOut.value) {
- _authenticationStatusOverride.value =
- ErrorAuthenticationStatus(
+ faceAuthenticationStatusOverride.value =
+ ErrorFaceAuthenticationStatus(
BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT,
context.resources.getString(R.string.keyguard_face_unlock_unavailable)
)
} else {
- _authenticationStatusOverride.value = null
+ faceAuthenticationStatusOverride.value = null
applicationScope.launch {
faceAuthenticationLogger.authRequested(uiEvent)
repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
index b354cfd..0f6d82e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
@@ -23,33 +23,35 @@
* Authentication status provided by
* [com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository]
*/
-sealed class AuthenticationStatus
+sealed class FaceAuthenticationStatus
/** Success authentication status. */
-data class SuccessAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) :
- AuthenticationStatus()
+data class SuccessFaceAuthenticationStatus(val successResult: FaceManager.AuthenticationResult) :
+ FaceAuthenticationStatus()
/** Face authentication help message. */
-data class HelpAuthenticationStatus(val msgId: Int, val msg: String?) : AuthenticationStatus()
+data class HelpFaceAuthenticationStatus(val msgId: Int, val msg: String?) :
+ FaceAuthenticationStatus()
/** Face acquired message. */
-data class AcquiredAuthenticationStatus(val acquiredInfo: Int) : AuthenticationStatus()
+data class AcquiredFaceAuthenticationStatus(val acquiredInfo: Int) : FaceAuthenticationStatus()
/** Face authentication failed message. */
-object FailedAuthenticationStatus : AuthenticationStatus()
+object FailedFaceAuthenticationStatus : FaceAuthenticationStatus()
/** Face authentication error message */
-data class ErrorAuthenticationStatus(
+data class ErrorFaceAuthenticationStatus(
val msgId: Int,
val msg: String? = null,
// present to break equality check if the same error occurs repeatedly.
val createdAt: Long = elapsedRealtime()
-) : AuthenticationStatus() {
+) : FaceAuthenticationStatus() {
/**
* Method that checks if [msgId] is a lockout error. A lockout error means that face
* authentication is locked out.
*/
- fun isLockoutError() = msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
+ fun isLockoutError() =
+ msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT || msgId == FaceManager.FACE_ERROR_LOCKOUT
/**
* Method that checks if [msgId] is a cancellation error. This means that face authentication
@@ -64,4 +66,4 @@
}
/** Face detection success message. */
-data class DetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean)
+data class FaceDetectionStatus(val sensorId: Int, val userId: Int, val isStrongBiometric: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
new file mode 100644
index 0000000..7fc6016
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import android.os.SystemClock.elapsedRealtime
+
+/**
+ * Fingerprint authentication status provided by
+ * [com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository]
+ */
+sealed class FingerprintAuthenticationStatus
+
+/** Fingerprint authentication success status. */
+data class SuccessFingerprintAuthenticationStatus(
+ val userId: Int,
+ val isStrongBiometric: Boolean,
+) : FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication help message. */
+data class HelpFingerprintAuthenticationStatus(
+ val msgId: Int,
+ val msg: String?,
+) : FingerprintAuthenticationStatus()
+
+/** Fingerprint acquired message. */
+data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
+ FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication failed message. */
+object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
+
+/** Fingerprint authentication error message */
+data class ErrorFingerprintAuthenticationStatus(
+ val msgId: Int,
+ val msg: String? = null,
+ // present to break equality check if the same error occurs repeatedly.
+ val createdAt: Long = elapsedRealtime(),
+) : FingerprintAuthenticationStatus()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt
new file mode 100644
index 0000000..80a1b75
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenModel.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+import com.android.systemui.keyguard.ScreenLifecycle
+
+/** Model device screen lifecycle states. */
+data class ScreenModel(
+ val state: ScreenState,
+) {
+ companion object {
+ fun fromScreenLifecycle(screenLifecycle: ScreenLifecycle): ScreenModel {
+ return ScreenModel(ScreenState.fromScreenLifecycleInt(screenLifecycle.getScreenState()))
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt
new file mode 100644
index 0000000..fe5d935
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScreenState.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.keyguard.shared.model
+
+import com.android.systemui.keyguard.ScreenLifecycle
+
+enum class ScreenState {
+ /** Screen is fully off. */
+ SCREEN_OFF,
+ /** Signal that the screen is turning on. */
+ SCREEN_TURNING_ON,
+ /** Screen is fully on. */
+ SCREEN_ON,
+ /** Signal that the screen is turning off. */
+ SCREEN_TURNING_OFF;
+
+ companion object {
+ fun fromScreenLifecycleInt(value: Int): ScreenState {
+ return when (value) {
+ ScreenLifecycle.SCREEN_OFF -> SCREEN_OFF
+ ScreenLifecycle.SCREEN_TURNING_ON -> SCREEN_TURNING_ON
+ ScreenLifecycle.SCREEN_ON -> SCREEN_ON
+ ScreenLifecycle.SCREEN_TURNING_OFF -> SCREEN_TURNING_OFF
+ else -> throw IllegalArgumentException("Invalid screen value: $value")
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
index 728dd39..9872d97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt
@@ -37,6 +37,7 @@
view: LottieAnimationView,
viewModel: UdfpsAodViewModel,
) {
+ view.alpha = 0f
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
index 26ef468..0113628 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt
@@ -38,6 +38,7 @@
view: ImageView,
viewModel: BackgroundViewModel,
) {
+ view.alpha = 0f
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
index 0ab8e52..bab04f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt
@@ -42,6 +42,7 @@
view: LottieAnimationView,
viewModel: FingerprintViewModel,
) {
+ view.alpha = 0f
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 68cdfb6..373f705 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -4,7 +4,7 @@
import android.hardware.face.FaceSensorPropertiesInternal
import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.log.dagger.FaceAuthLog
@@ -240,7 +240,7 @@
)
}
- fun hardwareError(errorStatus: ErrorAuthenticationStatus) {
+ fun hardwareError(errorStatus: ErrorFaceAuthenticationStatus) {
logBuffer.log(
TAG,
DEBUG,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
index 0b33904..258284e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
@@ -31,15 +31,12 @@
private const val TAG = "RecommendationViewHolder"
/** ViewHolder for a Smartspace media recommendation. */
-class RecommendationViewHolder private constructor(itemView: View, updatedView: Boolean) {
+class RecommendationViewHolder private constructor(itemView: View) {
val recommendations = itemView as TransitionLayout
// Recommendation screen
- lateinit var cardIcon: ImageView
- lateinit var mediaAppIcons: List<CachingIconView>
- lateinit var mediaProgressBars: List<SeekBar>
- lateinit var cardTitle: TextView
+ val cardTitle: TextView = itemView.requireViewById(R.id.media_rec_title)
val mediaCoverContainers =
listOf<ViewGroup>(
@@ -47,53 +44,25 @@
itemView.requireViewById(R.id.media_cover2_container),
itemView.requireViewById(R.id.media_cover3_container)
)
+ val mediaAppIcons: List<CachingIconView> =
+ mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
val mediaTitles: List<TextView> =
- if (updatedView) {
- mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
- } else {
- listOf(
- itemView.requireViewById(R.id.media_title1),
- itemView.requireViewById(R.id.media_title2),
- itemView.requireViewById(R.id.media_title3)
- )
- }
+ mediaCoverContainers.map { it.requireViewById(R.id.media_title) }
val mediaSubtitles: List<TextView> =
- if (updatedView) {
- mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
- } else {
- listOf(
- itemView.requireViewById(R.id.media_subtitle1),
- itemView.requireViewById(R.id.media_subtitle2),
- itemView.requireViewById(R.id.media_subtitle3)
- )
+ mediaCoverContainers.map { it.requireViewById(R.id.media_subtitle) }
+ val mediaProgressBars: List<SeekBar> =
+ mediaCoverContainers.map {
+ it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
+ // Media playback is in the direction of tape, not time, so it stays LTR
+ layoutDirection = View.LAYOUT_DIRECTION_LTR
+ }
}
val mediaCoverItems: List<ImageView> =
- if (updatedView) {
- mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
- } else {
- listOf(
- itemView.requireViewById(R.id.media_cover1),
- itemView.requireViewById(R.id.media_cover2),
- itemView.requireViewById(R.id.media_cover3)
- )
- }
+ mediaCoverContainers.map { it.requireViewById(R.id.media_cover) }
val gutsViewHolder = GutsViewHolder(itemView)
init {
- if (updatedView) {
- mediaAppIcons = mediaCoverContainers.map { it.requireViewById(R.id.media_rec_app_icon) }
- cardTitle = itemView.requireViewById(R.id.media_rec_title)
- mediaProgressBars =
- mediaCoverContainers.map {
- it.requireViewById<SeekBar?>(R.id.media_progress_bar).apply {
- // Media playback is in the direction of tape, not time, so it stays LTR
- layoutDirection = View.LAYOUT_DIRECTION_LTR
- }
- }
- } else {
- cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
- }
(recommendations.background as IlluminationDrawable).let { background ->
mediaCoverContainers.forEach { background.registerLightSource(it) }
background.registerLightSource(gutsViewHolder.cancel)
@@ -114,63 +83,31 @@
* @param parent Parent of inflated view.
*/
@JvmStatic
- fun create(
- inflater: LayoutInflater,
- parent: ViewGroup,
- updatedView: Boolean,
- ): RecommendationViewHolder {
+ fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
val itemView =
- if (updatedView) {
- inflater.inflate(
- R.layout.media_recommendations,
- parent,
- false /* attachToRoot */
- )
- } else {
- inflater.inflate(
- R.layout.media_smartspace_recommendations,
- parent,
- false /* attachToRoot */
- )
- }
+ inflater.inflate(R.layout.media_recommendations, parent, false /* attachToRoot */)
// Because this media view (a TransitionLayout) is used to measure and layout the views
// in various states before being attached to its parent, we can't depend on the default
// LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction.
itemView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE
- return RecommendationViewHolder(itemView, updatedView)
+ return RecommendationViewHolder(itemView)
}
// Res Ids for the control components on the recommendation view.
val controlsIds =
setOf(
- R.id.recommendation_card_icon,
R.id.media_rec_title,
- R.id.media_cover1,
- R.id.media_cover2,
- R.id.media_cover3,
R.id.media_cover,
R.id.media_cover1_container,
R.id.media_cover2_container,
R.id.media_cover3_container,
- R.id.media_title1,
- R.id.media_title2,
- R.id.media_title3,
R.id.media_title,
- R.id.media_subtitle1,
- R.id.media_subtitle2,
- R.id.media_subtitle3,
R.id.media_subtitle,
)
val mediaTitlesAndSubtitlesIds =
setOf(
- R.id.media_title1,
- R.id.media_title2,
- R.id.media_title3,
R.id.media_title,
- R.id.media_subtitle1,
- R.id.media_subtitle2,
- R.id.media_subtitle3,
R.id.media_subtitle,
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 70b5e75..398dcf2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -744,11 +744,7 @@
val newRecs = mediaControlPanelFactory.get()
newRecs.attachRecommendation(
- RecommendationViewHolder.create(
- LayoutInflater.from(context),
- mediaContent,
- mediaFlags.isRecommendationCardUpdateEnabled()
- )
+ RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
)
newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
val lp =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index a978b92..a12bc2c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -784,14 +784,7 @@
contentDescription =
mRecommendationViewHolder.getGutsViewHolder().getGutsText().getText();
} else if (data != null) {
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- contentDescription = mContext.getString(
- R.string.controls_media_smartspace_rec_header);
- } else {
- contentDescription = mContext.getString(
- R.string.controls_media_smartspace_rec_description,
- data.getAppName(mContext));
- }
+ contentDescription = mContext.getString(R.string.controls_media_smartspace_rec_header);
} else {
contentDescription = null;
}
@@ -1377,10 +1370,6 @@
PackageManager packageManager = mContext.getPackageManager();
// Set up media source app's logo.
Drawable icon = packageManager.getApplicationIcon(applicationInfo);
- if (!mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- ImageView headerLogoImageView = mRecommendationViewHolder.getCardIcon();
- headerLogoImageView.setImageDrawable(icon);
- }
fetchAndUpdateRecommendationColors(icon);
// Set up media rec card's tap action if applicable.
@@ -1401,16 +1390,7 @@
// Set up media item cover.
ImageView mediaCoverImageView = mediaCoverItems.get(itemIndex);
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- bindRecommendationArtwork(
- recommendation,
- data.getPackageName(),
- itemIndex
- );
- } else {
- mediaCoverImageView.post(
- () -> mediaCoverImageView.setImageIcon(recommendation.getIcon()));
- }
+ bindRecommendationArtwork(recommendation, data.getPackageName(), itemIndex);
// Set up the media item's click listener if applicable.
ViewGroup mediaCoverContainer = mediaCoverContainers.get(itemIndex);
@@ -1455,21 +1435,18 @@
subtitleView.setText(subtitle);
// Set up progress bar
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- SeekBar mediaProgressBar =
- mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
- TextView mediaSubtitle =
- mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
- // show progress bar if the recommended album is played.
- Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
- if (progress == null || progress <= 0.0) {
- mediaProgressBar.setVisibility(View.GONE);
- mediaSubtitle.setVisibility(View.VISIBLE);
- } else {
- mediaProgressBar.setProgress((int) (progress * 100));
- mediaProgressBar.setVisibility(View.VISIBLE);
- mediaSubtitle.setVisibility(View.GONE);
- }
+ SeekBar mediaProgressBar =
+ mRecommendationViewHolder.getMediaProgressBars().get(itemIndex);
+ TextView mediaSubtitle = mRecommendationViewHolder.getMediaSubtitles().get(itemIndex);
+ // show progress bar if the recommended album is played.
+ Double progress = MediaDataUtils.getDescriptionProgress(recommendation.getExtras());
+ if (progress == null || progress <= 0.0) {
+ mediaProgressBar.setVisibility(View.GONE);
+ mediaSubtitle.setVisibility(View.VISIBLE);
+ } else {
+ mediaProgressBar.setProgress((int) (progress * 100));
+ mediaProgressBar.setVisibility(View.VISIBLE);
+ mediaSubtitle.setVisibility(View.GONE);
}
}
mSmartspaceMediaItemsCount = NUM_REQUIRED_RECOMMENDATIONS;
@@ -1588,9 +1565,7 @@
int textPrimaryColor = MediaColorSchemesKt.textPrimaryFromScheme(colorScheme);
int textSecondaryColor = MediaColorSchemesKt.textSecondaryFromScheme(colorScheme);
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
- }
+ mRecommendationViewHolder.getCardTitle().setTextColor(textPrimaryColor);
mRecommendationViewHolder.getRecommendations()
.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
@@ -1598,12 +1573,9 @@
(title) -> title.setTextColor(textPrimaryColor));
mRecommendationViewHolder.getMediaSubtitles().forEach(
(subtitle) -> subtitle.setTextColor(textSecondaryColor));
- if (mFeatureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)) {
- mRecommendationViewHolder.getMediaProgressBars().forEach(
- (progressBar) -> progressBar.setProgressTintList(
- ColorStateList.valueOf(textPrimaryColor))
- );
- }
+ mRecommendationViewHolder.getMediaProgressBars().forEach(
+ (progressBar) -> progressBar.setProgressTintList(
+ ColorStateList.valueOf(textPrimaryColor)));
mRecommendationViewHolder.getGutsViewHolder().setColors(colorScheme);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 4bca778..1dd969f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -655,13 +655,8 @@
expandedLayout.load(context, R.xml.media_session_expanded)
}
TYPE.RECOMMENDATION -> {
- if (mediaFlags.isRecommendationCardUpdateEnabled()) {
- collapsedLayout.load(context, R.xml.media_recommendations_view_collapsed)
- expandedLayout.load(context, R.xml.media_recommendations_view_expanded)
- } else {
- collapsedLayout.load(context, R.xml.media_recommendation_collapsed)
- expandedLayout.load(context, R.xml.media_recommendation_expanded)
- }
+ collapsedLayout.load(context, R.xml.media_recommendations_collapsed)
+ expandedLayout.load(context, R.xml.media_recommendations_expanded)
}
}
refreshState()
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 01f047c..09aef88 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
@@ -49,10 +49,6 @@
*/
fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
- /** Check whether we show the updated recommendation card. */
- fun isRecommendationCardUpdateEnabled() =
- featureFlags.isEnabled(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE)
-
/** Check whether to get progress information for resume players */
fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index eb1ca66..809edc0 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -70,6 +70,19 @@
}
}
+ /**
+ * Wakes up the device if dreaming with a screensaver.
+ *
+ * @param why a string explaining why we're waking the device for debugging purposes. Should be
+ * in SCREAMING_SNAKE_CASE.
+ * @param wakeReason the PowerManager-based reason why we're waking the device.
+ */
+ fun wakeUpIfDreaming(why: String, @PowerManager.WakeReason wakeReason: Int) {
+ if (statusBarStateController.isDreaming) {
+ repository.wakeUp(why, wakeReason)
+ }
+ }
+
companion object {
private const val FSI_WAKE_WHY = "full_screen_intent"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 83b373d..856a92e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -58,6 +58,7 @@
private final BrightnessSliderController mBrightnessSliderController;
private final BrightnessMirrorHandler mBrightnessMirrorHandler;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private boolean mListening;
private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() {
@Override
@@ -159,12 +160,15 @@
public void setListening(boolean listening, boolean expanded) {
setListening(listening && expanded);
- // Set the listening as soon as the QS fragment starts listening regardless of the
- //expansion, so it will update the current brightness before the slider is visible.
- if (listening) {
- mBrightnessController.registerCallbacks();
- } else {
- mBrightnessController.unregisterCallbacks();
+ if (listening != mListening) {
+ mListening = listening;
+ // Set the listening as soon as the QS fragment starts listening regardless of the
+ //expansion, so it will update the current brightness before the slider is visible.
+ if (listening) {
+ mBrightnessController.registerCallbacks();
+ } else {
+ mBrightnessController.unregisterCallbacks();
+ }
}
}
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 d81e4c2..764ef68 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -116,6 +116,10 @@
private lateinit var customDrawableView: ImageView
private lateinit var chevronView: ImageView
private var mQsLogger: QSLogger? = null
+
+ /**
+ * Controls if tile background is set to a [RippleDrawable] see [setClickable]
+ */
protected var showRippleEffect = true
private lateinit var ripple: RippleDrawable
@@ -440,7 +444,6 @@
protected open fun handleStateChanged(state: QSTile.State) {
val allowAnimations = animationsEnabled()
- showRippleEffect = state.showRippleEffect
isClickable = state.state != Tile.STATE_UNAVAILABLE
isLongClickable = state.handlesLongClick
icon.setIcon(state, allowAnimations)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index a444e76..9f10e6f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -155,7 +155,6 @@
state.contentDescription = state.label;
state.value = mPowerSave;
state.expandedAccessibilityClassName = Switch.class.getName();
- state.showRippleEffect = mSetting.getValue() == 0;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index a60d1ad..9ffcba6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -169,7 +169,6 @@
state.icon = ResourceIcon.get(state.state == Tile.STATE_ACTIVE
? R.drawable.qs_light_dark_theme_icon_on
: R.drawable.qs_light_dark_theme_icon_off);
- state.showRippleEffect = false;
state.expandedAccessibilityClassName = Switch.class.getName();
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
index 285ff74..b23c4ec 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
@@ -77,7 +77,7 @@
authenticationInteractor.isUnlocked
.map { isUnlocked ->
val currentSceneKey = sceneInteractor.currentScene(CONTAINER_NAME).value.key
- val isBypassEnabled = authenticationInteractor.isBypassEnabled.value
+ val isBypassEnabled = authenticationInteractor.isBypassEnabled()
when {
isUnlocked ->
when (currentSceneKey) {
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 5aa5fee..f9324a9 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
@@ -16,19 +16,25 @@
package com.android.systemui.scene.ui.view
+import android.view.Gravity
+import android.view.View
import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
+import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import java.time.Instant
import kotlinx.coroutines.launch
object SceneWindowRootViewBinder {
@@ -77,6 +83,9 @@
)
)
+ val legacyView = view.requireViewById<View>(R.id.legacy_window_root)
+ view.addView(createVisibilityToggleView(legacyView))
+
launch {
viewModel.isVisible.collect { isVisible ->
onVisibilityChangedInternal(isVisible)
@@ -89,4 +98,28 @@
}
}
}
+
+ private var clickCount = 0
+ private var lastClick = Instant.now()
+
+ /**
+ * A temporary UI to toggle on/off the visibility of the given [otherView]. It is toggled by
+ * tapping 5 times in quick succession on the device camera (top center).
+ */
+ // TODO(b/291321285): Remove this when the Flexiglass UI is mature enough to turn off legacy
+ // SysUI altogether.
+ private fun createVisibilityToggleView(otherView: View): View {
+ val toggleView = View(otherView.context)
+ toggleView.layoutParams = FrameLayout.LayoutParams(200, 200, Gravity.CENTER_HORIZONTAL)
+ toggleView.setOnClickListener {
+ val now = Instant.now()
+ clickCount = if (now.minusSeconds(2) > lastClick) 1 else clickCount + 1
+ if (clickCount == 5) {
+ otherView.isVisible = !otherView.isVisible
+ clickCount = 0
+ }
+ lastClick = now
+ }
+ return toggleView
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 9594ba3..6af9b73 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -22,7 +22,6 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
-import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.hardware.display.BrightnessInfo;
@@ -31,10 +30,10 @@
import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -43,6 +42,8 @@
import android.util.Log;
import android.util.MathUtils;
+import androidx.annotation.Nullable;
+
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -52,10 +53,13 @@
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.concurrent.Executor;
-import javax.inject.Inject;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
private static final String TAG = "CentralSurfaces.BrightnessController";
@@ -75,8 +79,11 @@
private final DisplayManager mDisplayManager;
private final UserTracker mUserTracker;
private final DisplayTracker mDisplayTracker;
+ @Nullable
private final IVrManager mVrManager;
+ private final SecureSettings mSecureSettings;
+
private final Executor mMainExecutor;
private final Handler mBackgroundHandler;
private final BrightnessObserver mBrightnessObserver;
@@ -106,6 +113,8 @@
/** ContentObserver to watch brightness */
private class BrightnessObserver extends ContentObserver {
+ private boolean mObserving = false;
+
BrightnessObserver(Handler handler) {
super(handler);
}
@@ -124,19 +133,17 @@
}
public void startObserving() {
- final ContentResolver cr = mContext.getContentResolver();
- cr.unregisterContentObserver(this);
- cr.registerContentObserver(
- BRIGHTNESS_MODE_URI,
- false, this, UserHandle.USER_ALL);
- mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener,
- new HandlerExecutor(mHandler));
+ if (!mObserving) {
+ mObserving = true;
+ mSecureSettings.registerContentObserverForUser(
+ BRIGHTNESS_MODE_URI,
+ false, this, UserHandle.USER_ALL);
+ }
}
public void stopObserving() {
- final ContentResolver cr = mContext.getContentResolver();
- cr.unregisterContentObserver(this);
- mDisplayTracker.removeCallback(mBrightnessListener);
+ mSecureSettings.unregisterContentObserver(this);
+ mObserving = false;
}
}
@@ -159,6 +166,8 @@
}
mBrightnessObserver.startObserving();
+ mDisplayTracker.addBrightnessChangeCallback(mBrightnessListener,
+ new HandlerExecutor(mMainHandler));
mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
// Update the slider and mode before attaching the listener so we don't
@@ -166,7 +175,7 @@
mUpdateModeRunnable.run();
mUpdateSliderRunnable.run();
- mHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
+ mMainHandler.sendEmptyMessage(MSG_ATTACH_LISTENER);
}
};
@@ -187,9 +196,10 @@
}
mBrightnessObserver.stopObserving();
+ mDisplayTracker.removeCallback(mBrightnessListener);
mUserTracker.removeCallback(mUserChangedCallback);
- mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
+ mMainHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
}
};
@@ -225,7 +235,7 @@
mBrightnessMin = info.brightnessMinimum;
// Value is passed as intbits, since this is what the message takes.
final int valueAsIntBits = Float.floatToIntBits(info.brightness);
- mHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
+ mMainHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
inVrMode ? 1 : 0).sendToTarget();
}
};
@@ -233,14 +243,14 @@
private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
@Override
public void onVrStateChanged(boolean enabled) {
- mHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0)
+ mMainHandler.obtainMessage(MSG_VR_MODE_CHANGED, enabled ? 1 : 0, 0)
.sendToTarget();
}
};
- private final Handler mHandler = new Handler() {
+ private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
- public void handleMessage(Message msg) {
+ public boolean handleMessage(Message msg) {
mExternalChange = true;
try {
switch (msg.what) {
@@ -257,14 +267,18 @@
updateVrMode(msg.arg1 != 0);
break;
default:
- super.handleMessage(msg);
+ return false;
+
}
} finally {
mExternalChange = false;
}
+ return true;
}
};
+ private final Handler mMainHandler;
+
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@Override
@@ -274,12 +288,17 @@
}
};
+ @AssistedInject
public BrightnessController(
Context context,
- ToggleSlider control,
+ @Assisted ToggleSlider control,
UserTracker userTracker,
DisplayTracker displayTracker,
+ DisplayManager displayManager,
+ SecureSettings secureSettings,
+ @Nullable IVrManager iVrManager,
@Main Executor mainExecutor,
+ @Main Looper mainLooper,
@Background Handler bgHandler) {
mContext = context;
mControl = control;
@@ -288,22 +307,23 @@
mBackgroundHandler = bgHandler;
mUserTracker = userTracker;
mDisplayTracker = displayTracker;
- mBrightnessObserver = new BrightnessObserver(mHandler);
-
+ mSecureSettings = secureSettings;
mDisplayId = mContext.getDisplayId();
- PowerManager pm = context.getSystemService(PowerManager.class);
+ mDisplayManager = displayManager;
+ mVrManager = iVrManager;
- mDisplayManager = context.getSystemService(DisplayManager.class);
- mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
- Context.VR_SERVICE));
+ mMainHandler = new Handler(mainLooper, mHandlerCallback);
+ mBrightnessObserver = new BrightnessObserver(mMainHandler);
}
public void registerCallbacks() {
+ mBackgroundHandler.removeCallbacks(mStartListeningRunnable);
mBackgroundHandler.post(mStartListeningRunnable);
}
/** Unregister all call backs, both to and from the controller */
public void unregisterCallbacks() {
+ mBackgroundHandler.removeCallbacks(mStopListeningRunnable);
mBackgroundHandler.post(mStopListeningRunnable);
mControlValueInitialized = false;
}
@@ -418,38 +438,12 @@
mSliderAnimator.start();
}
+
+
/** Factory for creating a {@link BrightnessController}. */
- public static class Factory {
- private final Context mContext;
- private final UserTracker mUserTracker;
- private final DisplayTracker mDisplayTracker;
- private final Executor mMainExecutor;
- private final Handler mBackgroundHandler;
-
- @Inject
- public Factory(
- Context context,
- UserTracker userTracker,
- DisplayTracker displayTracker,
- @Main Executor mainExecutor,
- @Background Handler bgHandler) {
- mContext = context;
- mUserTracker = userTracker;
- mDisplayTracker = displayTracker;
- mMainExecutor = mainExecutor;
- mBackgroundHandler = bgHandler;
- }
-
+ @AssistedFactory
+ public interface Factory {
/** Create a {@link BrightnessController} */
- public BrightnessController create(ToggleSlider toggleSlider) {
- return new BrightnessController(
- mContext,
- toggleSlider,
- mUserTracker,
- mDisplayTracker,
- mMainExecutor,
- mBackgroundHandler);
- }
+ BrightnessController create(ToggleSlider toggleSlider);
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 182e456..38b1f14 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -23,7 +23,6 @@
import android.app.Activity;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.Handler;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
@@ -37,10 +36,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -56,26 +52,21 @@
private BrightnessController mBrightnessController;
private final BrightnessSliderController.Factory mToggleSliderFactory;
- private final UserTracker mUserTracker;
- private final DisplayTracker mDisplayTracker;
+ private final BrightnessController.Factory mBrightnessControllerFactory;
private final DelayableExecutor mMainExecutor;
- private final Handler mBackgroundHandler;
private final AccessibilityManagerWrapper mAccessibilityMgr;
private Runnable mCancelTimeoutRunnable;
@Inject
public BrightnessDialog(
- UserTracker userTracker,
- DisplayTracker displayTracker,
- BrightnessSliderController.Factory factory,
+ BrightnessSliderController.Factory brightnessSliderfactory,
+ BrightnessController.Factory brightnessControllerFactory,
@Main DelayableExecutor mainExecutor,
- @Background Handler bgHandler,
- AccessibilityManagerWrapper accessibilityMgr) {
- mUserTracker = userTracker;
- mDisplayTracker = displayTracker;
- mToggleSliderFactory = factory;
+ AccessibilityManagerWrapper accessibilityMgr
+ ) {
+ mToggleSliderFactory = brightnessSliderfactory;
+ mBrightnessControllerFactory = brightnessControllerFactory;
mMainExecutor = mainExecutor;
- mBackgroundHandler = bgHandler;
mAccessibilityMgr = accessibilityMgr;
}
@@ -121,8 +112,7 @@
controller.init();
frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
- mBrightnessController = new BrightnessController(
- this, controller, mUserTracker, mDisplayTracker, mMainExecutor, mBackgroundHandler);
+ mBrightnessController = mBrightnessControllerFactory.create(controller);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt
new file mode 100644
index 0000000..45fc68a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/LockscreenHostedDreamGestureListener.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.os.PowerManager
+import android.view.GestureDetector
+import android.view.MotionEvent
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import javax.inject.Inject
+
+/**
+ * This gestureListener will wake up by tap when the device is dreaming but not dozing, and the
+ * selected screensaver is hosted in lockscreen. Tap is gated by the falsing manager.
+ *
+ * Touches go through the [NotificationShadeWindowViewController].
+ */
+@SysUISingleton
+class LockscreenHostedDreamGestureListener
+@Inject
+constructor(
+ private val falsingManager: FalsingManager,
+ private val powerInteractor: PowerInteractor,
+ private val statusBarStateController: StatusBarStateController,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val keyguardRepository: KeyguardRepository,
+ private val shadeLogger: ShadeLogger,
+) : GestureDetector.SimpleOnGestureListener() {
+ private val TAG = this::class.simpleName
+
+ override fun onSingleTapUp(e: MotionEvent): Boolean {
+ if (shouldHandleMotionEvent()) {
+ if (!falsingManager.isFalseTap(LOW_PENALTY)) {
+ shadeLogger.d("$TAG#onSingleTapUp tap handled, requesting wakeUpIfDreaming")
+ powerInteractor.wakeUpIfDreaming(
+ "DREAMING_SINGLE_TAP",
+ PowerManager.WAKE_REASON_TAP
+ )
+ } else {
+ shadeLogger.d("$TAG#onSingleTapUp false tap ignored")
+ }
+ return true
+ }
+ return false
+ }
+
+ private fun shouldHandleMotionEvent(): Boolean {
+ return keyguardRepository.isActiveDreamLockscreenHosted.value &&
+ statusBarStateController.state == StatusBarState.KEYGUARD &&
+ !primaryBouncerInteractor.isBouncerShowing()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ea15035..9399d48 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -168,7 +168,6 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -224,6 +223,8 @@
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.animation.FlingAnimationUtils;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -234,8 +235,6 @@
import javax.inject.Inject;
import javax.inject.Provider;
-import kotlin.Unit;
-
import kotlinx.coroutines.CoroutineDispatcher;
@SysUISingleton
@@ -440,8 +439,6 @@
private final FalsingCollector mFalsingCollector;
private final ShadeHeadsUpTrackerImpl mShadeHeadsUpTracker = new ShadeHeadsUpTrackerImpl();
private final ShadeFoldAnimator mShadeFoldAnimator = new ShadeFoldAnimatorImpl();
- private final ShadeNotificationPresenterImpl mShadeNotificationPresenter =
- new ShadeNotificationPresenterImpl();
private boolean mShowIconsWhenExpanded;
private int mIndicationBottomPadding;
@@ -1473,7 +1470,7 @@
// so we should not add a padding for them
stackScrollerPadding = 0;
} else {
- stackScrollerPadding = mQsController.getUnlockedStackScrollerPadding();
+ stackScrollerPadding = mQsController.getHeaderHeight();
}
} else {
stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
@@ -1520,7 +1517,7 @@
userSwitcherPreferredY,
darkAmount, mOverStretchAmount,
bypassEnabled,
- mQsController.getUnlockedStackScrollerPadding(),
+ mQsController.getHeaderHeight(),
mQsController.computeExpansionFraction(),
mDisplayTopInset,
mSplitShadeEnabled,
@@ -3323,23 +3320,6 @@
).printTableData(ipw);
}
- private final class ShadeNotificationPresenterImpl implements ShadeNotificationPresenter{
- @Override
- public RemoteInputController.Delegate createRemoteInputDelegate() {
- return mNotificationStackScrollLayoutController.createDelegate();
- }
-
- @Override
- public boolean hasPulsingNotifications() {
- return mNotificationListContainer.hasPulsingNotifications();
- }
- }
-
- @Override
- public ShadeNotificationPresenter getShadeNotificationPresenter() {
- return mShadeNotificationPresenter;
- }
-
@Override
public void initDependencies(
CentralSurfaces centralSurfaces,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index bb2be66..b328c55 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -543,7 +543,6 @@
state.forceUserActivity,
state.launchingActivityFromNotification,
state.mediaBackdropShowing,
- state.wallpaperSupportsAmbientMode,
state.windowNotTouchable,
state.componentsForcingTopUi,
state.forceOpenTokens,
@@ -734,12 +733,6 @@
apply(mCurrentState);
}
- @Override
- public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
- mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode;
- apply(mCurrentState);
- }
-
/**
* @param state The {@link StatusBarStateController} of the status bar.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index 7812f07..d252943 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -46,7 +46,6 @@
@JvmField var forceUserActivity: Boolean = false,
@JvmField var launchingActivityFromNotification: Boolean = false,
@JvmField var mediaBackdropShowing: Boolean = false,
- @JvmField var wallpaperSupportsAmbientMode: Boolean = false,
@JvmField var windowNotTouchable: Boolean = false,
@JvmField var componentsForcingTopUi: MutableSet<String> = mutableSetOf(),
@JvmField var forceOpenTokens: MutableSet<Any> = mutableSetOf(),
@@ -84,7 +83,6 @@
forceUserActivity.toString(),
launchingActivityFromNotification.toString(),
mediaBackdropShowing.toString(),
- wallpaperSupportsAmbientMode.toString(),
windowNotTouchable.toString(),
componentsForcingTopUi.toString(),
forceOpenTokens.toString(),
@@ -124,7 +122,6 @@
forceUserActivity: Boolean,
launchingActivity: Boolean,
backdropShowing: Boolean,
- wallpaperSupportsAmbientMode: Boolean,
notTouchable: Boolean,
componentsForcingTopUi: MutableSet<String>,
forceOpenTokens: MutableSet<Any>,
@@ -153,7 +150,6 @@
this.forceUserActivity = forceUserActivity
this.launchingActivityFromNotification = launchingActivity
this.mediaBackdropShowing = backdropShowing
- this.wallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode
this.windowNotTouchable = notTouchable
this.componentsForcingTopUi.clear()
this.componentsForcingTopUi.addAll(componentsForcingTopUi)
@@ -200,7 +196,6 @@
"forceUserActivity",
"launchingActivity",
"backdropShowing",
- "wallpaperSupportsAmbientMode",
"notTouchable",
"componentsForcingTopUi",
"forceOpenTokens",
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 5c41d57..108ea68 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -16,6 +16,7 @@
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.util.kotlin.JavaAdapterKt.collectFlow;
@@ -46,6 +47,7 @@
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.compose.ComposeFacade;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -72,7 +74,6 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.util.time.SystemClock;
@@ -87,7 +88,7 @@
/**
* Controller for {@link NotificationShadeWindowView}.
*/
-@CentralSurfacesComponent.CentralSurfacesScope
+@SysUISingleton
public class NotificationShadeWindowViewController {
private static final String TAG = "NotifShadeWindowVC";
private final FalsingCollector mFalsingCollector;
@@ -102,9 +103,12 @@
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final AmbientState mAmbientState;
private final PulsingGestureListener mPulsingGestureListener;
+ private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
private final NotificationInsetsController mNotificationInsetsController;
private final boolean mIsTrackpadCommonEnabled;
+ private final FeatureFlags mFeatureFlags;
private GestureDetector mPulsingWakeupGestureHandler;
+ private GestureDetector mDreamingWakeupGestureHandler;
private View mBrightnessMirror;
private boolean mTouchActive;
private boolean mTouchCancelled;
@@ -156,6 +160,7 @@
NotificationInsetsController notificationInsetsController,
AmbientState ambientState,
PulsingGestureListener pulsingGestureListener,
+ LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
KeyguardBouncerViewModel keyguardBouncerViewModel,
KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
@@ -187,8 +192,10 @@
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mAmbientState = ambientState;
mPulsingGestureListener = pulsingGestureListener;
+ mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
mNotificationInsetsController = notificationInsetsController;
mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
+ mFeatureFlags = featureFlags;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -237,7 +244,10 @@
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mPulsingGestureListener);
-
+ if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+ mDreamingWakeupGestureHandler = new GestureDetector(mView.getContext(),
+ mLockscreenHostedDreamGestureListener);
+ }
mView.setLayoutInsetsController(mNotificationInsetsController);
mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
@Override
@@ -291,6 +301,10 @@
mFalsingCollector.onTouchEvent(ev);
mPulsingWakeupGestureHandler.onTouchEvent(ev);
+ if (mDreamingWakeupGestureHandler != null
+ && mDreamingWakeupGestureHandler.onTouchEvent(ev)) {
+ return true;
+ }
if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index fba0120..5c1dd56 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -29,6 +29,8 @@
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.plugins.qs.QS
@@ -36,6 +38,7 @@
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.util.ViewController
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -54,7 +57,10 @@
private val shadeHeaderController: ShadeHeaderController,
private val shadeExpansionStateManager: ShadeExpansionStateManager,
private val fragmentService: FragmentService,
- @Main private val delayableExecutor: DelayableExecutor
+ @Main private val delayableExecutor: DelayableExecutor,
+ private val featureFlags: FeatureFlags,
+ private val
+ notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
private var qsExpanded = false
@@ -118,6 +124,9 @@
isGestureNavigation = QuickStepContract.isGesturalMode(mode)
}
isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
+
+ mView.setStackScroller(notificationStackScrollLayoutController.getView())
+ mView.setMigratingNSSL(featureFlags.isEnabled(Flags.MIGRATE_NSSL))
}
public override fun onViewAttached() {
@@ -254,14 +263,17 @@
}
private fun setNotificationsConstraints(constraintSet: ConstraintSet) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ return
+ }
val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
+ val nsslId = R.id.notification_stack_scroller
constraintSet.apply {
- connect(R.id.notification_stack_scroller, START, startConstraintId, START)
- setMargin(R.id.notification_stack_scroller, START,
- if (splitShadeEnabled) 0 else panelMarginHorizontal)
- setMargin(R.id.notification_stack_scroller, END, panelMarginHorizontal)
- setMargin(R.id.notification_stack_scroller, TOP, topMargin)
- setMargin(R.id.notification_stack_scroller, BOTTOM, notificationsBottomMargin)
+ connect(nsslId, START, startConstraintId, START)
+ setMargin(nsslId, START, if (splitShadeEnabled) 0 else panelMarginHorizontal)
+ setMargin(nsslId, END, panelMarginHorizontal)
+ setMargin(nsslId, TOP, topMargin)
+ setMargin(nsslId, BOTTOM, notificationsBottomMargin)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index e5b84bd..3b3df50 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -23,6 +23,7 @@
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
import android.view.WindowInsets;
import androidx.annotation.Nullable;
@@ -56,6 +57,7 @@
private QS mQs;
private View mQSContainer;
private int mLastQSPaddingBottom;
+ private boolean mIsMigratingNSSL;
/**
* These are used to compute the bounding box containing the shade and the notification scrim,
@@ -75,10 +77,13 @@
protected void onFinishInflate() {
super.onFinishInflate();
mQsFrame = findViewById(R.id.qs_frame);
- mStackScroller = findViewById(R.id.notification_stack_scroller);
mKeyguardStatusBar = findViewById(R.id.keyguard_header);
}
+ void setStackScroller(View stackScroller) {
+ mStackScroller = stackScroller;
+ }
+
@Override
public void onFragmentViewCreated(String tag, Fragment fragment) {
mQs = (QS) fragment;
@@ -108,7 +113,7 @@
}
public void setNotificationsMarginBottom(int margin) {
- LayoutParams params = (LayoutParams) mStackScroller.getLayoutParams();
+ MarginLayoutParams params = (MarginLayoutParams) mStackScroller.getLayoutParams();
params.bottomMargin = margin;
mStackScroller.setLayoutParams(params);
}
@@ -173,8 +178,15 @@
super.dispatchDraw(canvas);
}
+ void setMigratingNSSL(boolean isMigrating) {
+ mIsMigratingNSSL = isMigrating;
+ }
+
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ if (mIsMigratingNSSL) {
+ return super.drawChild(canvas, child, drawingTime);
+ }
int layoutIndex = mLayoutDrawingOrder.indexOf(child);
if (layoutIndex >= 0) {
return super.drawChild(canvas, mDrawingOrderedChildren.get(layoutIndex), drawingTime);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 025c4611..baac57c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -233,7 +233,6 @@
private int mMaxExpansionHeight;
/** Expansion fraction of the notification shade */
private float mShadeExpandedFraction;
- private int mPeekHeight;
private float mLastOverscroll;
private boolean mExpansionFromOverscroll;
private boolean mExpansionEnabledPolicy = true;
@@ -429,7 +428,6 @@
final ViewConfiguration configuration = ViewConfiguration.get(this.mPanelView.getContext());
mTouchSlop = configuration.getScaledTouchSlop();
mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
- mPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mPanelView.getContext());
mScrimCornerRadius = mResources.getDimensionPixelSize(
R.dimen.notification_scrim_corner_radius);
@@ -500,12 +498,7 @@
}
int getHeaderHeight() {
- return mQs.getHeader().getHeight();
- }
-
- /** Returns the padding of the stackscroller when unlocked */
- int getUnlockedStackScrollerPadding() {
- return (mQs != null ? mQs.getHeader().getHeight() : 0) + mPeekHeight;
+ return isQsFragmentCreated() ? mQs.getHeader().getHeight() : 0;
}
private boolean isRemoteInputActiveWithKeyboardUp() {
@@ -2090,8 +2083,6 @@
ipw.println(mMaxExpansionHeight);
ipw.print("mShadeExpandedFraction=");
ipw.println(mShadeExpandedFraction);
- ipw.print("mPeekHeight=");
- ipw.println(mPeekHeight);
ipw.print("mLastOverscroll=");
ipw.println(mLastOverscroll);
ipw.print("mExpansionFromOverscroll=");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 6e78357..be21df1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -44,6 +44,7 @@
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
@@ -185,6 +186,14 @@
return notificationShadeWindowView.findViewById(R.id.keyguard_root_view)
}
+ @Provides
+ @SysUISingleton
+ fun providesSharedNotificationContainer(
+ notificationShadeWindowView: NotificationShadeWindowView,
+ ): SharedNotificationContainer {
+ return notificationShadeWindowView.findViewById(R.id.shared_notification_container)
+ }
+
// TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 8d5c30b..2532bad 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -18,6 +18,7 @@
import android.view.ViewPropertyAnimator
import com.android.systemui.statusbar.GestureRecorder
import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
@@ -63,6 +64,9 @@
/** Animates the view from its current alpha to zero then runs the runnable. */
fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable): ViewPropertyAnimator
+ /** Returns the NSSL controller. */
+ val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
/** Set whether the bouncer is showing. */
fun setBouncerShowing(bouncerShowing: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 9aa5eb0..d5b5c87 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -19,9 +19,7 @@
import android.view.ViewGroup
import android.view.ViewTreeObserver
import com.android.systemui.keyguard.shared.model.WakefulnessModel
-import com.android.systemui.statusbar.RemoteInputController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
import com.android.systemui.statusbar.phone.KeyguardStatusBarView
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController
@@ -141,9 +139,6 @@
/** Returns the StatusBarState. */
val barState: Int
- /** Returns the NSSL controller. */
- val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
-
/** Sets the amount of progress in the status bar launch animation. */
fun applyLaunchAnimationProgress(linearProgress: Float)
@@ -261,9 +256,6 @@
/** Returns the ShadeFoldAnimator. */
val shadeFoldAnimator: ShadeFoldAnimator
- /** Returns the ShadeNotificationPresenter. */
- val shadeNotificationPresenter: ShadeNotificationPresenter
-
companion object {
/**
* Returns a multiplicative factor to use when determining the falsing threshold for touches
@@ -325,16 +317,7 @@
fun cancelFoldToAodAnimation()
/** Returns the main view of the shade. */
- val view: ViewGroup
-}
-
-/** Handles the shade's interactions with StatusBarNotificationPresenter. */
-interface ShadeNotificationPresenter {
- /** Returns a new delegate for some view controller pieces of the remote input process. */
- fun createRemoteInputDelegate(): RemoteInputController.Delegate
-
- /** Returns whether the screen has temporarily woken up to display notifications. */
- fun hasPulsingNotifications(): Boolean
+ val view: ViewGroup?
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
new file mode 100644
index 0000000..287ac52
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.phone.HeadsUpAppearanceController
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/** Empty implementation of ShadeViewController for variants with no shade. */
+class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController {
+ override fun expand(animate: Boolean) {}
+ override fun expandToQs() {}
+ override fun expandToNotifications() {}
+ override val isExpandingOrCollapsing: Boolean = false
+ override val isExpanded: Boolean = false
+ override val isPanelExpanded: Boolean = false
+ override val isShadeFullyExpanded: Boolean = false
+ override fun collapse(delayed: Boolean, speedUpFactor: Float) {}
+ override fun collapse(animate: Boolean, delayed: Boolean, speedUpFactor: Float) {}
+ override fun collapseWithDuration(animationDuration: Int) {}
+ override fun instantCollapse() {}
+ override fun animateCollapseQs(fullyCollapse: Boolean) {}
+ override fun canBeCollapsed(): Boolean = false
+ override val isCollapsing: Boolean = false
+ override val isFullyCollapsed: Boolean = false
+ override val isTracking: Boolean = false
+ override val isViewEnabled: Boolean = false
+ override fun setOpenCloseListener(openCloseListener: OpenCloseListener) {}
+ override fun shouldHideStatusBarIconsWhenExpanded() = false
+ override fun blockExpansionForCurrentTouch() {}
+ override fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) {}
+ override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {}
+ override fun startExpandLatencyTracking() {}
+ override fun startBouncerPreHideAnimation() {}
+ override fun dozeTimeTick() {}
+ override fun resetViews(animate: Boolean) {}
+ override val barState: Int = 0
+ override fun applyLaunchAnimationProgress(linearProgress: Float) {}
+ override fun closeUserSwitcherIfOpen(): Boolean {
+ return false
+ }
+ override fun onBackPressed() {}
+ override fun setIsLaunchAnimationRunning(running: Boolean) {}
+ override fun setAlpha(alpha: Int, animate: Boolean) {}
+ override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
+ override fun setPulsing(pulsing: Boolean) {}
+ override fun setQsScrimEnabled(qsScrimEnabled: Boolean) {}
+ override fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean) {}
+ override fun updateSystemUiStateFlags() {}
+ override fun updateTouchableRegion() {}
+ override fun addOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
+ override fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {}
+ override fun postToView(action: Runnable): Boolean {
+ return false
+ }
+ override fun transitionToExpandedShade(delay: Long) {}
+ override val isUnlockHintRunning: Boolean = false
+
+ override fun resetViewGroupFade() {}
+ override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {}
+ override fun setOverStretchAmount(amount: Float) {}
+ override fun setKeyguardStatusBarAlpha(alpha: Float) {}
+ override fun showAodUi() {}
+ override fun isFullyExpanded(): Boolean {
+ return false
+ }
+ override fun handleExternalTouch(event: MotionEvent): Boolean {
+ return false
+ }
+ override fun startTrackingExpansionFromStatusBar() {}
+ override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
+ override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl()
+}
+
+class ShadeHeadsUpTrackerEmptyImpl : ShadeHeadsUpTracker {
+ override fun addTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {}
+ override fun removeTrackingHeadsUpListener(listener: Consumer<ExpandableNotificationRow>) {}
+ override fun setHeadsUpAppearanceController(
+ headsUpAppearanceController: HeadsUpAppearanceController?
+ ) {}
+ override val trackedHeadsUpNotification: ExpandableNotificationRow? = null
+}
+
+class ShadeFoldAnimatorEmptyImpl : ShadeFoldAnimator {
+ override fun prepareFoldToAodAnimation() {}
+ override fun startFoldToAodAnimation(
+ startAction: Runnable,
+ endAction: Runnable,
+ cancelAction: Runnable,
+ ) {}
+ override fun cancelFoldToAodAnimation() {}
+ override val view: ViewGroup? = null
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 47a4641..5ac542b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -112,9 +112,6 @@
/** Sets the state of whether heads up is showing or not. */
default void setHeadsUpShowing(boolean showing) {}
- /** Sets whether the wallpaper supports ambient mode or not. */
- default void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {}
-
/** Gets whether the wallpaper is showing or not. */
default boolean isShowingWallpaper() {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 5f28ecb..577ad20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -16,27 +16,21 @@
package com.android.systemui.statusbar.notification
-import android.content.Context
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import javax.inject.Inject
-class NotifPipelineFlags @Inject constructor(
- val context: Context,
- val featureFlags: FeatureFlags,
- val sysPropFlags: FlagResolver,
+class NotifPipelineFlags
+@Inject
+constructor(
+ private val featureFlags: FeatureFlags,
+ private val sysPropFlags: FlagResolver,
) {
fun isDevLoggingEnabled(): Boolean =
featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
fun allowDismissOngoing(): Boolean =
- sysPropFlags.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)
-
- fun isOtpRedactionEnabled(): Boolean =
- sysPropFlags.isEnabled(NotificationFlags.OTP_REDACTION)
-
- val isNoHunForOldWhenEnabled: Boolean
- get() = featureFlags.isEnabled(Flags.NO_HUN_FOR_OLD_WHEN)
+ sysPropFlags.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt
new file mode 100644
index 0000000..d268e35
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinator.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Filter out notifications on the lockscreen if the lockscreen hosted dream is active. If the user
+ * stops dreaming, pulls the shade down or unlocks the device, then the notifications are unhidden.
+ */
+@CoordinatorScope
+class DreamCoordinator
+@Inject
+constructor(
+ private val statusBarStateController: SysuiStatusBarStateController,
+ @Application private val scope: CoroutineScope,
+ private val keyguardRepository: KeyguardRepository,
+) : Coordinator {
+ private var isOnKeyguard = false
+ private var isLockscreenHostedDream = false
+
+ override fun attach(pipeline: NotifPipeline) {
+ pipeline.addPreGroupFilter(filter)
+ statusBarStateController.addCallback(statusBarStateListener)
+ scope.launch { attachFilterOnDreamingStateChange() }
+ recordStatusBarState(statusBarStateController.state)
+ }
+
+ private val filter =
+ object : NotifFilter("LockscreenHostedDreamFilter") {
+ var isFiltering = false
+ override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean {
+ return isFiltering
+ }
+ inline fun update(msg: () -> String) {
+ val wasFiltering = isFiltering
+ isFiltering = isLockscreenHostedDream && isOnKeyguard
+ if (wasFiltering != isFiltering) {
+ invalidateList(msg())
+ }
+ }
+ }
+
+ private val statusBarStateListener =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ recordStatusBarState(newState)
+ }
+ }
+
+ private suspend fun attachFilterOnDreamingStateChange() {
+ keyguardRepository.isActiveDreamLockscreenHosted.collect { isDreaming ->
+ recordDreamingState(isDreaming)
+ }
+ }
+
+ private fun recordStatusBarState(newState: Int) {
+ isOnKeyguard = newState == StatusBarState.KEYGUARD
+ filter.update { "recordStatusBarState: " + StatusBarState.toString(newState) }
+ }
+
+ private fun recordDreamingState(isDreaming: Boolean) {
+ isLockscreenHostedDream = isDreaming
+ filter.update { "recordLockscreenHostedDreamState: $isDreaming" }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index e5953cf..0ccab9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -15,6 +15,8 @@
*/
package com.android.systemui.statusbar.notification.collection.coordinator
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.PipelineDumpable
import com.android.systemui.statusbar.notification.collection.PipelineDumper
@@ -31,32 +33,34 @@
@CoordinatorScope
class NotifCoordinatorsImpl @Inject constructor(
- sectionStyleProvider: SectionStyleProvider,
- dataStoreCoordinator: DataStoreCoordinator,
- hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
- hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
- keyguardCoordinator: KeyguardCoordinator,
- rankingCoordinator: RankingCoordinator,
- appOpsCoordinator: AppOpsCoordinator,
- deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
- bubbleCoordinator: BubbleCoordinator,
- headsUpCoordinator: HeadsUpCoordinator,
- gutsCoordinator: GutsCoordinator,
- conversationCoordinator: ConversationCoordinator,
- debugModeCoordinator: DebugModeCoordinator,
- groupCountCoordinator: GroupCountCoordinator,
- groupWhenCoordinator: GroupWhenCoordinator,
- mediaCoordinator: MediaCoordinator,
- preparationCoordinator: PreparationCoordinator,
- remoteInputCoordinator: RemoteInputCoordinator,
- rowAppearanceCoordinator: RowAppearanceCoordinator,
- stackCoordinator: StackCoordinator,
- shadeEventCoordinator: ShadeEventCoordinator,
- smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
- viewConfigCoordinator: ViewConfigCoordinator,
- visualStabilityCoordinator: VisualStabilityCoordinator,
- sensitiveContentCoordinator: SensitiveContentCoordinator,
- dismissibilityCoordinator: DismissibilityCoordinator,
+ sectionStyleProvider: SectionStyleProvider,
+ featureFlags: FeatureFlags,
+ dataStoreCoordinator: DataStoreCoordinator,
+ hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+ hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+ keyguardCoordinator: KeyguardCoordinator,
+ rankingCoordinator: RankingCoordinator,
+ appOpsCoordinator: AppOpsCoordinator,
+ deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
+ bubbleCoordinator: BubbleCoordinator,
+ headsUpCoordinator: HeadsUpCoordinator,
+ gutsCoordinator: GutsCoordinator,
+ conversationCoordinator: ConversationCoordinator,
+ debugModeCoordinator: DebugModeCoordinator,
+ groupCountCoordinator: GroupCountCoordinator,
+ groupWhenCoordinator: GroupWhenCoordinator,
+ mediaCoordinator: MediaCoordinator,
+ preparationCoordinator: PreparationCoordinator,
+ remoteInputCoordinator: RemoteInputCoordinator,
+ rowAppearanceCoordinator: RowAppearanceCoordinator,
+ stackCoordinator: StackCoordinator,
+ shadeEventCoordinator: ShadeEventCoordinator,
+ smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
+ viewConfigCoordinator: ViewConfigCoordinator,
+ visualStabilityCoordinator: VisualStabilityCoordinator,
+ sensitiveContentCoordinator: SensitiveContentCoordinator,
+ dismissibilityCoordinator: DismissibilityCoordinator,
+ dreamCoordinator: DreamCoordinator,
) : NotifCoordinators {
private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
@@ -96,6 +100,10 @@
mCoordinators.add(remoteInputCoordinator)
mCoordinators.add(dismissibilityCoordinator)
+ if (featureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+ mCoordinators.add(dreamCoordinator)
+ }
+
// Manually add Ordered Sections
mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 609f9d4..0c43da0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -582,10 +582,6 @@
}
private boolean shouldSuppressHeadsUpWhenAwakeForOldWhen(NotificationEntry entry, boolean log) {
- if (!mFlags.isNoHunForOldWhenEnabled()) {
- return false;
- }
-
final Notification notification = entry.getSbn().getNotification();
if (notification == null) {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index b2a3780..867e08b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -18,6 +18,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import dagger.Binds;
import dagger.Module;
@@ -58,9 +59,13 @@
@ElementsIntoSet
@Named(NOTIF_REMOTEVIEWS_FACTORIES)
static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
- FeatureFlags featureFlags
+ FeatureFlags featureFlags,
+ PrecomputedTextViewFactory precomputedTextViewFactory
) {
final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
+ if (featureFlags.isEnabled(Flags.PRECOMPUTED_TEXT)) {
+ replacementFactories.add(precomputedTextViewFactory);
+ }
return replacementFactories;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt
new file mode 100644
index 0000000..57c82dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedImageFloatingTextView.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.RemoteViews
+import com.android.internal.widget.ImageFloatingTextView
+
+/** Precomputed version of [ImageFloatingTextView] */
+@RemoteViews.RemoteView
+class PrecomputedImageFloatingTextView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ ImageFloatingTextView(context, attrs, defStyleAttr), TextPrecomputer {
+
+ override fun setTextAsync(text: CharSequence?): Runnable = precompute(this, text)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt
new file mode 100644
index 0000000..8508b1f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextView.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.RemoteViews
+import android.widget.TextView
+
+/**
+ * A user interface element that uses the PrecomputedText API to display text in a notification,
+ * with the help of RemoteViews.
+ */
+@RemoteViews.RemoteView
+class PrecomputedTextView
+@JvmOverloads
+constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+ TextView(context, attrs, defStyleAttr), TextPrecomputer {
+
+ override fun setTextAsync(text: CharSequence?): Runnable = precompute(this, text)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt
new file mode 100644
index 0000000..b002330
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PrecomputedTextViewFactory.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.TextView
+import com.android.internal.widget.ImageFloatingTextView
+import javax.inject.Inject
+
+class PrecomputedTextViewFactory @Inject constructor() : NotifRemoteViewsFactory {
+ override fun instantiate(
+ parent: View?,
+ name: String,
+ context: Context,
+ attrs: AttributeSet
+ ): View? {
+ return when (name) {
+ TextView::class.java.name,
+ TextView::class.java.simpleName -> PrecomputedTextView(context, attrs)
+ ImageFloatingTextView::class.java.name ->
+ PrecomputedImageFloatingTextView(context, attrs)
+ else -> null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt
new file mode 100644
index 0000000..49f4a33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/TextPrecomputer.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.notification.row
+
+import android.text.PrecomputedText
+import android.text.Spannable
+import android.util.Log
+import android.widget.TextView
+
+internal interface TextPrecomputer {
+ /**
+ * Creates PrecomputedText from given text and returns a runnable which sets precomputed text to
+ * the textview on main thread.
+ *
+ * @param text text to be converted to PrecomputedText
+ * @return Runnable that sets precomputed text on the main thread
+ */
+ fun precompute(
+ textView: TextView,
+ text: CharSequence?,
+ logException: Boolean = true
+ ): Runnable {
+ val precomputedText: Spannable? =
+ text?.let { PrecomputedText.create(it, textView.textMetricsParams) }
+
+ return Runnable {
+ try {
+ textView.text = precomputedText
+ } catch (exception: IllegalArgumentException) {
+ if (logException) {
+ Log.wtf(
+ /* tag = */ TAG,
+ /* msg = */ "PrecomputedText setText failed for TextView:$textView",
+ /* tr = */ exception
+ )
+ }
+ textView.text = text
+ }
+ }
+ }
+
+ private companion object {
+ private const val TAG = "TextPrecomputer"
+ }
+}
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 8063c8c..c1ceb3c 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
@@ -3758,20 +3758,20 @@
case MotionEvent.ACTION_UP:
if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
- debugLog("handleEmptySpaceClick: touch event propagated further");
+ debugShadeLog("handleEmptySpaceClick: touch event propagated further");
mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
}
break;
default:
- debugLog("handleEmptySpaceClick: MotionEvent ignored");
+ debugShadeLog("handleEmptySpaceClick: MotionEvent ignored");
}
}
- private void debugLog(@CompileTimeConstant final String s) {
+ private void debugShadeLog(@CompileTimeConstant final String s) {
if (mLogger == null) {
return;
}
- mLogger.d(s);
+ mLogger.logShadeDebugEvent(s);
}
private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 2c38b8d..3396306 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -7,6 +7,7 @@
import com.android.systemui.log.core.LogLevel.ERROR
import com.android.systemui.log.dagger.NotificationHeadsUpLog
import com.android.systemui.log.dagger.NotificationRenderLog
+import com.android.systemui.log.dagger.ShadeLog
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD
@@ -19,7 +20,8 @@
class NotificationStackScrollLogger @Inject constructor(
@NotificationHeadsUpLog private val buffer: LogBuffer,
- @NotificationRenderLog private val notificationRenderBuffer: LogBuffer
+ @NotificationRenderLog private val notificationRenderBuffer: LogBuffer,
+ @ShadeLog private val shadeLogBuffer: LogBuffer,
) {
fun hunAnimationSkipped(entry: NotificationEntry, reason: String) {
buffer.log(TAG, INFO, {
@@ -63,7 +65,7 @@
})
}
- fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
+ fun logShadeDebugEvent(@CompileTimeConstant msg: String) = shadeLogBuffer.log(TAG, DEBUG, msg)
fun logEmptySpaceClick(
isBelowLastNotification: Boolean,
@@ -71,7 +73,7 @@
touchIsClick: Boolean,
motionEventDesc: String
) {
- buffer.log(TAG, DEBUG, {
+ shadeLogBuffer.log(TAG, DEBUG, {
int1 = statusBarState
bool1 = touchIsClick
bool2 = isBelowLastNotification
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
new file mode 100644
index 0000000..874450b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.notification.stack.domain.interactor
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Encapsulates business-logic specifically related to the shared notification stack container. */
+class SharedNotificationContainerInteractor
+@Inject
+constructor(
+ configurationRepository: ConfigurationRepository,
+ private val context: Context,
+) {
+ val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
+ configurationRepository.onAnyConfigurationChange
+ .onStart { emit(Unit) }
+ .map { _ ->
+ with(context.resources) {
+ ConfigurationBasedDimensions(
+ useSplitShade = getBoolean(R.bool.config_use_split_notification_shade),
+ useLargeScreenHeader =
+ getBoolean(R.bool.config_use_large_screen_shade_header),
+ marginHorizontal =
+ getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal),
+ marginBottom =
+ getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
+ marginTop = getDimensionPixelSize(R.dimen.notification_panel_margin_top),
+ marginTopLargeScreen =
+ getDimensionPixelSize(R.dimen.large_screen_shade_header_height),
+ )
+ }
+ }
+ .distinctUntilChanged()
+
+ data class ConfigurationBasedDimensions(
+ val useSplitShade: Boolean,
+ val useLargeScreenHeader: Boolean,
+ val marginHorizontal: Int,
+ val marginBottom: Int,
+ val marginTop: Int,
+ val marginTopLargeScreen: Int,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
new file mode 100644
index 0000000..688843d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/SharedNotificationContainer.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.android.systemui.statusbar.notification.stack.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.VERTICAL
+import com.android.systemui.R
+
+/**
+ * Container for the stack scroller, so that the bounds can be externally specified, such as from
+ * the keyguard or shade scenes.
+ */
+class SharedNotificationContainer(
+ context: Context,
+ private val attrs: AttributeSet?,
+) :
+ ConstraintLayout(
+ context,
+ attrs,
+ ) {
+
+ private val baseConstraintSet = ConstraintSet()
+
+ init {
+ baseConstraintSet.apply {
+ create(R.id.nssl_guideline, VERTICAL)
+ setGuidelinePercent(R.id.nssl_guideline, 0.5f)
+ }
+ baseConstraintSet.applyTo(this)
+ }
+
+ fun addNotificationStackScrollLayout(nssl: View) {
+ addView(nssl)
+ }
+
+ fun updateConstraints(
+ useSplitShade: Boolean,
+ marginStart: Int,
+ marginTop: Int,
+ marginEnd: Int,
+ marginBottom: Int
+ ) {
+ val constraintSet = ConstraintSet()
+ constraintSet.clone(baseConstraintSet)
+
+ val startConstraintId =
+ if (useSplitShade) {
+ R.id.nssl_guideline
+ } else {
+ PARENT_ID
+ }
+ val nsslId = R.id.notification_stack_scroller
+ constraintSet.apply {
+ connect(nsslId, START, startConstraintId, START)
+ connect(nsslId, END, PARENT_ID, END)
+ connect(nsslId, BOTTOM, PARENT_ID, BOTTOM)
+ connect(nsslId, TOP, PARENT_ID, TOP)
+ setMargin(nsslId, START, marginStart)
+ setMargin(nsslId, END, marginEnd)
+ setMargin(nsslId, TOP, marginTop)
+ setMargin(nsslId, BOTTOM, marginBottom)
+ }
+ constraintSet.applyTo(this)
+ }
+}
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
new file mode 100644
index 0000000..fb1d55d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewbinder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import kotlinx.coroutines.launch
+
+/** Binds the shared notification container to its view-model. */
+object SharedNotificationContainerBinder {
+
+ @JvmStatic
+ fun bind(
+ view: SharedNotificationContainer,
+ viewModel: SharedNotificationContainerViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.configurationBasedDimensions.collect {
+ view.updateConstraints(
+ useSplitShade = it.useSplitShade,
+ marginStart = it.marginStart,
+ marginTop = it.marginTop,
+ marginEnd = it.marginEnd,
+ marginBottom = it.marginBottom,
+ )
+ }
+ }
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000..b2e5ac1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.notification.stack.ui.viewmodel
+
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/** View-model for the shared notification container */
+class SharedNotificationContainerViewModel
+@Inject
+constructor(
+ interactor: SharedNotificationContainerInteractor,
+) {
+ val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
+ interactor.configurationBasedDimensions
+ .map {
+ ConfigurationBasedDimensions(
+ marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
+ marginEnd = it.marginHorizontal,
+ marginBottom = it.marginBottom,
+ marginTop =
+ if (it.useLargeScreenHeader) it.marginTopLargeScreen else it.marginTop,
+ useSplitShade = it.useSplitShade,
+ )
+ }
+ .distinctUntilChanged()
+
+ data class ConfigurationBasedDimensions(
+ val marginStart: Int,
+ val marginTop: Int,
+ val marginEnd: Int,
+ val marginBottom: Int,
+ val useSplitShade: Boolean,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 66d217b..dc021fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -160,6 +160,7 @@
private KeyguardViewController mKeyguardViewController;
private DozeScrimController mDozeScrimController;
private KeyguardViewMediator mKeyguardViewMediator;
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private PendingAuthenticated mPendingAuthenticated = null;
private boolean mHasScreenTurnedOnSinceAuthenticating;
private boolean mFadedAwayAfterWakeAndUnlock;
@@ -280,7 +281,8 @@
LatencyTracker latencyTracker,
ScreenOffAnimationController screenOffAnimationController,
VibratorHelper vibrator,
- SystemClock systemClock
+ SystemClock systemClock,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager
) {
mPowerManager = powerManager;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -308,6 +310,7 @@
mVibratorHelper = vibrator;
mLogger = biometricUnlockLogger;
mSystemClock = systemClock;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
dumpManager.registerDumpable(this);
}
@@ -449,8 +452,19 @@
// During wake and unlock, we need to draw black before waking up to avoid abrupt
// brightness changes due to display state transitions.
Runnable wakeUp = ()-> {
- if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
+ // Check to see if we are still locked when we are waking and unlocking from dream.
+ // This runnable should be executed after unlock. If that's true, we could be not
+ // dreaming, but still locked. In this case, we should attempt to authenticate instead
+ // of waking up.
+ if (mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM
+ && !mKeyguardStateController.isUnlocked()
+ && !mUpdateMonitor.isDreaming()) {
+ // Post wakeUp runnable is called from a callback in keyguard.
+ mHandler.post(() -> mKeyguardViewController.notifyKeyguardAuthenticated(
+ false /* primaryAuth */));
+ } else if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
mLogger.i("bio wakelock: Authenticated, waking up...");
+
mPowerManager.wakeUp(
mSystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_BIOMETRIC,
@@ -462,7 +476,7 @@
Trace.endSection();
};
- if (mMode != MODE_NONE) {
+ if (mMode != MODE_NONE && mMode != MODE_WAKE_AND_UNLOCK_FROM_DREAM) {
wakeUp.run();
}
switch (mMode) {
@@ -484,6 +498,10 @@
Trace.endSection();
break;
case MODE_WAKE_AND_UNLOCK_FROM_DREAM:
+ // In the case of waking and unlocking from dream, waking up is delayed until after
+ // unlock is complete to avoid conflicts during each sequence's transitions.
+ mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(wakeUp);
+ // Execution falls through here to proceed unlocking.
case MODE_WAKE_AND_UNLOCK_PULSING:
case MODE_WAKE_AND_UNLOCK:
if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 2b9c3d3..acd6e49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -259,8 +259,6 @@
void readyForKeyguardDone();
- void setLockscreenUser(int newUserId);
-
void showKeyguard();
boolean hideKeyguard();
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 0a7ee52..8361d6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -51,7 +51,6 @@
import android.app.StatusBarManager;
import android.app.TaskInfo;
import android.app.UiModeManager;
-import android.app.WallpaperInfo;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -413,7 +412,6 @@
private final Point mCurrentDisplaySize = new Point();
- protected NotificationShadeWindowView mNotificationShadeWindowView;
protected PhoneStatusBarView mStatusBarView;
private PhoneStatusBarViewController mPhoneStatusBarViewController;
private PhoneStatusBarTransitions mStatusBarTransitions;
@@ -456,7 +454,8 @@
private final FalsingManager mFalsingManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final ConfigurationController mConfigurationController;
- protected NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ private final Lazy<NotificationShadeWindowViewController>
+ mNotificationShadeWindowViewControllerLazy;
private final DozeParameters mDozeParameters;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
@@ -722,6 +721,7 @@
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
NotificationShadeWindowController notificationShadeWindowController,
+ Lazy<NotificationShadeWindowViewController> notificationShadeWindowViewControllerLazy,
NotificationShelfController notificationShelfController,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
DozeParameters dozeParameters,
@@ -825,6 +825,7 @@
mAssistManagerLazy = assistManagerLazy;
mConfigurationController = configurationController;
mNotificationShadeWindowController = notificationShadeWindowController;
+ mNotificationShadeWindowViewControllerLazy = notificationShadeWindowViewControllerLazy;
mNotificationShelfController = notificationShelfController;
mStackScrollerController = notificationStackScrollLayoutController;
mStackScroller = mStackScrollerController.getView();
@@ -978,16 +979,6 @@
createAndAddWindows(result);
- if (mWallpaperSupported) {
- // Make sure we always have the most current wallpaper info.
- IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED);
- mBroadcastDispatcher.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter,
- null /* handler */, UserHandle.ALL);
- mWallpaperChangedReceiver.onReceive(mContext, null);
- } else if (DEBUG) {
- Log.v(TAG, "start(): no wallpaper service ");
- }
-
// Set up the initial notification state. This needs to happen before CommandQueue.disable()
setUpPresenter();
@@ -1073,7 +1064,7 @@
mDozeServiceHost.initialize(
this,
mStatusBarKeyguardViewManager,
- mNotificationShadeWindowViewController,
+ getNotificationShadeWindowViewController(),
mShadeSurface,
mAmbientIndicationContainer);
updateLightRevealScrimVisibility();
@@ -1235,8 +1226,8 @@
updateTheme();
inflateStatusBarWindow();
- mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
- mWallpaperController.setRootView(mNotificationShadeWindowView);
+ getNotificationShadeWindowView().setOnTouchListener(getStatusBarWindowTouchListener());
+ mWallpaperController.setRootView(getNotificationShadeWindowView());
// TODO: Deal with the ugliness that comes from having some of the status bar broken out
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
@@ -1257,7 +1248,7 @@
mStatusBarView = statusBarView;
mPhoneStatusBarViewController = statusBarViewController;
mStatusBarTransitions = statusBarTransitions;
- mNotificationShadeWindowViewController
+ getNotificationShadeWindowViewController()
.setStatusBarViewController(mPhoneStatusBarViewController);
// Ensure we re-propagate panel expansion values to the panel controller and
// any listeners it may have, such as PanelBar. This will also ensure we
@@ -1271,7 +1262,7 @@
mStatusBarInitializer.initializeStatusBar(
mCentralSurfacesComponent::createCollapsedStatusBarFragment);
- mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
+ mStatusBarTouchableRegionManager.setup(this, getNotificationShadeWindowView());
createNavigationBar(result);
@@ -1279,7 +1270,7 @@
mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
}
- mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById(
+ mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById(
R.id.ambient_indication_container);
mAutoHideController.setStatusBar(new AutoHideUiElement() {
@@ -1304,10 +1295,10 @@
}
});
- ScrimView scrimBehind = mNotificationShadeWindowView.findViewById(R.id.scrim_behind);
- ScrimView notificationsScrim = mNotificationShadeWindowView
+ ScrimView scrimBehind = getNotificationShadeWindowView().findViewById(R.id.scrim_behind);
+ ScrimView notificationsScrim = getNotificationShadeWindowView()
.findViewById(R.id.scrim_notifications);
- ScrimView scrimInFront = mNotificationShadeWindowView.findViewById(R.id.scrim_in_front);
+ ScrimView scrimInFront = getNotificationShadeWindowView().findViewById(R.id.scrim_in_front);
mScrimController.setScrimVisibleListener(scrimsVisible -> {
mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
@@ -1345,7 +1336,7 @@
mNotificationShelfController,
mHeadsUpManager);
- BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
+ BackDropView backdrop = getNotificationShadeWindowView().findViewById(R.id.backdrop);
if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
mMediaManager.setup(null, null, null, mScrimController, null);
} else {
@@ -1364,7 +1355,7 @@
});
// Set up the quick settings tile panel
- final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
+ final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
if (container != null) {
FragmentHostManager fragmentHostManager =
mFragmentService.getFragmentHostManager(container);
@@ -1379,7 +1370,7 @@
.withDefault(this::createDefaultQSFragment)
.build());
mBrightnessMirrorController = new BrightnessMirrorController(
- mNotificationShadeWindowView,
+ getNotificationShadeWindowView(),
mShadeSurface,
mNotificationShadeDepthControllerLazy.get(),
mBrightnessSliderFactory,
@@ -1396,7 +1387,7 @@
});
}
- mReportRejectedTouch = mNotificationShadeWindowView
+ mReportRejectedTouch = getNotificationShadeWindowView()
.findViewById(R.id.report_rejected_touch);
if (mReportRejectedTouch != null) {
updateReportRejectedTouchVisibility();
@@ -1544,7 +1535,7 @@
protected QS createDefaultQSFragment() {
return mFragmentService
- .getFragmentHostManager(mNotificationShadeWindowView)
+ .getFragmentHostManager(getNotificationShadeWindowView())
.create(QSFragment.class);
}
@@ -1553,7 +1544,7 @@
mActivityLaunchAnimator.setCallback(mActivityLaunchAnimatorCallback);
mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
- mNotificationShadeWindowViewController,
+ getNotificationShadeWindowViewController(),
mNotifListContainer,
mHeadsUpManager,
mJankMonitor);
@@ -1592,7 +1583,7 @@
mAutoHideController.checkUserAutoHide(event);
mRemoteInputManager.checkRemoteInputOutside(event);
mShadeController.onStatusBarTouch(event);
- return mNotificationShadeWindowView.onTouchEvent(event);
+ return getNotificationShadeWindowView().onTouchEvent(event);
};
}
@@ -1606,15 +1597,12 @@
mCentralSurfacesComponent::createCollapsedStatusBarFragment);
ViewGroup windowRootView = mCentralSurfacesComponent.getWindowRootView();
- mNotificationShadeWindowView = mCentralSurfacesComponent.getNotificationShadeWindowView();
- mNotificationShadeWindowViewController = mCentralSurfacesComponent
- .getNotificationShadeWindowViewController();
// TODO(b/277762009): Inject [NotificationShadeWindowView] directly into the controller.
// (Right now, there's a circular dependency.)
mNotificationShadeWindowController.setWindowRootView(windowRootView);
- mNotificationShadeWindowViewController.setupExpandedStatusBar();
+ getNotificationShadeWindowViewController().setupExpandedStatusBar();
mShadeController.setNotificationShadeWindowViewController(
- mNotificationShadeWindowViewController);
+ getNotificationShadeWindowViewController());
mBackActionInteractor.setup(mQsController, mShadeSurface);
mPresenter = mCentralSurfacesComponent.getNotificationPresenter();
mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter();
@@ -1633,6 +1621,14 @@
mCommandQueue.addCallback(mCommandQueueCallbacks);
}
+ protected NotificationShadeWindowViewController getNotificationShadeWindowViewController() {
+ return mNotificationShadeWindowViewControllerLazy.get();
+ }
+
+ protected NotificationShadeWindowView getNotificationShadeWindowView() {
+ return getNotificationShadeWindowViewController().getView();
+ }
+
protected void startKeyguard() {
Trace.beginSection("CentralSurfaces#startKeyguard");
mStatusBarStateController.addCallback(mStateListener,
@@ -1688,7 +1684,7 @@
@Override
public AuthKeyguardMessageArea getKeyguardMessageArea() {
- return mNotificationShadeWindowViewController.getKeyguardMessageArea();
+ return getNotificationShadeWindowViewController().getKeyguardMessageArea();
}
@Override
@@ -1982,11 +1978,9 @@
pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported);
pw.println(" ShadeWindowView: ");
- if (mNotificationShadeWindowViewController != null) {
- mNotificationShadeWindowViewController.dump(pw, args);
- CentralSurfaces.dumpBarTransitions(
- pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
- }
+ getNotificationShadeWindowViewController().dump(pw, args);
+ CentralSurfaces.dumpBarTransitions(
+ pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
pw.println(" mMediaManager: ");
if (mMediaManager != null) {
@@ -2159,18 +2153,6 @@
};
/**
- * Notify the shade controller that the current user changed
- *
- * @param newUserId userId of the new user
- */
- @Override
- public void setLockscreenUser(int newUserId) {
- if (mWallpaperSupported) {
- mWallpaperChangedReceiver.onReceive(mContext, null);
- }
- }
-
- /**
* Reload some of our resources when the configuration changes.
*
* We don't reload everything when the configuration changes -- we probably
@@ -2860,7 +2842,7 @@
updateVisibleToUser();
updateNotificationPanelTouchState();
- mNotificationShadeWindowViewController.cancelCurrentTouch();
+ getNotificationShadeWindowViewController().cancelCurrentTouch();
if (mLaunchCameraOnFinishedGoingToSleep) {
mLaunchCameraOnFinishedGoingToSleep = false;
@@ -3172,12 +3154,6 @@
updateScrimController();
}
- @VisibleForTesting
- public void setNotificationShadeWindowViewController(
- NotificationShadeWindowViewController nswvc) {
- mNotificationShadeWindowViewController = nswvc;
- }
-
/**
* Set the amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
@@ -3412,7 +3388,7 @@
mVisible = visible;
if (visible) {
DejankUtils.notifyRendererOfExpensiveFrame(
- mNotificationShadeWindowView, "onShadeVisibilityChanged");
+ getNotificationShadeWindowView(), "onShadeVisibilityChanged");
} else {
mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
@@ -3550,33 +3526,6 @@
}
};
- /**
- * @deprecated See {@link com.android.systemui.wallpapers.data.repository.WallpaperRepository}
- * instead.
- */
- private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!mWallpaperSupported) {
- // Receiver should not have been registered at all...
- Log.wtf(TAG, "WallpaperManager not supported");
- return;
- }
- WallpaperInfo info = mWallpaperManager.getWallpaperInfoForUser(
- mUserTracker.getUserId());
- mWallpaperController.onWallpaperInfoUpdated(info);
-
- final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
- // If WallpaperInfo is null, it must be ImageWallpaper.
- final boolean supportsAmbientMode = deviceSupportsAodWallpaper
- && (info != null && info.supportsAmbientMode());
-
- mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
- mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode);
- }
- };
-
private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
public void onConfigChanged(Configuration newConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 7312db6..ed9722e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -311,6 +311,7 @@
@Override
public void dozeTimeTick() {
+ mDozeInteractor.dozeTimeTick();
mNotificationPanel.dozeTimeTick();
mAuthController.dozeTimeTick();
if (mAmbientIndicationContainer instanceof DozeReceiver) {
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 ff1b31d..924aac4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -50,12 +50,6 @@
@DevicePostureInt private var postureState: Int = DEVICE_POSTURE_UNKNOWN
private var pendingUnlock: PendingUnlock? = null
private val listeners = mutableListOf<OnBypassStateChangedListener>()
- private val postureCallback = DevicePostureController.Callback { posture ->
- if (postureState != posture) {
- postureState = posture
- notifyListeners()
- }
- }
private val faceAuthEnabledChangedCallback = object : KeyguardStateController.Callback {
override fun onFaceAuthEnabledChanged() = notifyListeners()
}
@@ -162,10 +156,8 @@
val dismissByDefault = if (context.resources.getBoolean(
com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
- tunerService.addTunable(object : TunerService.Tunable {
- override fun onTuningChanged(key: String?, newValue: String?) {
- bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
- }
+ tunerService.addTunable({ key, _ ->
+ bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
}, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
lockscreenUserManager.addUserChangedListener(
object : NotificationLockscreenUserManager.UserChangedListener {
@@ -281,8 +273,6 @@
}
companion object {
- const val BYPASS_FADE_DURATION = 67
-
private const val FACE_UNLOCK_BYPASS_NO_OVERRIDE = 0
private const val FACE_UNLOCK_BYPASS_ALWAYS = 1
private const val FACE_UNLOCK_BYPASS_NEVER = 2
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index f26a84b..dc5eb0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -38,8 +38,6 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.settings.DisplayTracker;
@@ -65,7 +63,6 @@
private final SysuiDarkIconDispatcher mStatusBarIconController;
private final BatteryController mBatteryController;
- private final boolean mUseNewLightBarLogic;
private BiometricUnlockController mBiometricUnlockController;
private LightBarTransitionsController mNavigationBarController;
@@ -123,10 +120,8 @@
DarkIconDispatcher darkIconDispatcher,
BatteryController batteryController,
NavigationModeController navModeController,
- FeatureFlags featureFlags,
DumpManager dumpManager,
DisplayTracker displayTracker) {
- mUseNewLightBarLogic = featureFlags.isEnabled(Flags.NEW_LIGHT_BAR_LOGIC);
mDarkIconColor = ctx.getColor(R.color.dark_mode_icon_color_single_tone);
mLightIconColor = ctx.getColor(R.color.light_mode_icon_color_single_tone);
mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
@@ -188,51 +183,31 @@
final boolean last = mNavigationLight;
mHasLightNavigationBar = isLight(appearance, navigationBarMode,
APPEARANCE_LIGHT_NAVIGATION_BARS);
- if (mUseNewLightBarLogic) {
- final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme;
- final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce;
- final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce;
- final boolean darkForQs = (mQsCustomizing || mQsExpanded) && !mBouncerVisible;
- final boolean darkForTop = darkForQs || mGlobalActionsVisible;
- mNavigationLight =
- ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop;
- if (DEBUG_NAVBAR) {
- mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
- .append("onNavigationBarAppearanceChanged()")
- .append(" appearance=").append(appearance)
- .append(" nbModeChanged=").append(nbModeChanged)
- .append(" navigationBarMode=").append(navigationBarMode)
- .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
- .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
- .append(" ignoreScrimForce=").append(ignoreScrimForce)
- .append(" darkForScrim=").append(darkForScrim)
- .append(" lightForScrim=").append(lightForScrim)
- .append(" darkForQs=").append(darkForQs)
- .append(" darkForTop=").append(darkForTop)
- .append(" mNavigationLight=").append(mNavigationLight)
- .append(" last=").append(last)
- .append(" timestamp=").append(System.currentTimeMillis())
- .toString();
- if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
- }
- } else {
- mNavigationLight = mHasLightNavigationBar
- && (mDirectReplying && mNavbarColorManagedByIme || !mForceDarkForScrim)
- && !mQsCustomizing;
- if (DEBUG_NAVBAR) {
- mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
- .append("onNavigationBarAppearanceChanged()")
- .append(" appearance=").append(appearance)
- .append(" nbModeChanged=").append(nbModeChanged)
- .append(" navigationBarMode=").append(navigationBarMode)
- .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
- .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
- .append(" mNavigationLight=").append(mNavigationLight)
- .append(" last=").append(last)
- .append(" timestamp=").append(System.currentTimeMillis())
- .toString();
- if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
- }
+ final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme;
+ final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce;
+ final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce;
+ final boolean darkForQs = (mQsCustomizing || mQsExpanded) && !mBouncerVisible;
+ final boolean darkForTop = darkForQs || mGlobalActionsVisible;
+ mNavigationLight =
+ ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop;
+ if (DEBUG_NAVBAR) {
+ mLastNavigationBarAppearanceChangedLog = getLogStringBuilder()
+ .append("onNavigationBarAppearanceChanged()")
+ .append(" appearance=").append(appearance)
+ .append(" nbModeChanged=").append(nbModeChanged)
+ .append(" navigationBarMode=").append(navigationBarMode)
+ .append(" navbarColorManagedByIme=").append(navbarColorManagedByIme)
+ .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+ .append(" ignoreScrimForce=").append(ignoreScrimForce)
+ .append(" darkForScrim=").append(darkForScrim)
+ .append(" lightForScrim=").append(lightForScrim)
+ .append(" darkForQs=").append(darkForQs)
+ .append(" darkForTop=").append(darkForTop)
+ .append(" mNavigationLight=").append(mNavigationLight)
+ .append(" last=").append(last)
+ .append(" timestamp=").append(System.currentTimeMillis())
+ .toString();
+ if (DEBUG_LOGS) Log.d(TAG, mLastNavigationBarAppearanceChangedLog);
}
if (mNavigationLight != last) {
updateNavigation();
@@ -311,66 +286,39 @@
public void setScrimState(ScrimState scrimState, float scrimBehindAlpha,
GradientColors scrimInFrontColor) {
- if (mUseNewLightBarLogic) {
- boolean bouncerVisibleLast = mBouncerVisible;
- boolean forceDarkForScrimLast = mForceDarkForScrim;
- boolean forceLightForScrimLast = mForceLightForScrim;
- mBouncerVisible =
- scrimState == ScrimState.BOUNCER || scrimState == ScrimState.BOUNCER_SCRIMMED;
- final boolean forceForScrim = mBouncerVisible
- || scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
- final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText();
+ boolean bouncerVisibleLast = mBouncerVisible;
+ boolean forceDarkForScrimLast = mForceDarkForScrim;
+ boolean forceLightForScrimLast = mForceLightForScrim;
+ mBouncerVisible =
+ scrimState == ScrimState.BOUNCER || scrimState == ScrimState.BOUNCER_SCRIMMED;
+ final boolean forceForScrim = mBouncerVisible
+ || scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
+ final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText();
- mForceDarkForScrim = forceForScrim && !scrimColorIsLight;
- mForceLightForScrim = forceForScrim && scrimColorIsLight;
- if (mBouncerVisible != bouncerVisibleLast) {
- reevaluate();
- } else if (mHasLightNavigationBar) {
- if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate();
- } else {
- if (mForceLightForScrim != forceLightForScrimLast) reevaluate();
- }
- if (DEBUG_NAVBAR) {
- mLastSetScrimStateLog = getLogStringBuilder()
- .append("setScrimState()")
- .append(" scrimState=").append(scrimState)
- .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
- .append(" scrimInFrontColor=").append(scrimInFrontColor)
- .append(" forceForScrim=").append(forceForScrim)
- .append(" scrimColorIsLight=").append(scrimColorIsLight)
- .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
- .append(" mBouncerVisible=").append(mBouncerVisible)
- .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
- .append(" mForceLightForScrim=").append(mForceLightForScrim)
- .append(" timestamp=").append(System.currentTimeMillis())
- .toString();
- if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
- }
+ mForceDarkForScrim = forceForScrim && !scrimColorIsLight;
+ mForceLightForScrim = forceForScrim && scrimColorIsLight;
+ if (mBouncerVisible != bouncerVisibleLast) {
+ reevaluate();
+ } else if (mHasLightNavigationBar) {
+ if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate();
} else {
- boolean forceDarkForScrimLast = mForceDarkForScrim;
- // For BOUNCER/BOUNCER_SCRIMMED cases, we assume that alpha is always below threshold.
- // This enables IMEs to control the navigation bar color.
- // For other cases, scrim should be able to veto the light navigation bar.
- // NOTE: this was also wrong for S and has been removed in the new logic.
- mForceDarkForScrim = scrimState != ScrimState.BOUNCER
- && scrimState != ScrimState.BOUNCER_SCRIMMED
- && scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD
- && !scrimInFrontColor.supportsDarkText();
- if (mHasLightNavigationBar && (mForceDarkForScrim != forceDarkForScrimLast)) {
- reevaluate();
- }
- if (DEBUG_NAVBAR) {
- mLastSetScrimStateLog = getLogStringBuilder()
- .append("setScrimState()")
- .append(" scrimState=").append(scrimState)
- .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
- .append(" scrimInFrontColor=").append(scrimInFrontColor)
- .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
- .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
- .append(" timestamp=").append(System.currentTimeMillis())
- .toString();
- if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
- }
+ if (mForceLightForScrim != forceLightForScrimLast) reevaluate();
+ }
+ if (DEBUG_NAVBAR) {
+ mLastSetScrimStateLog = getLogStringBuilder()
+ .append("setScrimState()")
+ .append(" scrimState=").append(scrimState)
+ .append(" scrimBehindAlpha=").append(scrimBehindAlpha)
+ .append(" scrimInFrontColor=").append(scrimInFrontColor)
+ .append(" forceForScrim=").append(forceForScrim)
+ .append(" scrimColorIsLight=").append(scrimColorIsLight)
+ .append(" mHasLightNavigationBar=").append(mHasLightNavigationBar)
+ .append(" mBouncerVisible=").append(mBouncerVisible)
+ .append(" mForceDarkForScrim=").append(mForceDarkForScrim)
+ .append(" mForceLightForScrim=").append(mForceLightForScrim)
+ .append(" timestamp=").append(System.currentTimeMillis())
+ .toString();
+ if (DEBUG_LOGS) Log.d(TAG, mLastSetScrimStateLog);
}
}
@@ -498,7 +446,6 @@
private final DarkIconDispatcher mDarkIconDispatcher;
private final BatteryController mBatteryController;
private final NavigationModeController mNavModeController;
- private final FeatureFlags mFeatureFlags;
private final DumpManager mDumpManager;
private final DisplayTracker mDisplayTracker;
@@ -507,14 +454,12 @@
DarkIconDispatcher darkIconDispatcher,
BatteryController batteryController,
NavigationModeController navModeController,
- FeatureFlags featureFlags,
DumpManager dumpManager,
DisplayTracker displayTracker) {
mDarkIconDispatcher = darkIconDispatcher;
mBatteryController = batteryController;
mNavModeController = navModeController;
- mFeatureFlags = featureFlags;
mDumpManager = dumpManager;
mDisplayTracker = displayTracker;
}
@@ -522,7 +467,7 @@
/** Create an {@link LightBarController} */
public LightBarController create(Context context) {
return new LightBarController(context, mDarkIconDispatcher, mBatteryController,
- mNavModeController, mFeatureFlags, mDumpManager, mDisplayTracker);
+ mNavModeController, mDumpManager, mDisplayTracker);
}
}
}
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 f0fc143..862f169 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -30,6 +30,7 @@
import com.android.systemui.flags.Flags
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeLogger
+import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
@@ -51,6 +52,7 @@
@Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
private val centralSurfaces: CentralSurfaces,
private val shadeController: ShadeController,
+ private val shadeViewController: ShadeViewController,
private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
private val userChipViewModel: StatusBarUserChipViewModel,
@@ -165,20 +167,20 @@
if (event.action == MotionEvent.ACTION_DOWN) {
// If the view that would receive the touch is disabled, just have status
// bar eat the gesture.
- if (!centralSurfaces.shadeViewController.isViewEnabled) {
+ if (!shadeViewController.isViewEnabled) {
shadeLogger.logMotionEvent(event,
"onTouchForwardedFromStatusBar: panel view disabled")
return true
}
- if (centralSurfaces.shadeViewController.isFullyCollapsed &&
+ if (shadeViewController.isFullyCollapsed &&
event.y < 1f) {
// b/235889526 Eat events on the top edge of the phone when collapsed
shadeLogger.logMotionEvent(event, "top edge touch ignored")
return true
}
- centralSurfaces.shadeViewController.startTrackingExpansionFromStatusBar()
+ shadeViewController.startTrackingExpansionFromStatusBar()
}
- return centralSurfaces.shadeViewController.handleExternalTouch(event)
+ return shadeViewController.handleExternalTouch(event)
}
}
@@ -222,6 +224,7 @@
private val userChipViewModel: StatusBarUserChipViewModel,
private val centralSurfaces: CentralSurfaces,
private val shadeController: ShadeController,
+ private val shadeViewController: ShadeViewController,
private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
private val configurationController: ConfigurationController,
@@ -241,6 +244,7 @@
progressProvider.getOrNull(),
centralSurfaces,
shadeController,
+ shadeViewController,
shadeLogger,
statusBarMoveFromCenterAnimationController,
userChipViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 33bf06a..e82ac59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -57,8 +57,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.ScrimAlpha;
@@ -254,8 +252,6 @@
private int mScrimsVisibility;
private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
- private final FeatureFlags mFeatureFlags;
- private final boolean mUseNewLightBarLogic;
private Consumer<Integer> mScrimVisibleListener;
private boolean mBlankScreen;
private boolean mScreenBlankingCallbackCalled;
@@ -311,12 +307,9 @@
KeyguardTransitionInteractor keyguardTransitionInteractor,
WallpaperRepository wallpaperRepository,
@Main CoroutineDispatcher mainDispatcher,
- LargeScreenShadeInterpolator largeScreenShadeInterpolator,
- FeatureFlags featureFlags) {
+ LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
mScrimStateListener = lightBarController::setScrimState;
mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
- mFeatureFlags = featureFlags;
- mUseNewLightBarLogic = featureFlags.isEnabled(Flags.NEW_LIGHT_BAR_LOGIC);
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
mKeyguardStateController = keyguardStateController;
@@ -1170,13 +1163,7 @@
if (mClipsQsScrim && mQsBottomVisible) {
alpha = mNotificationsAlpha;
}
- if (mUseNewLightBarLogic) {
- mScrimStateListener.accept(mState, alpha, mColors);
- } else {
- // NOTE: This wasn't wrong, but it implied that each scrim might have different colors,
- // when in fact they all share the same GradientColors instance, which we own.
- mScrimStateListener.accept(mState, alpha, mScrimInFront.getColors());
- }
+ mScrimStateListener.accept(mState, alpha, mColors);
}
private void dispatchScrimsVisible() {
@@ -1500,15 +1487,8 @@
int accent = Utils.getColorAccent(mScrimBehind.getContext()).getDefaultColor();
mColors.setMainColor(background);
mColors.setSecondaryColor(accent);
- if (mUseNewLightBarLogic) {
- final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background);
- mColors.setSupportsDarkText(isBackgroundLight);
- } else {
- // NOTE: This was totally backward, but LightBarController was flipping it back.
- // There may be other consumers of this which would struggle though
- mColors.setSupportsDarkText(
- ColorUtils.calculateContrast(mColors.getMainColor(), Color.WHITE) > 4.5);
- }
+ final boolean isBackgroundLight = !ContrastColorUtil.isColorDark(background);
+ mColors.setSupportsDarkText(isBackgroundLight);
int surface = Utils.getColorAttr(mScrimBehind.getContext(),
com.android.internal.R.attr.materialColorSurface).getDefaultColor();
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 481cf3c..9a295e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -21,6 +21,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.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -35,6 +36,7 @@
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarWindowController mStatusBarWindowController;
private final ShadeViewController mShadeViewController;
+ private final NotificationStackScrollLayoutController mNsslController;
private final KeyguardBypassController mKeyguardBypassController;
private final HeadsUpManagerPhone mHeadsUpManager;
private final StatusBarStateController mStatusBarStateController;
@@ -45,14 +47,15 @@
NotificationShadeWindowController notificationShadeWindowController,
StatusBarWindowController statusBarWindowController,
ShadeViewController shadeViewController,
+ NotificationStackScrollLayoutController nsslController,
KeyguardBypassController keyguardBypassController,
HeadsUpManagerPhone headsUpManager,
StatusBarStateController statusBarStateController,
NotificationRemoteInputManager notificationRemoteInputManager) {
-
mNotificationShadeWindowController = notificationShadeWindowController;
mStatusBarWindowController = statusBarWindowController;
mShadeViewController = shadeViewController;
+ mNsslController = nsslController;
mKeyguardBypassController = keyguardBypassController;
mHeadsUpManager = headsUpManager;
mStatusBarStateController = statusBarStateController;
@@ -85,8 +88,7 @@
//animation
// is finished.
mHeadsUpManager.setHeadsUpGoingAway(true);
- mShadeViewController.getNotificationStackScrollLayoutController()
- .runAfterAnimationFinished(() -> {
+ mNsslController.runAfterAnimationFinished(() -> {
if (!mHeadsUpManager.hasPinnedHeadsUp()) {
mNotificationShadeWindowController.setHeadsUpShowing(false);
mHeadsUpManager.setHeadsUpGoingAway(false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 35285b2..a9135ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -28,7 +28,6 @@
import android.util.Log;
import android.util.Slog;
import android.view.View;
-import android.view.accessibility.AccessibilityManager;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.InitController;
@@ -50,7 +49,6 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
@@ -59,7 +57,6 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
-import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
@@ -78,21 +75,18 @@
private final NotifShadeEventSource mNotifShadeEventSource;
private final NotificationMediaManager mMediaManager;
private final NotificationGutsManager mGutsManager;
-
private final ShadeViewController mNotificationPanel;
private final HeadsUpManagerPhone mHeadsUpManager;
private final AboveShelfObserver mAboveShelfObserver;
private final DozeScrimController mDozeScrimController;
private final CentralSurfaces mCentralSurfaces;
private final NotificationsInteractor mNotificationsInteractor;
+ private final NotificationStackScrollLayoutController mNsslController;
private final LockscreenShadeTransitionController mShadeTransitionController;
private final PowerInteractor mPowerInteractor;
private final CommandQueue mCommandQueue;
-
- private final AccessibilityManager mAccessibilityManager;
private final KeyguardManager mKeyguardManager;
private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final NotifPipelineFlags mNotifPipelineFlags;
private final IStatusBarService mBarService;
private final DynamicPrivacyController mDynamicPrivacyController;
private final NotificationListContainer mNotifListContainer;
@@ -123,11 +117,9 @@
NotifShadeEventSource notifShadeEventSource,
NotificationMediaManager notificationMediaManager,
NotificationGutsManager notificationGutsManager,
- LockscreenGestureLogger lockscreenGestureLogger,
InitController initController,
NotificationInterruptStateProvider notificationInterruptStateProvider,
NotificationRemoteInputManager remoteInputManager,
- NotifPipelineFlags notifPipelineFlags,
NotificationRemoteInputManager.Callback remoteInputManagerCallback,
NotificationListContainer notificationListContainer) {
mActivityStarter = activityStarter;
@@ -139,6 +131,7 @@
// TODO: use KeyguardStateController#isOccluded to remove this dependency
mCentralSurfaces = centralSurfaces;
mNotificationsInteractor = notificationsInteractor;
+ mNsslController = stackScrollerController;
mShadeTransitionController = shadeTransitionController;
mPowerInteractor = powerInteractor;
mCommandQueue = commandQueue;
@@ -149,10 +142,8 @@
mGutsManager = notificationGutsManager;
mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView());
mNotificationShadeWindowController = notificationShadeWindowController;
- mNotifPipelineFlags = notifPipelineFlags;
mAboveShelfObserver.setListener(statusBarWindow.findViewById(
R.id.notification_container_parent));
- mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mDozeScrimController = dozeScrimController;
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mBarService = IStatusBarService.Stub.asInterface(
@@ -170,7 +161,7 @@
}
remoteInputManager.setUpWithCallback(
remoteInputManagerCallback,
- mNotificationPanel.getShadeNotificationPresenter().createRemoteInputDelegate());
+ mNsslController.createDelegate());
initController.addPostInitTask(() -> {
mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
@@ -202,7 +193,7 @@
}
private void maybeEndAmbientPulse() {
- if (mNotificationPanel.getShadeNotificationPresenter().hasPulsingNotifications()
+ if (mNsslController.getNotificationListContainer().hasPulsingNotifications()
&& !mHeadsUpManager.hasNotifications()) {
// We were showing a pulse for a notification, but no notifications are pulsing anymore.
// Finish the pulse.
@@ -217,7 +208,6 @@
// End old BaseStatusBar.userSwitched
mCommandQueue.animateCollapsePanels();
mMediaManager.clearCurrentMediaNotification();
- mCentralSurfaces.setLockscreenUser(newUserId);
updateMediaMetaData(true, false);
}
@@ -272,22 +262,6 @@
}
};
- private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() {
- @Override
- public void checkSave(Runnable saveImportance, StatusBarNotification sbn) {
- // If the user has security enabled, show challenge if the setting is changed.
- if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier())
- && mKeyguardManager.isKeyguardLocked()) {
- onLockedNotificationImportanceChange(() -> {
- saveImportance.run();
- return true;
- });
- } else {
- saveImportance.run();
- }
- }
- };
-
private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() {
@Override
public void onSettingsClick(String key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
index 4ae460a..e77f419 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
@@ -21,8 +21,6 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.android.systemui.scene.ui.view.WindowRootView;
-import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.ShadeHeaderController;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -77,16 +75,6 @@
WindowRootView getWindowRootView();
/**
- * Creates or returns a {@link NotificationShadeWindowView}.
- */
- NotificationShadeWindowView getNotificationShadeWindowView();
-
- /**
- * Creates a NotificationShadeWindowViewController.
- */
- NotificationShadeWindowViewController getNotificationShadeWindowViewController();
-
- /**
* Creates a StatusBarHeadsUpChangeListener.
*/
StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener();
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 27cc64f..0e99c67 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,12 +19,10 @@
import android.net.wifi.WifiManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModelImpl
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
@@ -46,6 +44,8 @@
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder
import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinderImpl
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
+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.WifiRepositorySwitcher
@@ -58,9 +58,9 @@
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
-import kotlinx.coroutines.flow.Flow
import java.util.function.Supplier
import javax.inject.Named
+import kotlinx.coroutines.flow.Flow
@Module
abstract class StatusBarPipelineModule {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index bcf3b0c..24987ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -240,8 +240,10 @@
Insets.of(0, safeTouchRegionHeight, 0, 0));
}
lp.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()),
- new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()),
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars())
+ .setInsetsSize(getInsets(height)),
+ new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement())
+ .setInsetsSize(getInsets(height)),
gestureInsetsProvider
};
return lp;
@@ -306,12 +308,37 @@
mLpChanged.height =
state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : mBarHeight;
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+ int height = SystemBarUtils.getStatusBarHeightForRotation(mContext, rot);
mLpChanged.paramsForRotation[rot].height =
- state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT :
- SystemBarUtils.getStatusBarHeightForRotation(mContext, rot);
+ state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : height;
+ // The status bar height could change at runtime if one display has a cutout while
+ // another doesn't (like some foldables). It could also change when using debug cutouts.
+ // So, we need to re-fetch the height and re-apply it to the insets each time to avoid
+ // bugs like b/290300359.
+ InsetsFrameProvider[] providers = mLpChanged.paramsForRotation[rot].providedInsets;
+ if (providers != null) {
+ for (InsetsFrameProvider provider : providers) {
+ provider.setInsetsSize(getInsets(height));
+ }
+ }
}
}
+ /**
+ * Get the insets that should be applied to the status bar window given the current status bar
+ * height.
+ *
+ * The status bar window height can sometimes be full-screen (see {@link #applyHeight(State)}.
+ * However, the status bar *insets* should *not* be full-screen, because this would prevent apps
+ * from drawing any content and can cause animations to be cancelled (see b/283958440). Instead,
+ * the status bar insets should always be equal to the space occupied by the actual status bar
+ * content -- setting the insets correctly will prevent window manager from unnecessarily
+ * re-drawing this window and other windows. This method provides the correct insets.
+ */
+ private Insets getInsets(int height) {
+ return Insets.of(0, height, 0, 0);
+ }
+
private void apply(State state) {
if (!mIsAttached) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index eed7950..cbe4020 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -94,7 +94,7 @@
wakefulnessLifecycle.addObserver(this)
// TODO(b/254878364): remove this call to NPVC.getView()
- getShadeFoldAnimator().view.repeatWhenAttached {
+ getShadeFoldAnimator().view?.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) { listenForDozing(this) }
}
}
@@ -161,10 +161,9 @@
// but we should wait for the initial animation preparations to be drawn
// (setting initial alpha/translation)
// TODO(b/254878364): remove this call to NPVC.getView()
- OneShotPreDrawListener.add(
- getShadeFoldAnimator().view,
- onReady
- )
+ getShadeFoldAnimator().view?.let {
+ OneShotPreDrawListener.add(it, onReady)
+ }
} else {
// No animation, call ready callback immediately
onReady.run()
diff --git a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
index db2aca8..65a0218 100644
--- a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt
@@ -16,32 +16,34 @@
package com.android.systemui.util
-import android.app.WallpaperInfo
import android.app.WallpaperManager
import android.util.Log
import android.view.View
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.wallpapers.data.repository.WallpaperRepository
import javax.inject.Inject
import kotlin.math.max
private const val TAG = "WallpaperController"
+/**
+ * Controller for wallpaper-related logic.
+ *
+ * Note: New logic should be added to [WallpaperRepository], not this class.
+ */
@SysUISingleton
-class WallpaperController @Inject constructor(private val wallpaperManager: WallpaperManager) {
+class WallpaperController @Inject constructor(
+ private val wallpaperManager: WallpaperManager,
+ private val wallpaperRepository: WallpaperRepository,
+) {
var rootView: View? = null
private var notificationShadeZoomOut: Float = 0f
private var unfoldTransitionZoomOut: Float = 0f
- private var wallpaperInfo: WallpaperInfo? = null
-
- fun onWallpaperInfoUpdated(wallpaperInfo: WallpaperInfo?) {
- this.wallpaperInfo = wallpaperInfo
- }
-
private val shouldUseDefaultUnfoldTransition: Boolean
- get() = wallpaperInfo?.shouldUseDefaultUnfoldTransition()
+ get() = wallpaperRepository.wallpaperInfo.value?.shouldUseDefaultUnfoldTransition()
?: true
fun setNotificationShadeZoom(zoomOut: Float) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 349f368..87b2697 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -34,6 +34,7 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
@@ -82,6 +83,7 @@
import android.util.SparseBooleanArray;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.AccessibilityDelegate;
@@ -120,6 +122,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
@@ -300,6 +303,7 @@
private final DevicePostureController mDevicePostureController;
private @DevicePostureController.DevicePostureInt int mDevicePosture;
private int mOrientation;
+ private final FeatureFlags mFeatureFlags;
public VolumeDialogImpl(
Context context,
@@ -315,7 +319,9 @@
CsdWarningDialog.Factory csdWarningDialogFactory,
DevicePostureController devicePostureController,
Looper looper,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
mContext =
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
mHandler = new H(looper);
@@ -1319,12 +1325,14 @@
private void provideTouchFeedbackH(int newRingerMode) {
VibrationEffect effect = null;
+ int hapticConstant = HapticFeedbackConstants.NO_HAPTICS;
switch (newRingerMode) {
case RINGER_MODE_NORMAL:
mController.scheduleTouchFeedback();
break;
case RINGER_MODE_SILENT:
effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ hapticConstant = HapticFeedbackConstants.TOGGLE_OFF;
break;
case RINGER_MODE_VIBRATE:
// Feedback handled by onStateChange, for feedback both when user toggles
@@ -1332,8 +1340,11 @@
break;
default:
effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
+ hapticConstant = HapticFeedbackConstants.TOGGLE_ON;
}
- if (effect != null) {
+ if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ mDialogView.performHapticFeedback(hapticConstant);
+ } else if (effect != null) {
mController.vibrate(effect);
}
}
@@ -1770,7 +1781,22 @@
&& mState.ringerModeInternal != -1
&& mState.ringerModeInternal != state.ringerModeInternal
&& state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
- mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
+
+ if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ if (mShowing) {
+ // The dialog view is responsible for triggering haptics in the oneway API
+ mDialogView.performHapticFeedback(HapticFeedbackConstants.TOGGLE_ON);
+ }
+ /*
+ TODO(b/290642122): If the dialog is not showing, we have the case where haptics is
+ enabled by dragging the volume slider of Settings to a value of 0. This must be
+ handled by view Slices in Settings whilst using the performHapticFeedback API.
+ */
+
+ } else {
+ // Old behavior only active if the oneway API is not used.
+ mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK));
+ }
}
mState = state;
mDynamic.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index d0edc6e..cc9f3e1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -22,6 +22,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
@@ -61,7 +62,8 @@
InteractionJankMonitor interactionJankMonitor,
CsdWarningDialog.Factory csdFactory,
DevicePostureController devicePostureController,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ FeatureFlags featureFlags) {
VolumeDialogImpl impl = new VolumeDialogImpl(
context,
volumeDialogController,
@@ -76,7 +78,8 @@
csdFactory,
devicePostureController,
Looper.getMainLooper(),
- dumpManager);
+ dumpManager,
+ featureFlags);
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
impl.setAutomute(true);
impl.setSilentMode(false);
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
index a640589..b45b8cd 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/NoopWallpaperRepository.kt
@@ -16,9 +16,11 @@
package com.android.systemui.wallpapers.data.repository
+import android.app.WallpaperInfo
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/**
@@ -29,5 +31,6 @@
*/
@SysUISingleton
class NoopWallpaperRepository @Inject constructor() : WallpaperRepository {
+ override val wallpaperInfo: StateFlow<WallpaperInfo?> = MutableStateFlow(null).asStateFlow()
override val wallpaperSupportsAmbientMode = MutableStateFlow(false).asStateFlow()
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
index 48895ff..b8f9583 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.wallpapers.data.repository
+import android.app.WallpaperInfo
import android.app.WallpaperManager
import android.content.Context
import android.content.Intent
@@ -36,11 +37,15 @@
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
/** A repository storing information about the current wallpaper. */
interface WallpaperRepository {
+ /** Emits the current user's current wallpaper. */
+ val wallpaperInfo: StateFlow<WallpaperInfo?>
+
/** Emits true if the current user's current wallpaper supports ambient mode. */
val wallpaperSupportsAmbientMode: StateFlow<Boolean>
}
@@ -78,28 +83,35 @@
// Only update the wallpaper status once the user selection has finished.
.filter { it.selectionStatus == SelectionStatus.SELECTION_COMPLETE }
- override val wallpaperSupportsAmbientMode: StateFlow<Boolean> =
+ override val wallpaperInfo: StateFlow<WallpaperInfo?> =
if (!wallpaperManager.isWallpaperSupported || !deviceSupportsAodWallpaper) {
- MutableStateFlow(false).asStateFlow()
+ MutableStateFlow(null).asStateFlow()
} else {
combine(wallpaperChanged, selectedUser) { _, selectedUser ->
- doesWallpaperSupportAmbientMode(selectedUser)
+ getWallpaper(selectedUser)
}
.stateIn(
scope,
// Always be listening for wallpaper changes.
SharingStarted.Eagerly,
- initialValue =
- doesWallpaperSupportAmbientMode(userRepository.selectedUser.value),
+ initialValue = getWallpaper(userRepository.selectedUser.value),
)
}
- private fun doesWallpaperSupportAmbientMode(selectedUser: SelectedUserModel): Boolean {
- return wallpaperManager
- .getWallpaperInfoForUser(
- selectedUser.userInfo.id,
+ override val wallpaperSupportsAmbientMode: StateFlow<Boolean> =
+ wallpaperInfo
+ .map {
+ // If WallpaperInfo is null, it's ImageWallpaper which never supports ambient mode.
+ it?.supportsAmbientMode() == true
+ }
+ .stateIn(
+ scope,
+ // Always be listening for wallpaper changes.
+ SharingStarted.Eagerly,
+ initialValue = wallpaperInfo.value?.supportsAmbientMode() == true,
)
- // If WallpaperInfo is null, it's ImageWallpaper which never supports ambient mode.
- ?.supportsAmbientMode() == true
+
+ private fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? {
+ return wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index 319a02d..d506584 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -33,12 +33,15 @@
import android.app.admin.IKeyguardClient;
import android.content.ComponentName;
import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.ViewUtils;
+import android.view.Display;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceView;
import android.view.View;
@@ -50,7 +53,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -60,7 +62,6 @@
@RunWithLooper
@RunWith(AndroidTestingRunner.class)
@SmallTest
-@Ignore("b/286245842")
public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
private static final int TARGET_USER_ID = KeyguardUpdateMonitor.getCurrentUser();
@@ -79,7 +80,7 @@
private KeyguardSecurityCallback mKeyguardCallback;
@Mock
private KeyguardUpdateMonitor mUpdateMonitor;
- @Mock
+
private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
@Before
@@ -99,6 +100,11 @@
when(mKeyguardClient.queryLocalInterface(anyString())).thenReturn(mKeyguardClient);
when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient);
+ Display display = mContext.getSystemService(DisplayManager.class).getDisplay(
+ Display.DEFAULT_DISPLAY);
+ mSurfacePackage = (new SurfaceControlViewHost(mContext, display,
+ new Binder())).getSurfacePackage();
+
mTestController = new AdminSecondaryLockScreenController.Factory(
mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler)
.create(mKeyguardCallback);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 5d75428..cb18229 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -76,7 +76,7 @@
private lateinit var mKeyguardMessageAreaController:
KeyguardMessageAreaController<BouncerKeyguardMessageArea>
- @Mock private lateinit var mPostureController: DevicePostureController
+ @Mock private lateinit var mPostureController: DevicePostureController
private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
private lateinit var fakeFeatureFlags: FakeFeatureFlags
@@ -119,7 +119,7 @@
mKeyguardPatternViewController.onViewAttached()
- assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline())
+ assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
}
@Test
@@ -131,15 +131,20 @@
mKeyguardPatternViewController.onViewAttached()
// Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
- assertThat(getPatternTopGuideline()).isEqualTo(getExpectedTopGuideline())
+ assertThat(getPatternTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
// Simulate posture change to state DEVICE_POSTURE_OPENED with callback
verify(mPostureController).addCallback(postureCallbackCaptor.capture())
val postureCallback: DevicePostureController.Callback = postureCallbackCaptor.value
postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
- // Verify view is now in posture state DEVICE_POSTURE_OPENED
- assertThat(getPatternTopGuideline()).isNotEqualTo(getExpectedTopGuideline())
+ // Simulate posture change to same state with callback
+ assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+
+ postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ // Verify view is still in posture state DEVICE_POSTURE_OPENED
+ assertThat(getPatternTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
}
private fun getPatternTopGuideline(): Float {
@@ -150,7 +155,7 @@
return cs.getConstraint(R.id.pattern_top_guideline).layout.guidePercent
}
- private fun getExpectedTopGuideline(): Float {
+ private fun getHalfOpenedBouncerHeightRatio(): Float {
return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index d256ee1..4dc7652 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -19,6 +19,8 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
@@ -32,6 +34,8 @@
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,7 +55,10 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPinViewControllerTest : SysuiTestCase() {
- @Mock private lateinit var keyguardPinView: KeyguardPINView
+
+ private lateinit var objectKeyguardPINView: KeyguardPINView
+
+ @Mock private lateinit var mockKeyguardPinView: KeyguardPINView
@Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea
@@ -83,64 +90,73 @@
@Mock lateinit var deleteButton: NumPadButton
@Mock lateinit var enterButton: View
- private lateinit var pinViewController: KeyguardPinViewController
-
@Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback>
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- Mockito.`when`(keyguardPinView.requireViewById<View>(R.id.bouncer_message_area))
+ Mockito.`when`(mockKeyguardPinView.requireViewById<View>(R.id.bouncer_message_area))
.thenReturn(keyguardMessageArea)
Mockito.`when`(
keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java))
)
.thenReturn(keyguardMessageAreaController)
- `when`(keyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry)
- `when`(keyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry))
+ `when`(mockKeyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry)
+ `when`(mockKeyguardPinView.findViewById<PasswordTextView>(R.id.pinEntry))
.thenReturn(passwordTextView)
- `when`(keyguardPinView.resources).thenReturn(context.resources)
- `when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
+ `when`(mockKeyguardPinView.resources).thenReturn(context.resources)
+ `when`(mockKeyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
.thenReturn(deleteButton)
- `when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
+ `when`(mockKeyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton)
// For posture tests:
- `when`(keyguardPinView.buttons).thenReturn(arrayOf())
+ `when`(mockKeyguardPinView.buttons).thenReturn(arrayOf())
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
- pinViewController =
- KeyguardPinViewController(
- keyguardPinView,
- keyguardUpdateMonitor,
- securityMode,
- lockPatternUtils,
- mKeyguardSecurityCallback,
- keyguardMessageAreaControllerFactory,
- mLatencyTracker,
- liftToActivateListener,
- mEmergencyButtonController,
- falsingCollector,
- postureController,
- featureFlags
- )
+ objectKeyguardPINView =
+ View.inflate(mContext, R.layout.keyguard_pin_view, null)
+ .findViewById(R.id.keyguard_pin_view) as KeyguardPINView
+ }
+
+ private fun constructPinViewController(
+ mKeyguardPinView: KeyguardPINView
+ ): KeyguardPinViewController {
+ return KeyguardPinViewController(
+ mKeyguardPinView,
+ keyguardUpdateMonitor,
+ securityMode,
+ lockPatternUtils,
+ mKeyguardSecurityCallback,
+ keyguardMessageAreaControllerFactory,
+ mLatencyTracker,
+ liftToActivateListener,
+ mEmergencyButtonController,
+ falsingCollector,
+ postureController,
+ featureFlags
+ )
}
@Test
- fun onViewAttached_deviceHalfFolded_propagatedToPinView() {
- `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+ fun onViewAttached_deviceHalfFolded_propagatedToPatternView() {
+ val pinViewController = constructPinViewController(objectKeyguardPINView)
+ overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+ whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
pinViewController.onViewAttached()
- verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED)
+ assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
}
@Test
- fun onDevicePostureChanged_deviceHalfFolded_propagatedToPinView() {
- `when`(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+ fun onDevicePostureChanged_deviceOpened_propagatedToPatternView() {
+ val pinViewController = constructPinViewController(objectKeyguardPINView)
+ overrideResource(R.dimen.half_opened_bouncer_height_ratio, 0.5f)
+
+ whenever(postureController.devicePosture).thenReturn(DEVICE_POSTURE_HALF_OPENED)
+ pinViewController.onViewAttached()
// Verify view begins in posture state DEVICE_POSTURE_HALF_OPENED
- pinViewController.onViewAttached()
-
- verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED)
+ assertThat(getPinTopGuideline()).isEqualTo(getHalfOpenedBouncerHeightRatio())
// Simulate posture change to state DEVICE_POSTURE_OPENED with callback
verify(postureController).addCallback(postureCallbackCaptor.capture())
@@ -148,31 +164,57 @@
postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
// Verify view is now in posture state DEVICE_POSTURE_OPENED
- verify(keyguardPinView).onDevicePostureChanged(DEVICE_POSTURE_OPENED)
+ assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+
+ // Simulate posture change to same state with callback
+ postureCallback.onPostureChanged(DEVICE_POSTURE_OPENED)
+
+ // Verify view is still in posture state DEVICE_POSTURE_OPENED
+ assertThat(getPinTopGuideline()).isNotEqualTo(getHalfOpenedBouncerHeightRatio())
+ }
+
+ private fun getPinTopGuideline(): Float {
+ val cs = ConstraintSet()
+ val container = objectKeyguardPINView.findViewById(R.id.pin_container) as ConstraintLayout
+ cs.clone(container)
+ return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent
+ }
+
+ private fun getHalfOpenedBouncerHeightRatio(): Float {
+ return mContext.resources.getFloat(R.dimen.half_opened_bouncer_height_ratio)
}
@Test
fun startAppearAnimation() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
+
pinViewController.startAppearAnimation()
+
verify(keyguardMessageAreaController)
.setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
}
@Test
fun startAppearAnimation_withExistingMessage() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+
pinViewController.startAppearAnimation()
+
verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
}
@Test
fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
`when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
+ `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
`when`(passwordTextView.text).thenReturn("")
pinViewController.startAppearAnimation()
+
verify(deleteButton).visibility = View.INVISIBLE
verify(enterButton).visibility = View.INVISIBLE
verify(passwordTextView).setUsePinShapes(true)
@@ -181,12 +223,15 @@
@Test
fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
`when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
+ `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
`when`(passwordTextView.text).thenReturn("")
pinViewController.startAppearAnimation()
+
verify(deleteButton).visibility = View.VISIBLE
verify(enterButton).visibility = View.VISIBLE
verify(passwordTextView).setUsePinShapes(true)
@@ -195,7 +240,10 @@
@Test
fun handleLockout_readsNumberOfErrorAttempts() {
+ val pinViewController = constructPinViewController(mockKeyguardPinView)
+
pinViewController.handleAttemptLockout(0)
+
verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index da9ceb4..212dad7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -8,6 +8,7 @@
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.children
import junit.framework.Assert.assertEquals
@@ -19,7 +20,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import com.android.app.animation.Interpolators
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -178,7 +178,7 @@
}
@Test
- fun animatesRootAndChildren() {
+ fun animatesRootAndChildren_withoutExcludedViews() {
setUpRootWithChildren()
val success = ViewHierarchyAnimator.animate(rootView)
@@ -208,6 +208,40 @@
}
@Test
+ fun animatesRootAndChildren_withExcludedViews() {
+ setUpRootWithChildren()
+
+ val success = ViewHierarchyAnimator.animate(
+ rootView,
+ excludedViews = setOf(rootView.getChildAt(0))
+ )
+ // Change all bounds.
+ rootView.measure(
+ View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+ )
+ rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
+
+ assertTrue(success)
+ assertNotNull(rootView.getTag(R.id.tag_animator))
+ assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator))
+ assertNotNull(rootView.getChildAt(1).getTag(R.id.tag_animator))
+ // The initial values for the affected views should be those of the previous layout, while
+ // the excluded view should be at the final values from the beginning.
+ checkBounds(rootView, l = 0, t = 0, r = 200, b = 100)
+ checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 90, b = 100)
+ checkBounds(rootView.getChildAt(1), l = 100, t = 0, r = 200, b = 100)
+ endAnimation(rootView)
+ assertNull(rootView.getTag(R.id.tag_animator))
+ assertNull(rootView.getChildAt(0).getTag(R.id.tag_animator))
+ assertNull(rootView.getChildAt(1).getTag(R.id.tag_animator))
+ // The end values should be those of the latest layout.
+ checkBounds(rootView, l = 10, t = 20, r = 200, b = 120)
+ checkBounds(rootView.getChildAt(0), l = 0, t = 0, r = 90, b = 100)
+ checkBounds(rootView.getChildAt(1), l = 90, t = 0, r = 180, b = 100)
+ }
+
+ @Test
fun animatesInvisibleViews() {
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
rootView.visibility = View.INVISIBLE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index c223c5a..a6ad4b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -103,19 +103,6 @@
}
@Test
- fun toggleBypassEnabled() =
- testScope.runTest {
- val isBypassEnabled by collectLastValue(underTest.isBypassEnabled)
- assertThat(isBypassEnabled).isFalse()
-
- underTest.toggleBypassEnabled()
- assertThat(isBypassEnabled).isTrue()
-
- underTest.toggleBypassEnabled()
- assertThat(isBypassEnabled).isFalse()
- }
-
- @Test
fun isAuthenticationRequired_lockedAndSecured_true() =
testScope.runTest {
utils.authenticationRepository.setUnlocked(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index d022653..ecc776b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -44,25 +44,27 @@
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
-import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
import android.view.WindowMetrics
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.FakeExecutor
@@ -99,7 +101,7 @@
@SmallTest
@RoboPilotTest
@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
class SideFpsControllerTest : SysuiTestCase() {
@JvmField @Rule var rule = MockitoJUnit.rule()
@@ -118,6 +120,8 @@
private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
private lateinit var displayStateInteractor: DisplayStateInteractor
+ private lateinit var sideFpsOverlayViewModel: SideFpsOverlayViewModel
+ private val fingerprintRepository = FakeFingerprintPropertyRepository()
private val executor = FakeExecutor(FakeSystemClock())
private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
@@ -149,8 +153,8 @@
mock(KeyguardStateController::class.java),
keyguardBouncerRepository,
FakeBiometricSettingsRepository(),
- FakeDeviceEntryFingerprintAuthRepository(),
FakeSystemClock(),
+ mock(KeyguardUpdateMonitor::class.java),
)
displayStateInteractor =
DisplayStateInteractorImpl(
@@ -159,6 +163,15 @@
executor,
rearDisplayStateRepository
)
+ sideFpsOverlayViewModel =
+ SideFpsOverlayViewModel(context, SideFpsOverlayInteractorImpl(fingerprintRepository))
+
+ fingerprintRepository.setProperties(
+ sensorId = 1,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.REAR,
+ sensorLocations = mapOf("" to SensorLocationInternal("", 2500, 0, 0))
+ )
context.addMockSystemService(DisplayManager::class.java, displayManager)
context.addMockSystemService(WindowManager::class.java, windowManager)
@@ -265,6 +278,7 @@
executor,
handler,
alternateBouncerInteractor,
+ { sideFpsOverlayViewModel },
TestCoroutineScope(),
dumpManager
)
@@ -683,106 +697,6 @@
verify(windowManager).removeView(any())
}
- /**
- * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
- * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
- * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement
- * in other rotations have been omitted.
- */
- @Test
- fun verifiesIndicatorPlacementForXAlignedSensor_0() =
- testWithDisplay(
- deviceConfig = DeviceConfig.X_ALIGNED,
- isReverseDefaultRotation = false,
- { rotation = Surface.ROTATION_0 }
- ) {
- sideFpsController.overlayOffsets = sensorLocation
-
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
- assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
- assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
- }
-
- /**
- * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
- * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct
- * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works
- * correctly, tests for indicator placement in other rotations have been omitted.
- */
- @Test
- fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() =
- testWithDisplay(
- deviceConfig = DeviceConfig.X_ALIGNED,
- isReverseDefaultRotation = true,
- { rotation = Surface.ROTATION_270 }
- ) {
- sideFpsController.overlayOffsets = sensorLocation
-
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
- assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
- assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
- }
-
- /**
- * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,
- * and uses RotateUtils.rotateBounds to map to the correct indicator location given the device
- * rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator placement
- * in other rotations have been omitted.
- */
- @Test
- fun verifiesIndicatorPlacementForYAlignedSensor_0() =
- testWithDisplay(
- deviceConfig = DeviceConfig.Y_ALIGNED,
- isReverseDefaultRotation = false,
- { rotation = Surface.ROTATION_0 }
- ) {
- sideFpsController.overlayOffsets = sensorLocation
-
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
- assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
- assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
- }
-
- /**
- * {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
- * in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the correct
- * indicator location given the device rotation. Assuming RotationUtils.rotateBounds works
- * correctly, tests for indicator placement in other rotations have been omitted.
- */
- @Test
- fun verifiesIndicatorPlacementForYAlignedSensor_InReverseDefaultRotation_270() =
- testWithDisplay(
- deviceConfig = DeviceConfig.Y_ALIGNED,
- isReverseDefaultRotation = true,
- { rotation = Surface.ROTATION_270 }
- ) {
- sideFpsController.overlayOffsets = sensorLocation
-
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
-
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
- assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
- assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
- }
-
@Test
fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay {
// By default all those tests assume the side fps sensor is available.
@@ -795,51 +709,6 @@
assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
}
-
- @Test
- fun testLayoutParams_isKeyguardDialogType() =
- testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
- sideFpsController.overlayOffsets = sensorLocation
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
- val lpType = overlayViewParamsCaptor.value.type
-
- assertThat((lpType and TYPE_KEYGUARD_DIALOG) != 0).isTrue()
- }
-
- @Test
- fun testLayoutParams_hasNoMoveAnimationWindowFlag() =
- testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
- sideFpsController.overlayOffsets = sensorLocation
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
- val lpFlags = overlayViewParamsCaptor.value.privateFlags
-
- assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
- }
-
- @Test
- fun testLayoutParams_hasTrustedOverlayWindowFlag() =
- testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED) {
- sideFpsController.overlayOffsets = sensorLocation
- sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
- val lpFlags = overlayViewParamsCaptor.value.privateFlags
-
- assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
- }
}
private fun insetsForSmallNavbar() = insetsWithBottom(60)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 9df06dc..8dfeb3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -34,7 +34,6 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -106,8 +105,8 @@
mock(KeyguardStateController::class.java),
keyguardBouncerRepository,
mock(BiometricSettingsRepository::class.java),
- mock(DeviceEntryFingerprintAuthRepository::class.java),
mock(SystemClock::class.java),
+ mKeyguardUpdateMonitor,
)
return createUdfpsKeyguardViewController(
/* useModernBouncer */ true, /* useExpandedOverlay */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
index 239e317..ea25615 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
@@ -73,10 +73,15 @@
@Test
fun initializeProperties() =
testScope.runTest {
- val isInitialized = collectLastValue(repository.isInitialized)
+ val sensorId by collectLastValue(repository.sensorId)
+ val strength by collectLastValue(repository.strength)
+ val sensorType by collectLastValue(repository.sensorType)
+ val sensorLocations by collectLastValue(repository.sensorLocations)
- assertDefaultProperties()
- assertThat(isInitialized()).isFalse()
+ // Assert default properties.
+ assertThat(sensorId).isEqualTo(-1)
+ assertThat(strength).isEqualTo(SensorStrength.CONVENIENCE)
+ assertThat(sensorType).isEqualTo(FingerprintSensorType.UNKNOWN)
val fingerprintProps =
listOf(
@@ -115,31 +120,24 @@
fingerprintAuthenticatorsCaptor.value.onAllAuthenticatorsRegistered(fingerprintProps)
- assertThat(repository.sensorId.value).isEqualTo(1)
- assertThat(repository.strength.value).isEqualTo(SensorStrength.STRONG)
- assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.REAR)
+ assertThat(sensorId).isEqualTo(1)
+ assertThat(strength).isEqualTo(SensorStrength.STRONG)
+ assertThat(sensorType).isEqualTo(FingerprintSensorType.REAR)
- assertThat(repository.sensorLocations.value.size).isEqualTo(2)
- assertThat(repository.sensorLocations.value).containsKey("display_id_1")
- with(repository.sensorLocations.value["display_id_1"]!!) {
+ assertThat(sensorLocations?.size).isEqualTo(2)
+ assertThat(sensorLocations).containsKey("display_id_1")
+ with(sensorLocations?.get("display_id_1")!!) {
assertThat(displayId).isEqualTo("display_id_1")
assertThat(sensorLocationX).isEqualTo(100)
assertThat(sensorLocationY).isEqualTo(300)
assertThat(sensorRadius).isEqualTo(20)
}
- assertThat(repository.sensorLocations.value).containsKey("")
- with(repository.sensorLocations.value[""]!!) {
+ assertThat(sensorLocations).containsKey("")
+ with(sensorLocations?.get("")!!) {
assertThat(displayId).isEqualTo("")
assertThat(sensorLocationX).isEqualTo(540)
assertThat(sensorLocationY).isEqualTo(1636)
assertThat(sensorRadius).isEqualTo(130)
}
- assertThat(isInitialized()).isTrue()
}
-
- private fun assertDefaultProperties() {
- assertThat(repository.sensorId.value).isEqualTo(-1)
- assertThat(repository.strength.value).isEqualTo(SensorStrength.CONVENIENCE)
- assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.UNKNOWN)
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
index fd96cf4..896f9b11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
@@ -22,6 +22,7 @@
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -51,8 +52,9 @@
}
@Test
- fun testGetOverlayOffsets() =
+ fun testGetOverlayoffsets() =
testScope.runTest {
+ // Arrange.
fingerprintRepository.setProperties(
sensorId = 1,
strength = SensorStrength.STRONG,
@@ -76,16 +78,33 @@
)
)
- var offsets = interactor.getOverlayOffsets("display_id_1")
- assertThat(offsets.displayId).isEqualTo("display_id_1")
- assertThat(offsets.sensorLocationX).isEqualTo(100)
- assertThat(offsets.sensorLocationY).isEqualTo(300)
- assertThat(offsets.sensorRadius).isEqualTo(20)
+ // Act.
+ val offsets by collectLastValue(interactor.overlayOffsets)
+ val displayId by collectLastValue(interactor.displayId)
- offsets = interactor.getOverlayOffsets("invalid_display_id")
- assertThat(offsets.displayId).isEqualTo("")
- assertThat(offsets.sensorLocationX).isEqualTo(540)
- assertThat(offsets.sensorLocationY).isEqualTo(1636)
- assertThat(offsets.sensorRadius).isEqualTo(130)
+ // Assert offsets of empty displayId.
+ assertThat(displayId).isEqualTo("")
+ assertThat(offsets?.displayId).isEqualTo("")
+ assertThat(offsets?.sensorLocationX).isEqualTo(540)
+ assertThat(offsets?.sensorLocationY).isEqualTo(1636)
+ assertThat(offsets?.sensorRadius).isEqualTo(130)
+
+ // Offsets should be updated correctly.
+ interactor.changeDisplay("display_id_1")
+ assertThat(displayId).isEqualTo("display_id_1")
+ assertThat(offsets?.displayId).isEqualTo("display_id_1")
+ assertThat(offsets?.sensorLocationX).isEqualTo(100)
+ assertThat(offsets?.sensorLocationY).isEqualTo(300)
+ assertThat(offsets?.sensorRadius).isEqualTo(20)
+
+ // Should return default offset when the displayId is invalid.
+ interactor.changeDisplay("invalid_display_id")
+ assertThat(displayId).isEqualTo("invalid_display_id")
+ assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId)
+ assertThat(offsets?.sensorLocationX)
+ .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX)
+ assertThat(offsets?.sensorLocationY)
+ .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY)
+ assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
new file mode 100644
index 0000000..a859321
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.display.DisplayManagerGlobal
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import android.view.Surface
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.SysuiTestableContext
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+
+private const val DISPLAY_ID = 2
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SideFpsOverlayViewModelTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+ private var testScope: TestScope = TestScope(StandardTestDispatcher())
+
+ private val fingerprintRepository = FakeFingerprintPropertyRepository()
+ private lateinit var interactor: SideFpsOverlayInteractor
+ private lateinit var viewModel: SideFpsOverlayViewModel
+
+ enum class DeviceConfig {
+ X_ALIGNED,
+ Y_ALIGNED,
+ }
+
+ private lateinit var deviceConfig: DeviceConfig
+ private lateinit var indicatorBounds: Rect
+ private lateinit var displayBounds: Rect
+ private lateinit var sensorLocation: SensorLocationInternal
+ private var displayWidth: Int = 0
+ private var displayHeight: Int = 0
+ private var boundsWidth: Int = 0
+ private var boundsHeight: Int = 0
+
+ @Before
+ fun setup() {
+ interactor = SideFpsOverlayInteractorImpl(fingerprintRepository)
+
+ fingerprintRepository.setProperties(
+ sensorId = 1,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.REAR,
+ sensorLocations =
+ mapOf(
+ "" to
+ SensorLocationInternal(
+ "" /* displayId */,
+ 540 /* sensorLocationX */,
+ 1636 /* sensorLocationY */,
+ 130 /* sensorRadius */
+ ),
+ "display_id_1" to
+ SensorLocationInternal(
+ "display_id_1" /* displayId */,
+ 100 /* sensorLocationX */,
+ 300 /* sensorLocationY */,
+ 20 /* sensorRadius */
+ )
+ )
+ )
+ }
+
+ @Test
+ fun testOverlayOffsets() =
+ testScope.runTest {
+ viewModel = SideFpsOverlayViewModel(mContext, interactor)
+
+ val interactorOffsets by collectLastValue(interactor.overlayOffsets)
+ val viewModelOffsets by collectLastValue(viewModel.overlayOffsets)
+
+ assertThat(viewModelOffsets).isEqualTo(interactorOffsets)
+ }
+
+ private fun testWithDisplay(
+ deviceConfig: DeviceConfig = DeviceConfig.X_ALIGNED,
+ isReverseDefaultRotation: Boolean = false,
+ initInfo: DisplayInfo.() -> Unit = {},
+ block: () -> Unit
+ ) {
+ this.deviceConfig = deviceConfig
+
+ when (deviceConfig) {
+ DeviceConfig.X_ALIGNED -> {
+ displayWidth = 3000
+ displayHeight = 1500
+ sensorLocation = SensorLocationInternal("", 2500, 0, 0)
+ boundsWidth = 200
+ boundsHeight = 100
+ }
+ DeviceConfig.Y_ALIGNED -> {
+ displayWidth = 2500
+ displayHeight = 2000
+ sensorLocation = SensorLocationInternal("", 0, 300, 0)
+ boundsWidth = 100
+ boundsHeight = 200
+ }
+ }
+
+ indicatorBounds = Rect(0, 0, boundsWidth, boundsHeight)
+ displayBounds = Rect(0, 0, displayWidth, displayHeight)
+
+ val displayInfo = DisplayInfo()
+ displayInfo.initInfo()
+
+ val dmGlobal = Mockito.mock(DisplayManagerGlobal::class.java)
+ val display =
+ Display(
+ dmGlobal,
+ DISPLAY_ID,
+ displayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+ )
+
+ whenever(dmGlobal.getDisplayInfo(ArgumentMatchers.eq(DISPLAY_ID))).thenReturn(displayInfo)
+
+ val sideFpsOverlayViewModelContext =
+ context.createDisplayContext(display) as SysuiTestableContext
+ sideFpsOverlayViewModelContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_reverseDefaultRotation,
+ isReverseDefaultRotation
+ )
+ viewModel = SideFpsOverlayViewModel(sideFpsOverlayViewModelContext, interactor)
+
+ block()
+ }
+
+ /**
+ * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+ * ROTATION_0, and uses RotateUtils.rotateBounds to map to the correct indicator location given
+ * the device rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator
+ * placement in other rotations have been omitted.
+ */
+ @Test
+ fun verifiesIndicatorPlacementForXAlignedSensor_0() =
+ testScope.runTest {
+ testWithDisplay(
+ deviceConfig = DeviceConfig.X_ALIGNED,
+ isReverseDefaultRotation = false,
+ { rotation = Surface.ROTATION_0 }
+ ) {
+ viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+ val displayInfo: DisplayInfo = DisplayInfo()
+ context.display!!.getDisplayInfo(displayInfo)
+ assertThat(displayInfo.rotation).isEqualTo(Surface.ROTATION_0)
+
+ assertThat(viewModel.sensorBounds.value).isNotNull()
+ assertThat(viewModel.sensorBounds.value.left)
+ .isEqualTo(sensorLocation.sensorLocationX)
+ assertThat(viewModel.sensorBounds.value.top).isEqualTo(0)
+ }
+ }
+
+ /**
+ * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+ * ROTATION_270 in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the
+ * correct indicator location given the device rotation. Assuming RotationUtils.rotateBounds
+ * works correctly, tests for indicator placement in other rotations have been omitted.
+ */
+ @Test
+ fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() =
+ testScope.runTest {
+ testWithDisplay(
+ deviceConfig = DeviceConfig.X_ALIGNED,
+ isReverseDefaultRotation = true,
+ { rotation = Surface.ROTATION_270 }
+ ) {
+ viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+ assertThat(viewModel.sensorBounds.value).isNotNull()
+ assertThat(viewModel.sensorBounds.value.left)
+ .isEqualTo(sensorLocation.sensorLocationX)
+ assertThat(viewModel.sensorBounds.value.top).isEqualTo(0)
+ }
+ }
+
+ /**
+ * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+ * ROTATION_0, and uses RotateUtils.rotateBounds to map to the correct indicator location given
+ * the device rotation. Assuming RotationUtils.rotateBounds works correctly, tests for indicator
+ * placement in other rotations have been omitted.
+ */
+ @Test
+ fun verifiesIndicatorPlacementForYAlignedSensor_0() =
+ testScope.runTest {
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED,
+ isReverseDefaultRotation = false,
+ { rotation = Surface.ROTATION_0 }
+ ) {
+ viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+ assertThat(viewModel.sensorBounds.value).isNotNull()
+ assertThat(viewModel.sensorBounds.value.left).isEqualTo(displayWidth - boundsWidth)
+ assertThat(viewModel.sensorBounds.value.top)
+ .isEqualTo(sensorLocation.sensorLocationY)
+ }
+ }
+
+ /**
+ * {@link SideFpsOverlayViewModel#updateSensorBounds} calculates indicator placement for
+ * ROTATION_270 in reverse default rotation. It then uses RotateUtils.rotateBounds to map to the
+ * correct indicator location given the device rotation. Assuming RotationUtils.rotateBounds
+ * works correctly, tests for indicator placement in other rotations have been omitted.
+ */
+ @Test
+ fun verifiesIndicatorPlacementForYAlignedSensor_InReverseDefaultRotation_270() =
+ testScope.runTest {
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED,
+ isReverseDefaultRotation = true,
+ { rotation = Surface.ROTATION_270 }
+ ) {
+ viewModel.updateSensorBounds(indicatorBounds, displayBounds, sensorLocation)
+
+ assertThat(viewModel.sensorBounds.value).isNotNull()
+ assertThat(viewModel.sensorBounds.value.left).isEqualTo(displayWidth - boundsWidth)
+ assertThat(viewModel.sensorBounds.value.top)
+ .isEqualTo(sensorLocation.sensorLocationY)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index 37b9ca4..186df02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -18,6 +18,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
@@ -54,6 +55,7 @@
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var systemClock: SystemClock
@Mock private lateinit var bouncerLogger: TableLogBuffer
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Before
fun setup() {
@@ -72,8 +74,8 @@
keyguardStateController,
bouncerRepository,
biometricSettingsRepository,
- deviceEntryFingerprintAuthRepository,
systemClock,
+ keyguardUpdateMonitor,
)
}
@@ -118,7 +120,7 @@
@Test
fun canShowAlternateBouncerForFingerprint_fingerprintLockedOut() {
givenCanShowAlternateBouncer()
- deviceEntryFingerprintAuthRepository.setLockedOut(true)
+ whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
assertFalse(underTest.canShowAlternateBouncerForFingerprint())
}
@@ -168,7 +170,7 @@
biometricSettingsRepository.setFingerprintEnrolled(true)
biometricSettingsRepository.setStrongBiometricAllowed(true)
biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
- deviceEntryFingerprintAuthRepository.setLockedOut(false)
+ whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
whenever(keyguardStateController.isUnlocked).thenReturn(false)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
index 461ec65..40f0ed3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
@@ -28,10 +28,10 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.DreamLogger;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.log.core.FakeLogBuffer;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -57,8 +57,6 @@
private FakeFeatureFlags mFeatureFlags;
@Mock
private Observer mObserver;
- @Mock
- private DreamLogger mLogger;
@Before
public void setUp() {
@@ -70,7 +68,7 @@
mExecutor,
/* overlayEnabled= */ true,
mFeatureFlags,
- mLogger);
+ FakeLogBuffer.Factory.Companion.create());
mLiveData = new ComplicationCollectionLiveData(mStateController);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index a00e545..57307fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -9,6 +9,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.complication.ComplicationHostViewController
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.mockito.argumentCaptor
@@ -47,7 +48,7 @@
@Mock private lateinit var stateController: DreamOverlayStateController
@Mock private lateinit var configController: ConfigurationController
@Mock private lateinit var transitionViewModel: DreamingToLockscreenTransitionViewModel
- @Mock private lateinit var logger: DreamLogger
+ private val logBuffer = FakeLogBuffer.Factory.create()
private lateinit var controller: DreamOverlayAnimationsController
@Before
@@ -66,7 +67,7 @@
DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
DREAM_IN_TRANSLATION_Y_DISTANCE,
DREAM_IN_TRANSLATION_Y_DURATION,
- logger
+ logBuffer
)
val mockView: View = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 2c1ebe4..44a78ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -34,6 +34,8 @@
import com.android.systemui.complication.Complication;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.FakeLogBuffer;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -58,8 +60,7 @@
@Mock
private FeatureFlags mFeatureFlags;
- @Mock
- private DreamLogger mLogger;
+ private final LogBuffer mLogBuffer = FakeLogBuffer.Factory.Companion.create();
final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@@ -408,6 +409,11 @@
}
private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) {
- return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags, mLogger);
+ return new DreamOverlayStateController(
+ mExecutor,
+ overlayEnabled,
+ mFeatureFlags,
+ mLogBuffer
+ );
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index 5dc0e55..4e74f451 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -48,6 +48,8 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.core.FakeLogBuffer;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.NextAlarmController;
@@ -113,8 +115,8 @@
DreamOverlayStateController mDreamOverlayStateController;
@Mock
UserTracker mUserTracker;
- @Mock
- DreamLogger mLogger;
+
+ LogBuffer mLogBuffer = FakeLogBuffer.Factory.Companion.create();
@Captor
private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
@@ -149,7 +151,7 @@
mDreamOverlayStatusBarItemsProvider,
mDreamOverlayStateController,
mUserTracker,
- mLogger);
+ mLogBuffer);
}
@Test
@@ -293,7 +295,7 @@
mDreamOverlayStatusBarItemsProvider,
mDreamOverlayStateController,
mUserTracker,
- mLogger);
+ mLogBuffer);
controller.onViewAttached();
verify(mView, never()).showIcon(
eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
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 ddcbe79..00f67a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -78,6 +78,7 @@
import com.android.keyguard.KeyguardSecurityView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TestScopeProvider;
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.DejankUtils;
import com.android.systemui.SysuiTestCase;
@@ -115,9 +116,11 @@
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import org.junit.After;
@@ -132,6 +135,7 @@
import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.test.TestScope;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -139,6 +143,9 @@
public class KeyguardViewMediatorTest extends SysuiTestCase {
private KeyguardViewMediator mViewMediator;
+ private final TestScope mTestScope = TestScopeProvider.getTestScope();
+ private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
+
private @Mock UserTracker mUserTracker;
private @Mock DevicePolicyManager mDevicePolicyManager;
private @Mock LockPatternUtils mLockPatternUtils;
@@ -191,6 +198,7 @@
private @Mock SecureSettings mSecureSettings;
private @Mock AlarmManager mAlarmManager;
private FakeSystemClock mSystemClock;
+ private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
/** Most recent value passed to {@link KeyguardStateController#notifyKeyguardGoingAway}. */
private boolean mKeyguardGoingAway = false;
@@ -928,6 +936,8 @@
mKeyguardTransitions,
mInteractionJankMonitor,
mDreamOverlayStateController,
+ mJavaAdapter,
+ mWallpaperRepository,
() -> mShadeController,
() -> mNotificationShadeWindowController,
() -> mActivityLaunchAnimator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index f9070b3..c6a2fa5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -162,11 +162,11 @@
@Test
fun convenienceBiometricAllowedChange() =
testScope.runTest {
+ overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, false)
createBiometricSettingsRepository()
val convenienceBiometricAllowed =
collectLastValue(underTest.isNonStrongBiometricAllowed)
runCurrent()
-
onNonStrongAuthChanged(true, PRIMARY_USER_ID)
assertThat(convenienceBiometricAllowed()).isTrue()
@@ -175,6 +175,45 @@
onNonStrongAuthChanged(false, PRIMARY_USER_ID)
assertThat(convenienceBiometricAllowed()).isFalse()
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_strongAuthRequiredOnBoot
+ )
+ }
+
+ @Test
+ fun whenStrongAuthRequiredAfterBoot_nonStrongBiometricNotAllowed() =
+ testScope.runTest {
+ overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, true)
+ createBiometricSettingsRepository()
+
+ val convenienceBiometricAllowed =
+ collectLastValue(underTest.isNonStrongBiometricAllowed)
+ runCurrent()
+ onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+
+ assertThat(convenienceBiometricAllowed()).isFalse()
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_strongAuthRequiredOnBoot
+ )
+ }
+
+ @Test
+ fun whenStrongBiometricAuthIsNotAllowed_nonStrongBiometrics_alsoNotAllowed() =
+ testScope.runTest {
+ overrideResource(com.android.internal.R.bool.config_strongAuthRequiredOnBoot, false)
+ createBiometricSettingsRepository()
+
+ val convenienceBiometricAllowed =
+ collectLastValue(underTest.isNonStrongBiometricAllowed)
+ runCurrent()
+ onNonStrongAuthChanged(true, PRIMARY_USER_ID)
+ assertThat(convenienceBiometricAllowed()).isTrue()
+
+ onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, PRIMARY_USER_ID)
+ assertThat(convenienceBiometricAllowed()).isFalse()
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_strongAuthRequiredOnBoot
+ )
}
private fun onStrongAuthChanged(flags: Int, userId: Int) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 020c0b2..47c662c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -36,6 +36,7 @@
import com.android.keyguard.FaceAuthUiEvent
import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
@@ -50,12 +51,12 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -113,6 +114,7 @@
@Mock private lateinit var sessionTracker: SessionTracker
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Captor
private lateinit var authenticationCallback: ArgumentCaptor<FaceManager.AuthenticationCallback>
@@ -131,8 +133,8 @@
private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
private lateinit var testScope: TestScope
private lateinit var fakeUserRepository: FakeUserRepository
- private lateinit var authStatus: FlowValue<AuthenticationStatus?>
- private lateinit var detectStatus: FlowValue<DetectionStatus?>
+ private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?>
+ private lateinit var detectStatus: FlowValue<FaceDetectionStatus?>
private lateinit var authRunning: FlowValue<Boolean?>
private lateinit var bypassEnabled: FlowValue<Boolean?>
private lateinit var lockedOut: FlowValue<Boolean?>
@@ -175,10 +177,10 @@
AlternateBouncerInteractor(
bouncerRepository = bouncerRepository,
biometricSettingsRepository = biometricSettingsRepository,
- deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
systemClock = mock(SystemClock::class.java),
keyguardStateController = FakeKeyguardStateController(),
statusBarStateController = mock(StatusBarStateController::class.java),
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
)
bypassStateChangedListener =
@@ -262,7 +264,7 @@
val successResult = successResult()
authenticationCallback.value.onAuthenticationSucceeded(successResult)
- assertThat(authStatus()).isEqualTo(SuccessAuthenticationStatus(successResult))
+ assertThat(authStatus()).isEqualTo(SuccessFaceAuthenticationStatus(successResult))
assertThat(authenticated()).isTrue()
assertThat(authRunning()).isFalse()
assertThat(canFaceAuthRun()).isFalse()
@@ -383,7 +385,7 @@
detectionCallback.value.onFaceDetected(1, 1, true)
- assertThat(detectStatus()).isEqualTo(DetectionStatus(1, 1, true))
+ assertThat(detectStatus()).isEqualTo(FaceDetectionStatus(1, 1, true))
}
@Test
@@ -423,7 +425,7 @@
FACE_ERROR_CANCELED,
"First auth attempt cancellation completed"
)
- val value = authStatus() as ErrorAuthenticationStatus
+ val value = authStatus() as ErrorFaceAuthenticationStatus
assertThat(value.msgId).isEqualTo(FACE_ERROR_CANCELED)
assertThat(value.msg).isEqualTo("First auth attempt cancellation completed")
@@ -465,7 +467,7 @@
authenticationCallback.value.onAuthenticationHelp(10, "Ignored help msg")
authenticationCallback.value.onAuthenticationHelp(11, "Ignored help msg")
- assertThat(authStatus()).isEqualTo(HelpAuthenticationStatus(9, "help msg"))
+ assertThat(authStatus()).isEqualTo(HelpFaceAuthenticationStatus(9, "help msg"))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 264328b..def016a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -26,7 +26,11 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,7 +53,6 @@
@RunWith(AndroidJUnit4::class)
class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- @Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var authController: AuthController
@Captor
private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -68,7 +71,6 @@
authController,
keyguardUpdateMonitor,
testScope.backgroundScope,
- dumpManager,
)
}
@@ -177,4 +179,129 @@
callback.value.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT)
assertThat(availableFpSensorType()).isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
}
+
+ @Test
+ fun onFingerprintSuccess_successAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricAuthenticated(
+ 0,
+ BiometricSourceType.FINGERPRINT,
+ true,
+ )
+
+ val status = authenticationStatus as SuccessFingerprintAuthenticationStatus
+ assertThat(status.userId).isEqualTo(0)
+ assertThat(status.isStrongBiometric).isEqualTo(true)
+ }
+
+ @Test
+ fun onFingerprintFailed_failedAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricAuthFailed(
+ BiometricSourceType.FINGERPRINT,
+ )
+
+ assertThat(authenticationStatus)
+ .isInstanceOf(FailFingerprintAuthenticationStatus::class.java)
+ }
+
+ @Test
+ fun onFingerprintError_errorAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricError(
+ 1,
+ "test_string",
+ BiometricSourceType.FINGERPRINT,
+ )
+
+ val status = authenticationStatus as ErrorFingerprintAuthenticationStatus
+ assertThat(status.msgId).isEqualTo(1)
+ assertThat(status.msg).isEqualTo("test_string")
+ }
+
+ @Test
+ fun onFingerprintHelp_helpAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricHelp(
+ 1,
+ "test_string",
+ BiometricSourceType.FINGERPRINT,
+ )
+
+ val status = authenticationStatus as HelpFingerprintAuthenticationStatus
+ assertThat(status.msgId).isEqualTo(1)
+ assertThat(status.msg).isEqualTo("test_string")
+ }
+
+ @Test
+ fun onFingerprintAcquired_acquiredAuthenticationStatus() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricAcquired(
+ BiometricSourceType.FINGERPRINT,
+ 5,
+ )
+
+ val status = authenticationStatus as AcquiredFingerprintAuthenticationStatus
+ assertThat(status.acquiredInfo).isEqualTo(5)
+ }
+
+ @Test
+ fun onFaceCallbacks_fingerprintAuthenticationStatusIsUnchanged() =
+ testScope.runTest {
+ val authenticationStatus by collectLastValue(underTest.authenticationStatus)
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricAuthenticated(
+ 0,
+ BiometricSourceType.FACE,
+ true,
+ )
+ assertThat(authenticationStatus).isNull()
+
+ updateMonitorCallback.value.onBiometricAuthFailed(
+ BiometricSourceType.FACE,
+ )
+ assertThat(authenticationStatus).isNull()
+
+ updateMonitorCallback.value.onBiometricHelp(
+ 1,
+ "test_string",
+ BiometricSourceType.FACE,
+ )
+ assertThat(authenticationStatus).isNull()
+
+ updateMonitorCallback.value.onBiometricAcquired(
+ BiometricSourceType.FACE,
+ 5,
+ )
+ assertThat(authenticationStatus).isNull()
+
+ updateMonitorCallback.value.onBiometricError(
+ 1,
+ "test_string",
+ BiometricSourceType.FACE,
+ )
+ assertThat(authenticationStatus).isNull()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index e9f0d56..ba7d349 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -31,16 +31,20 @@
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.keyguard.ScreenLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.ScreenModel
+import com.android.systemui.keyguard.shared.model.ScreenState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
@@ -70,9 +74,11 @@
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var screenLifecycle: ScreenLifecycle
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
@Mock private lateinit var dozeTransitionListener: DozeTransitionListener
@Mock private lateinit var authController: AuthController
+ @Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
@Mock private lateinit var dozeParameters: DozeParameters
@@ -90,8 +96,10 @@
KeyguardRepositoryImpl(
statusBarStateController,
wakefulnessLifecycle,
+ screenLifecycle,
biometricUnlockController,
keyguardStateController,
+ keyguardBypassController,
keyguardUpdateMonitor,
dozeTransitionListener,
dozeParameters,
@@ -157,6 +165,16 @@
}
@Test
+ fun dozeTimeTick() =
+ testScope.runTest {
+ var dozeTimeTickValue = collectLastValue(underTest.dozeTimeTick)
+ underTest.dozeTimeTick()
+ runCurrent()
+
+ assertThat(dozeTimeTickValue()).isNull()
+ }
+
+ @Test
fun isKeyguardShowing() =
testScope.runTest {
whenever(keyguardStateController.isShowing).thenReturn(false)
@@ -186,6 +204,20 @@
}
@Test
+ fun isBypassEnabled_disabledInController() {
+ whenever(keyguardBypassController.isBypassEnabled).thenReturn(false)
+ whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
+ assertThat(underTest.isBypassEnabled()).isFalse()
+ }
+
+ @Test
+ fun isBypassEnabled_enabledInController() {
+ whenever(keyguardBypassController.isBypassEnabled).thenReturn(true)
+ whenever(keyguardBypassController.bypassEnabled).thenReturn(true)
+ assertThat(underTest.isBypassEnabled()).isTrue()
+ }
+
+ @Test
fun isAodAvailable() = runTest {
val flow = underTest.isAodAvailable
var isAodAvailable = collectLastValue(flow)
@@ -354,6 +386,48 @@
}
@Test
+ fun screenModel() =
+ testScope.runTest {
+ val values = mutableListOf<ScreenModel>()
+ val job = underTest.screenModel.onEach(values::add).launchIn(this)
+
+ runCurrent()
+ val captor = argumentCaptor<ScreenLifecycle.Observer>()
+ verify(screenLifecycle).addObserver(captor.capture())
+
+ whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_TURNING_ON)
+ captor.value.onScreenTurningOn()
+ runCurrent()
+
+ whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_ON)
+ captor.value.onScreenTurnedOn()
+ runCurrent()
+
+ whenever(screenLifecycle.getScreenState())
+ .thenReturn(ScreenLifecycle.SCREEN_TURNING_OFF)
+ captor.value.onScreenTurningOff()
+ runCurrent()
+
+ whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_OFF)
+ captor.value.onScreenTurnedOff()
+ runCurrent()
+
+ assertThat(values.map { it.state })
+ .isEqualTo(
+ listOf(
+ // Initial value will be OFF
+ ScreenState.SCREEN_OFF,
+ ScreenState.SCREEN_TURNING_ON,
+ ScreenState.SCREEN_ON,
+ ScreenState.SCREEN_TURNING_OFF,
+ ScreenState.SCREEN_OFF,
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
fun isUdfpsSupported() =
testScope.runTest {
whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt
new file mode 100644
index 0000000..3389fa9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BiometricMessageInteractorTest.kt
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
+import android.hardware.fingerprint.FingerprintManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.util.IndicationHelper
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BiometricMessageInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: BiometricMessageInteractor
+ private lateinit var testScope: TestScope
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
+
+ @Mock private lateinit var indicationHelper: IndicationHelper
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ testScope = TestScope()
+ fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+ fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+ underTest =
+ BiometricMessageInteractor(
+ mContext.resources,
+ fingerprintAuthRepository,
+ fingerprintPropertyRepository,
+ indicationHelper,
+ keyguardUpdateMonitor,
+ )
+ }
+
+ @Test
+ fun fingerprintErrorMessage() =
+ testScope.runTest {
+ val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage)
+
+ // GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should NOT be suppressed
+ whenever(
+ indicationHelper.shouldSuppressErrorMsg(
+ FINGERPRINT,
+ FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
+ )
+ )
+ .thenReturn(false)
+
+ // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ msg = "test"
+ )
+ )
+
+ // THEN fingerprintErrorMessage is updated
+ assertThat(fingerprintErrorMessage?.source).isEqualTo(FINGERPRINT)
+ assertThat(fingerprintErrorMessage?.type).isEqualTo(BiometricMessageType.ERROR)
+ assertThat(fingerprintErrorMessage?.id)
+ .isEqualTo(FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE)
+ assertThat(fingerprintErrorMessage?.message).isEqualTo("test")
+ }
+
+ @Test
+ fun fingerprintErrorMessage_suppressedError() =
+ testScope.runTest {
+ val fingerprintErrorMessage by collectLastValue(underTest.fingerprintErrorMessage)
+
+ // GIVEN FINGERPRINT_ERROR_HW_UNAVAILABLE should be suppressed
+ whenever(
+ indicationHelper.shouldSuppressErrorMsg(
+ FINGERPRINT,
+ FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
+ )
+ )
+ .thenReturn(true)
+
+ // WHEN authentication status error is FINGERPRINT_ERROR_HW_UNAVAILABLE
+ fingerprintAuthRepository.setAuthenticationStatus(
+ ErrorFingerprintAuthenticationStatus(
+ msgId = FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ msg = "test"
+ )
+ )
+
+ // THEN fingerprintErrorMessage isn't update - it's still null
+ assertThat(fingerprintErrorMessage).isNull()
+ }
+
+ @Test
+ fun fingerprintHelpMessage() =
+ testScope.runTest {
+ val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage)
+
+ // GIVEN primary auth is NOT required
+ whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(true)
+
+ // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY
+ fingerprintAuthRepository.setAuthenticationStatus(
+ HelpFingerprintAuthenticationStatus(
+ msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
+ msg = "test"
+ )
+ )
+
+ // THEN fingerprintHelpMessage is updated
+ assertThat(fingerprintHelpMessage?.source).isEqualTo(FINGERPRINT)
+ assertThat(fingerprintHelpMessage?.type).isEqualTo(BiometricMessageType.HELP)
+ assertThat(fingerprintHelpMessage?.id)
+ .isEqualTo(FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY)
+ assertThat(fingerprintHelpMessage?.message).isEqualTo("test")
+ }
+
+ @Test
+ fun fingerprintHelpMessage_primaryAuthRequired() =
+ testScope.runTest {
+ val fingerprintHelpMessage by collectLastValue(underTest.fingerprintHelpMessage)
+
+ // GIVEN primary auth is required
+ whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(false)
+
+ // WHEN authentication status help is FINGERPRINT_ACQUIRED_IMAGER_DIRTY
+ fingerprintAuthRepository.setAuthenticationStatus(
+ HelpFingerprintAuthenticationStatus(
+ msgId = FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY,
+ msg = "test"
+ )
+ )
+
+ // THEN fingerprintHelpMessage isn't update - it's still null
+ assertThat(fingerprintHelpMessage).isNull()
+ }
+
+ @Test
+ fun fingerprintFailMessage_nonUdfps() =
+ testScope.runTest {
+ val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage)
+
+ // GIVEN primary auth is NOT required
+ whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(true)
+
+ // GIVEN rear fingerprint (not UDFPS)
+ fingerprintPropertyRepository.setProperties(
+ 0,
+ SensorStrength.STRONG,
+ FingerprintSensorType.REAR,
+ mapOf()
+ )
+
+ // WHEN authentication status fail
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+ // THEN fingerprintFailMessage is updated
+ assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT)
+ assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL)
+ assertThat(fingerprintFailMessage?.id)
+ .isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
+ assertThat(fingerprintFailMessage?.message)
+ .isEqualTo(
+ mContext.resources.getString(
+ com.android.internal.R.string.fingerprint_error_not_match
+ )
+ )
+ }
+
+ @Test
+ fun fingerprintFailMessage_udfps() =
+ testScope.runTest {
+ val fingerprintFailMessage by collectLastValue(underTest.fingerprintFailMessage)
+
+ // GIVEN primary auth is NOT required
+ whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(true)
+
+ // GIVEN UDFPS
+ fingerprintPropertyRepository.setProperties(
+ 0,
+ SensorStrength.STRONG,
+ FingerprintSensorType.UDFPS_OPTICAL,
+ mapOf()
+ )
+
+ // WHEN authentication status fail
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+ // THEN fingerprintFailMessage is updated to udfps message
+ assertThat(fingerprintFailMessage?.source).isEqualTo(FINGERPRINT)
+ assertThat(fingerprintFailMessage?.type).isEqualTo(BiometricMessageType.FAIL)
+ assertThat(fingerprintFailMessage?.id)
+ .isEqualTo(BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED)
+ assertThat(fingerprintFailMessage?.message)
+ .isEqualTo(
+ mContext.resources.getString(
+ com.android.internal.R.string.fingerprint_udfps_error_not_match
+ )
+ )
+ }
+
+ @Test
+ fun fingerprintFailedMessage_primaryAuthRequired() =
+ testScope.runTest {
+ val fingerprintFailedMessage by collectLastValue(underTest.fingerprintFailMessage)
+
+ // GIVEN primary auth is required
+ whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+ .thenReturn(false)
+
+ // WHEN authentication status fail
+ fingerprintAuthRepository.setAuthenticationStatus(FailFingerprintAuthenticationStatus)
+
+ // THEN fingerprintFailedMessage isn't update - it's still null
+ assertThat(fingerprintFailedMessage).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 3e81cd3..ced0a21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -38,10 +38,9 @@
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -121,8 +120,8 @@
mock(KeyguardStateController::class.java),
bouncerRepository,
mock(BiometricSettingsRepository::class.java),
- FakeDeviceEntryFingerprintAuthRepository(),
FakeSystemClock(),
+ keyguardUpdateMonitor,
),
keyguardTransitionInteractor,
featureFlags,
@@ -160,7 +159,7 @@
underTest.onDeviceLifted()
- val outputValue = authenticationStatus()!! as ErrorAuthenticationStatus
+ val outputValue = authenticationStatus()!! as ErrorFaceAuthenticationStatus
assertThat(outputValue.msgId)
.isEqualTo(BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT)
assertThat(outputValue.msg).isEqualTo("Face Unlock unavailable")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt b/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt
new file mode 100644
index 0000000..272d686
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.core
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogMessageImpl
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import org.mockito.Mockito.anyString
+
+/**
+ * A fake [LogBuffer] used for testing that obtains a real [LogMessage] to prevent a
+ * [NullPointerException].
+ */
+class FakeLogBuffer private constructor() {
+ class Factory private constructor() {
+ companion object {
+ fun create(): LogBuffer {
+ val logBuffer = mock<LogBuffer>()
+ whenever(
+ logBuffer.obtain(
+ tag = anyString(),
+ level = any(),
+ messagePrinter = any(),
+ exception = nullable(),
+ )
+ )
+ .thenReturn(LogMessageImpl.Factory.create())
+ return logBuffer
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index b4b3073..9a90a5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -216,9 +216,6 @@
@Mock private lateinit var recCardTitle: TextView
@Mock private lateinit var coverItem: ImageView
@Mock private lateinit var matrix: Matrix
- private lateinit var coverItem1: ImageView
- private lateinit var coverItem2: ImageView
- private lateinit var coverItem3: ImageView
private lateinit var recTitle1: TextView
private lateinit var recTitle2: TextView
private lateinit var recTitle3: TextView
@@ -233,7 +230,6 @@
FakeFeatureFlags().apply {
this.set(Flags.UMO_SURFACE_RIPPLE, false)
this.set(Flags.UMO_TURBULENCE_NOISE, false)
- this.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, false)
}
@Mock private lateinit var globalSettings: GlobalSettings
@@ -467,21 +463,25 @@
recSubtitle3 = TextView(context)
whenever(recommendationViewHolder.recommendations).thenReturn(view)
- whenever(recommendationViewHolder.cardIcon).thenReturn(appIcon)
-
- // Add a recommendation item
- coverItem1 = ImageView(context).also { it.setId(R.id.media_cover1) }
- coverItem2 = ImageView(context).also { it.setId(R.id.media_cover2) }
- coverItem3 = ImageView(context).also { it.setId(R.id.media_cover3) }
-
+ whenever(recommendationViewHolder.mediaAppIcons)
+ .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
+ whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
whenever(recommendationViewHolder.mediaCoverItems)
- .thenReturn(listOf(coverItem1, coverItem2, coverItem3))
+ .thenReturn(listOf(coverItem, coverItem, coverItem))
whenever(recommendationViewHolder.mediaCoverContainers)
.thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
whenever(recommendationViewHolder.mediaTitles)
.thenReturn(listOf(recTitle1, recTitle2, recTitle3))
whenever(recommendationViewHolder.mediaSubtitles)
.thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
+ whenever(recommendationViewHolder.mediaProgressBars)
+ .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
+ whenever(coverItem.imageMatrix).thenReturn(matrix)
+
+ // set ids for recommendation containers
+ whenever(coverContainer1.id).thenReturn(1)
+ whenever(coverContainer2.id).thenReturn(2)
+ whenever(coverContainer3.id).thenReturn(3)
whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
@@ -1561,7 +1561,8 @@
verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
val description = descriptionCaptor.value.toString()
- assertThat(description).contains(REC_APP_NAME)
+ assertThat(description)
+ .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header))
}
@Test
@@ -1585,7 +1586,8 @@
verify(viewHolder.player).contentDescription = descriptionCaptor.capture()
val description = descriptionCaptor.value.toString()
- assertThat(description).contains(REC_APP_NAME)
+ assertThat(description)
+ .isEqualTo(context.getString(R.string.controls_media_smartspace_rec_header))
}
@Test
@@ -2151,7 +2153,6 @@
@Test
fun bindRecommendation_setAfterExecutors() {
- setupUpdatedRecommendationViewHolder()
val albumArt = getColorIcon(Color.RED)
val data =
smartspaceData.copy(
@@ -2189,7 +2190,6 @@
@Test
fun bindRecommendationWithProgressBars() {
useRealConstraintSets()
- setupUpdatedRecommendationViewHolder()
val albumArt = getColorIcon(Color.RED)
val bundle =
Bundle().apply {
@@ -2236,7 +2236,6 @@
@Test
fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() {
useRealConstraintSets()
- setupUpdatedRecommendationViewHolder()
val albumArt = getColorIcon(Color.RED)
val data =
smartspaceData.copy(
@@ -2290,7 +2289,6 @@
@Test
fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() {
useRealConstraintSets()
- setupUpdatedRecommendationViewHolder()
val albumArt = getColorIcon(Color.RED)
val data =
smartspaceData.copy(
@@ -2505,27 +2503,6 @@
verify(activityStarter).postStartActivityDismissingKeyguard(eq(pendingIntent))
}
- private fun setupUpdatedRecommendationViewHolder() {
- fakeFeatureFlag.set(Flags.MEDIA_RECOMMENDATION_CARD_UPDATE, true)
- whenever(recommendationViewHolder.mediaAppIcons)
- .thenReturn(listOf(recAppIconItem, recAppIconItem, recAppIconItem))
- whenever(recommendationViewHolder.cardTitle).thenReturn(recCardTitle)
- whenever(recommendationViewHolder.mediaCoverContainers)
- .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
- whenever(recommendationViewHolder.mediaCoverItems)
- .thenReturn(listOf(coverItem, coverItem, coverItem))
- whenever(recommendationViewHolder.mediaProgressBars)
- .thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
- whenever(recommendationViewHolder.mediaSubtitles)
- .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
- whenever(coverItem.imageMatrix).thenReturn(matrix)
-
- // set ids for recommendation containers
- whenever(coverContainer1.id).thenReturn(1)
- whenever(coverContainer2.id).thenReturn(2)
- whenever(coverContainer3.id).thenReturn(3)
- }
-
private fun getColorIcon(color: Int): Icon {
val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bmp)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index c9956f3..ba97df9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -201,8 +201,8 @@
whenever(mockCopiedState.widgetStates)
.thenReturn(
mutableMapOf(
- R.id.media_title1 to mediaTitleWidgetState,
- R.id.media_subtitle1 to mediaSubTitleWidgetState,
+ R.id.media_title to mediaTitleWidgetState,
+ R.id.media_subtitle to mediaSubTitleWidgetState,
R.id.media_cover1_container to mediaContainerWidgetState
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
new file mode 100644
index 0000000..45f0a8c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Binder
+import android.os.IBinder
+import android.os.UserHandle
+import android.view.ContentRecordingSession
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakeMediaProjectionManager {
+
+ val mediaProjectionManager = mock<MediaProjectionManager>()
+
+ private val callbacks = mutableListOf<MediaProjectionManager.Callback>()
+
+ init {
+ whenever(mediaProjectionManager.addCallback(any(), any())).thenAnswer {
+ callbacks += it.arguments[0] as MediaProjectionManager.Callback
+ return@thenAnswer Unit
+ }
+ whenever(mediaProjectionManager.removeCallback(any())).thenAnswer {
+ callbacks -= it.arguments[0] as MediaProjectionManager.Callback
+ return@thenAnswer Unit
+ }
+ }
+
+ fun dispatchOnStart(info: MediaProjectionInfo = DEFAULT_INFO) {
+ callbacks.forEach { it.onStart(info) }
+ }
+
+ fun dispatchOnStop(info: MediaProjectionInfo = DEFAULT_INFO) {
+ callbacks.forEach { it.onStop(info) }
+ }
+
+ fun dispatchOnSessionSet(
+ info: MediaProjectionInfo = DEFAULT_INFO,
+ session: ContentRecordingSession?
+ ) {
+ callbacks.forEach { it.onRecordingSessionSet(info, session) }
+ }
+
+ companion object {
+ fun createDisplaySession(): ContentRecordingSession =
+ ContentRecordingSession.createDisplaySession(/* displayToMirror = */ 123)
+ fun createSingleTaskSession(token: IBinder = Binder()): ContentRecordingSession =
+ ContentRecordingSession.createTaskSession(token)
+
+ private const val DEFAULT_PACKAGE_NAME = "com.media.projection.test"
+ private val DEFAULT_USER_HANDLE = UserHandle.getUserHandleForUid(UserHandle.myUserId())
+ private val DEFAULT_INFO = MediaProjectionInfo(DEFAULT_PACKAGE_NAME, DEFAULT_USER_HANDLE)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt
deleted file mode 100644
index c59fd60..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionRepository.kt
+++ /dev/null
@@ -1,42 +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.mediaprojection.taskswitcher.data.repository
-
-import android.app.TaskInfo
-import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-class FakeMediaProjectionRepository : MediaProjectionRepository {
-
- private val state = MutableStateFlow<MediaProjectionState>(MediaProjectionState.NotProjecting)
-
- fun switchProjectedTask(newTask: TaskInfo) {
- state.value = MediaProjectionState.SingleTask(newTask)
- }
-
- override val mediaProjectionState: Flow<MediaProjectionState> = state.asStateFlow()
-
- fun projectEntireScreen() {
- state.value = MediaProjectionState.EntireScreen
- }
-
- fun stopProjecting() {
- state.value = MediaProjectionState.NotProjecting
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
deleted file mode 100644
index 593e389..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
+++ /dev/null
@@ -1,68 +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.mediaprojection.taskswitcher.data.repository
-
-import android.app.ActivityManager.RunningTaskInfo
-import android.content.Intent
-import android.os.IBinder
-import android.window.IWindowContainerToken
-import android.window.WindowContainerToken
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-class FakeTasksRepository : TasksRepository {
-
- private val _foregroundTask = MutableStateFlow(DEFAULT_TASK)
-
- override val foregroundTask: Flow<RunningTaskInfo> = _foregroundTask.asStateFlow()
-
- private val runningTasks = mutableListOf(DEFAULT_TASK)
-
- override suspend fun findRunningTaskFromWindowContainerToken(
- windowContainerToken: IBinder
- ): RunningTaskInfo? = runningTasks.firstOrNull { it.token.asBinder() == windowContainerToken }
-
- fun addRunningTask(task: RunningTaskInfo) {
- runningTasks.add(task)
- }
-
- fun moveTaskToForeground(task: RunningTaskInfo) {
- _foregroundTask.value = task
- }
-
- companion object {
- val DEFAULT_TASK = createTask(taskId = -1)
- val LAUNCHER_INTENT: Intent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
-
- fun createTask(
- taskId: Int,
- token: WindowContainerToken = createToken(),
- baseIntent: Intent = Intent()
- ) =
- RunningTaskInfo().apply {
- this.taskId = taskId
- this.token = token
- this.baseIntent = baseIntent
- }
-
- fun createToken(): WindowContainerToken {
- val realToken = object : IWindowContainerToken.Stub() {}
- return WindowContainerToken(realToken)
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
index 2b07465..3a74c72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -16,27 +16,22 @@
package com.android.systemui.mediaprojection.taskswitcher.data.repository
-import android.media.projection.MediaProjectionInfo
-import android.media.projection.MediaProjectionManager
import android.os.Binder
import android.os.Handler
-import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.view.ContentRecordingSession
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,29 +40,26 @@
@SmallTest
class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
- private val mediaProjectionManager = mock<MediaProjectionManager>()
-
private val dispatcher = StandardTestDispatcher()
private val testScope = TestScope(dispatcher)
- private val tasksRepo = FakeTasksRepository()
- private lateinit var callback: MediaProjectionManager.Callback
- private lateinit var repo: MediaProjectionManagerRepository
+ private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+ private val fakeActivityTaskManager = FakeActivityTaskManager()
- @Before
- fun setUp() {
- whenever(mediaProjectionManager.addCallback(any(), any())).thenAnswer {
- callback = it.arguments[0] as MediaProjectionManager.Callback
- return@thenAnswer Unit
- }
- repo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo
- )
- }
+ private val tasksRepo =
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = dispatcher
+ )
+
+ private val repo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo
+ )
@Test
fun mediaProjectionState_onStart_emitsNotProjecting() =
@@ -75,7 +67,7 @@
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- callback.onStart(TEST_MEDIA_INFO)
+ fakeMediaProjectionManager.dispatchOnStart()
assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
}
@@ -86,7 +78,7 @@
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- callback.onStop(TEST_MEDIA_INFO)
+ fakeMediaProjectionManager.dispatchOnStop()
assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
}
@@ -97,7 +89,7 @@
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, /* session= */ null)
+ fakeMediaProjectionManager.dispatchOnSessionSet(session = null)
assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
}
@@ -108,8 +100,9 @@
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- val session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = ContentRecordingSession.createDisplaySession(/* displayToMirror= */ 123)
+ )
assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
}
@@ -120,9 +113,10 @@
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- val session =
- ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null)
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session =
+ ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null)
+ )
assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
}
@@ -134,8 +128,9 @@
runCurrent()
val taskWindowContainerToken = Binder()
- val session = ContentRecordingSession.createTaskSession(taskWindowContainerToken)
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = ContentRecordingSession.createTaskSession(taskWindowContainerToken)
+ )
assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
}
@@ -143,20 +138,16 @@
@Test
fun mediaProjectionState_sessionSet_taskWithToken_matchingRunningTask_emitsSingleTask() =
testScope.runTest {
- val token = FakeTasksRepository.createToken()
- val task = FakeTasksRepository.createTask(taskId = 1, token = token)
- tasksRepo.addRunningTask(task)
+ val token = createToken()
+ val task = createTask(taskId = 1, token = token)
+ fakeActivityTaskManager.addRunningTasks(task)
val state by collectLastValue(repo.mediaProjectionState)
runCurrent()
- val session = ContentRecordingSession.createTaskSession(token.asBinder())
- callback.onRecordingSessionSet(TEST_MEDIA_INFO, session)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = ContentRecordingSession.createTaskSession(token.asBinder())
+ )
assertThat(state).isEqualTo(MediaProjectionState.SingleTask(task))
}
-
- companion object {
- val TEST_MEDIA_INFO =
- MediaProjectionInfo(/* packageName= */ "com.test.package", UserHandle.CURRENT)
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
index 112950b..b2ebe1bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.mediaprojection.taskswitcher.domain.interactor
import android.content.Intent
+import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -24,7 +25,9 @@
import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,7 +46,8 @@
private val testScope = TestScope(dispatcher)
private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val mediaRepo = FakeMediaProjectionRepository()
+ private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
private val tasksRepo =
ActivityTaskManagerTasksRepository(
activityTaskManager = fakeActivityTaskManager.activityTaskManager,
@@ -51,15 +55,26 @@
backgroundDispatcher = dispatcher
)
+ private val mediaRepo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo,
+ )
+
private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
@Test
fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
testScope.runTest {
- mediaRepo.stopProjecting()
+ val backgroundTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
- fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+ fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnStop()
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
}
@@ -67,10 +82,15 @@
@Test
fun taskSwitchChanges_projectingScreen_foregroundTaskChange_emitsNotProjectingTask() =
testScope.runTest {
- mediaRepo.projectEntireScreen()
+ val backgroundTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
- fakeActivityTaskManager.moveTaskToForeground(createTask(taskId = 1))
+ fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = FakeMediaProjectionManager.createDisplaySession()
+ )
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(taskSwitchState).isEqualTo(TaskSwitchState.NotProjectingTask)
}
@@ -80,9 +100,12 @@
testScope.runTest {
val projectedTask = createTask(taskId = 0)
val foregroundTask = createTask(taskId = 1)
- mediaRepo.switchProjectedTask(projectedTask)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(token = projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(taskSwitchState)
@@ -99,9 +122,12 @@
testScope.runTest {
val projectedTask = createTask(taskId = 0)
val foregroundTask = createTask(taskId = 1, baseIntent = LAUNCHER_INTENT)
- mediaRepo.switchProjectedTask(projectedTask)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
@@ -111,11 +137,13 @@
fun taskSwitchChanges_projectingTask_foregroundTaskSame_emitsTaskUnchanged() =
testScope.runTest {
val projectedTask = createTask(taskId = 0)
- val foregroundTask = createTask(taskId = 0)
- mediaRepo.switchProjectedTask(projectedTask)
val taskSwitchState by collectLastValue(interactor.taskSwitchChanges)
- fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+ fakeActivityTaskManager.addRunningTasks(projectedTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
+ fakeActivityTaskManager.moveTaskToForeground(projectedTask)
assertThat(taskSwitchState).isEqualTo(TaskSwitchState.TaskUnchanged)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
index cfbbf76..b396caf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
@@ -18,13 +18,15 @@
import android.app.Notification
import android.app.NotificationManager
+import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
import com.android.systemui.util.mockito.any
@@ -51,14 +53,25 @@
private val dispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(dispatcher)
+
private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val mediaRepo = FakeMediaProjectionRepository()
+ private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
private val tasksRepo =
ActivityTaskManagerTasksRepository(
activityTaskManager = fakeActivityTaskManager.activityTaskManager,
applicationScope = testScope.backgroundScope,
backgroundDispatcher = dispatcher
)
+
+ private val mediaRepo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo,
+ )
+
private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
private val viewModel = TaskSwitcherNotificationViewModel(interactor)
@@ -90,7 +103,7 @@
@Test
fun hideNotification() {
testScope.runTest {
- mediaRepo.stopProjecting()
+ fakeMediaProjectionManager.dispatchOnStop()
verify(notificationManager).cancel(any())
}
@@ -99,7 +112,7 @@
@Test
fun notificationIdIsConsistent() {
testScope.runTest {
- mediaRepo.stopProjecting()
+ fakeMediaProjectionManager.dispatchOnStop()
val idCancel = argumentCaptor<Int>()
verify(notificationManager).cancel(idCancel.capture())
@@ -114,7 +127,11 @@
private fun switchTask() {
val projectedTask = FakeActivityTaskManager.createTask(taskId = 1)
val foregroundTask = FakeActivityTaskManager.createTask(taskId = 2)
- mediaRepo.switchProjectedTask(projectedTask)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session =
+ FakeMediaProjectionManager.createSingleTaskSession(projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
index ea44fb3..7d38de4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel
import android.content.Intent
+import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -24,7 +25,10 @@
import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createDisplaySession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
import com.google.common.truth.Truth.assertThat
@@ -44,13 +48,23 @@
private val testScope = TestScope(dispatcher)
private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val mediaRepo = FakeMediaProjectionRepository()
+ private val fakeMediaProjectionManager = FakeMediaProjectionManager()
+
private val tasksRepo =
ActivityTaskManagerTasksRepository(
activityTaskManager = fakeActivityTaskManager.activityTaskManager,
applicationScope = testScope.backgroundScope,
backgroundDispatcher = dispatcher
)
+
+ private val mediaRepo =
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = testScope.backgroundScope,
+ tasksRepository = tasksRepo,
+ )
+
private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
private val viewModel = TaskSwitcherNotificationViewModel(interactor)
@@ -58,19 +72,23 @@
@Test
fun uiState_notProjecting_emitsNotShowing() =
testScope.runTest {
- mediaRepo.stopProjecting()
val uiState by collectLastValue(viewModel.uiState)
+ fakeMediaProjectionManager.dispatchOnStop()
+
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
}
@Test
fun uiState_notProjecting_foregroundTaskChanged_emitsNotShowing() =
testScope.runTest {
- mediaRepo.stopProjecting()
+ val backgroundTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
val uiState by collectLastValue(viewModel.uiState)
- mediaRepo.switchProjectedTask(createTask(taskId = 1))
+ fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnStop()
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
}
@@ -78,19 +96,23 @@
@Test
fun uiState_projectingEntireScreen_emitsNotShowing() =
testScope.runTest {
- mediaRepo.projectEntireScreen()
val uiState by collectLastValue(viewModel.uiState)
+ fakeMediaProjectionManager.dispatchOnSessionSet(session = createDisplaySession())
+
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
}
@Test
fun uiState_projectingEntireScreen_foregroundTaskChanged_emitsNotShowing() =
testScope.runTest {
- mediaRepo.projectEntireScreen()
+ val backgroundTask = createTask(taskId = 0)
+ val foregroundTask = createTask(taskId = 1)
val uiState by collectLastValue(viewModel.uiState)
- mediaRepo.switchProjectedTask(createTask(taskId = 1))
+ fakeActivityTaskManager.addRunningTasks(backgroundTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(session = createDisplaySession())
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
}
@@ -100,9 +122,12 @@
testScope.runTest {
val projectedTask = createTask(taskId = 1)
val foregroundTask = createTask(taskId = 2)
- mediaRepo.switchProjectedTask(projectedTask)
val uiState by collectLastValue(viewModel.uiState)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(uiState)
@@ -113,9 +138,12 @@
fun uiState_projectingTask_foregroundTaskChanged_same_emitsNotShowing() =
testScope.runTest {
val projectedTask = createTask(taskId = 1)
- mediaRepo.switchProjectedTask(projectedTask)
val uiState by collectLastValue(viewModel.uiState)
+ fakeActivityTaskManager.addRunningTasks(projectedTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(projectedTask)
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
@@ -126,9 +154,12 @@
testScope.runTest {
val projectedTask = createTask(taskId = 1)
val foregroundTask = createTask(taskId = 2, baseIntent = LAUNCHER_INTENT)
- mediaRepo.switchProjectedTask(projectedTask)
val uiState by collectLastValue(viewModel.uiState)
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
index 45bb931..435a1f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
@@ -182,6 +182,32 @@
assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_APPLICATION)
}
+ @Test
+ fun wakeUpIfDreaming_dreaming_woken() {
+ // GIVEN device is dreaming
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+
+ // WHEN wakeUpIfDreaming is called
+ underTest.wakeUpIfDreaming("testReason", PowerManager.WAKE_REASON_GESTURE)
+
+ // THEN device is woken up
+ assertThat(repository.lastWakeWhy).isEqualTo("testReason")
+ assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
+ }
+
+ @Test
+ fun wakeUpIfDreaming_notDreaming_notWoken() {
+ // GIVEN device is not dreaming
+ whenever(statusBarStateController.isDreaming).thenReturn(false)
+
+ // WHEN wakeUpIfDreaming is called
+ underTest.wakeUpIfDreaming("why", PowerManager.WAKE_REASON_TAP)
+
+ // THEN device is not woken
+ assertThat(repository.lastWakeWhy).isNull()
+ assertThat(repository.lastWakeReason).isNull()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
}
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 a0d8f98..9d9d0c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -154,6 +154,21 @@
verify(qsPanel).setCanCollapse(true)
}
+ @Test
+ fun multipleListeningOnlyCallsBrightnessControllerOnce() {
+ controller.setListening(true, true)
+ controller.setListening(true, false)
+ controller.setListening(true, true)
+
+ verify(brightnessController).registerCallbacks()
+
+ controller.setListening(false, true)
+ controller.setListening(false, false)
+ controller.setListening(false, true)
+
+ verify(brightnessController).unregisterCallbacks()
+ }
+
private fun setShouldUseSplitShade(shouldUse: Boolean) {
testableResources.addOverride(R.bool.config_use_split_notification_shade, shouldUse)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
index 3e9ddcb..5638d70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
@@ -28,7 +28,6 @@
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -36,7 +35,6 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() {
@@ -385,7 +383,7 @@
) {
featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
authenticationRepository.setUnlocked(isDeviceUnlocked)
- authenticationRepository.setBypassEnabled(isBypassEnabled)
+ keyguardRepository.setBypassEnabled(isBypassEnabled)
initialSceneKey?.let {
sceneInteractor.setCurrentScene(SceneContainerNames.SYSTEM_UI_DEFAULT, SceneModel(it))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
new file mode 100644
index 0000000..2b78405
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness
+
+import android.hardware.display.DisplayManager
+import android.os.Handler
+import android.service.vr.IVrManager
+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.settings.DisplayTracker
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class BrightnessControllerTest : SysuiTestCase() {
+
+ private val executor = FakeExecutor(FakeSystemClock())
+ private val secureSettings = FakeSettings()
+ @Mock private lateinit var toggleSlider: ToggleSlider
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var displayTracker: DisplayTracker
+ @Mock private lateinit var displayManager: DisplayManager
+ @Mock private lateinit var iVrManager: IVrManager
+
+ private lateinit var testableLooper: TestableLooper
+
+ private lateinit var underTest: BrightnessController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+
+ underTest =
+ BrightnessController(
+ context,
+ toggleSlider,
+ userTracker,
+ displayTracker,
+ displayManager,
+ secureSettings,
+ iVrManager,
+ executor,
+ mock(),
+ Handler(testableLooper.looper)
+ )
+ }
+
+ @Test
+ fun registerCallbacksMultipleTimes_onlyOneRegistration() {
+ val repeats = 100
+ repeat(repeats) { underTest.registerCallbacks() }
+ val messagesProcessed = testableLooper.processMessagesNonBlocking(repeats)
+
+ verify(displayTracker).addBrightnessChangeCallback(any(), any())
+ verify(iVrManager).registerListener(any())
+
+ assertThat(messagesProcessed).isEqualTo(1)
+ }
+
+ @Test
+ fun unregisterCallbacksMultipleTimes_onlyOneUnregistration() {
+ val repeats = 100
+ underTest.registerCallbacks()
+ testableLooper.processAllMessages()
+
+ repeat(repeats) { underTest.unregisterCallbacks() }
+ val messagesProcessed = testableLooper.processMessagesNonBlocking(repeats)
+
+ verify(displayTracker).removeCallback(any())
+ verify(iVrManager).unregisterListener(any())
+
+ assertThat(messagesProcessed).isEqualTo(1)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 5c35913..ed1397f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -18,7 +18,6 @@
import android.content.Intent
import android.graphics.Rect
-import android.os.Handler
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -29,8 +28,6 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.activity.SingleActivityFactory
-import com.android.systemui.settings.FakeDisplayTracker
-import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
@@ -53,28 +50,24 @@
@TestableLooper.RunWithLooper
class BrightnessDialogTest : SysuiTestCase() {
- @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory
- @Mock private lateinit var backgroundHandler: Handler
@Mock private lateinit var brightnessSliderController: BrightnessSliderController
+ @Mock private lateinit var brightnessControllerFactory: BrightnessController.Factory
+ @Mock private lateinit var brightnessController: BrightnessController
@Mock private lateinit var accessibilityMgr: AccessibilityManagerWrapper
private val clock = FakeSystemClock()
private val mainExecutor = FakeExecutor(clock)
- private var displayTracker = FakeDisplayTracker(mContext)
-
@Rule
@JvmField
var activityRule =
ActivityTestRule(
/* activityFactory= */ SingleActivityFactory {
TestDialog(
- userTracker,
- displayTracker,
brightnessSliderControllerFactory,
+ brightnessControllerFactory,
mainExecutor,
- backgroundHandler,
accessibilityMgr
)
},
@@ -88,6 +81,7 @@
`when`(brightnessSliderControllerFactory.create(any(), any()))
.thenReturn(brightnessSliderController)
`when`(brightnessSliderController.rootView).thenReturn(View(context))
+ `when`(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
}
@After
@@ -178,19 +172,15 @@
}
class TestDialog(
- userTracker: UserTracker,
- displayTracker: FakeDisplayTracker,
brightnessSliderControllerFactory: BrightnessSliderController.Factory,
+ brightnessControllerFactory: BrightnessController.Factory,
mainExecutor: DelayableExecutor,
- backgroundHandler: Handler,
accessibilityMgr: AccessibilityManagerWrapper
) :
BrightnessDialog(
- userTracker,
- displayTracker,
brightnessSliderControllerFactory,
+ brightnessControllerFactory,
mainExecutor,
- backgroundHandler,
accessibilityMgr
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
new file mode 100644
index 0000000..24d62fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LockscreenHostedDreamGestureListenerTest.kt
@@ -0,0 +1,189 @@
+/*
+ * 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
+
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import android.view.MotionEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+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
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+class LockscreenHostedDreamGestureListenerTest : SysuiTestCase() {
+ @Mock private lateinit var falsingManager: FalsingManager
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var shadeLogger: ShadeLogger
+ @Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController
+ @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private lateinit var powerRepository: FakePowerRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var underTest: LockscreenHostedDreamGestureListener
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ powerRepository = FakePowerRepository()
+ keyguardRepository = FakeKeyguardRepository()
+
+ underTest =
+ LockscreenHostedDreamGestureListener(
+ falsingManager,
+ PowerInteractor(
+ powerRepository,
+ keyguardRepository,
+ falsingCollector,
+ screenOffAnimationController,
+ statusBarStateController,
+ ),
+ statusBarStateController,
+ primaryBouncerInteractor,
+ keyguardRepository,
+ shadeLogger,
+ )
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(false)
+ }
+
+ @Test
+ fun testGestureDetector_onSingleTap_whileDreaming() =
+ testScope.runTest {
+ // GIVEN device dreaming and the dream is hosted in lockscreen
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ testScope.runCurrent()
+
+ // GIVEN the falsing manager does NOT think the tap is a false tap
+ whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN wake up device if dreaming
+ Truth.assertThat(powerRepository.lastWakeWhy).isNotNull()
+ Truth.assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP)
+ }
+
+ @Test
+ fun testGestureDetector_onSingleTap_notOnKeyguard() =
+ testScope.runTest {
+ // GIVEN device dreaming and the dream is hosted in lockscreen
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ testScope.runCurrent()
+
+ // GIVEN shade is open
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+
+ // GIVEN the falsing manager does NOT think the tap is a false tap
+ whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN the falsing manager never gets a call
+ verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+ }
+
+ @Test
+ fun testGestureDetector_onSingleTap_bouncerShown() =
+ testScope.runTest {
+ // GIVEN device dreaming and the dream is hosted in lockscreen
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ testScope.runCurrent()
+
+ // GIVEN bouncer is expanded
+ whenever(primaryBouncerInteractor.isBouncerShowing()).thenReturn(true)
+
+ // GIVEN the falsing manager does NOT think the tap is a false tap
+ whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(false)
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN the falsing manager never gets a call
+ verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+ }
+
+ @Test
+ fun testGestureDetector_onSingleTap_falsing() =
+ testScope.runTest {
+ // GIVEN device dreaming and the dream is hosted in lockscreen
+ whenever(statusBarStateController.isDreaming).thenReturn(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ testScope.runCurrent()
+
+ // GIVEN the falsing manager thinks the tap is a false tap
+ whenever(falsingManager.isFalseTap(ArgumentMatchers.anyInt())).thenReturn(true)
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN the device doesn't wake up
+ Truth.assertThat(powerRepository.lastWakeWhy).isNull()
+ Truth.assertThat(powerRepository.lastWakeReason).isNull()
+ }
+
+ @Test
+ fun testSingleTap_notDreaming_noFalsingCheck() =
+ testScope.runTest {
+ // GIVEN device not dreaming with lockscreen hosted dream
+ whenever(statusBarStateController.isDreaming).thenReturn(false)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+ testScope.runCurrent()
+
+ // WHEN there's a tap
+ underTest.onSingleTapUp(upEv)
+
+ // THEN the falsing manager never gets a call
+ verify(falsingManager, never()).isFalseTap(ArgumentMatchers.anyInt())
+ }
+}
+
+private val upEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
deleted file mode 100644
index 168cbb7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
+++ /dev/null
@@ -1,512 +0,0 @@
-package com.android.systemui.shade
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowInsets
-import android.view.WindowManagerPolicyConstants
-import androidx.annotation.IdRes
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.fragments.FragmentHostManager
-import com.android.systemui.fragments.FragmentService
-import com.android.systemui.navigationbar.NavigationModeController
-import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
-import com.android.systemui.plugins.qs.QS
-import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import java.util.function.Consumer
-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.Mockito.RETURNS_DEEP_STUBS
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class NotificationQSContainerControllerTest : SysuiTestCase() {
-
- companion object {
- const val STABLE_INSET_BOTTOM = 100
- const val CUTOUT_HEIGHT = 50
- const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
- const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
- const val NOTIFICATIONS_MARGIN = 50
- const val SCRIM_MARGIN = 10
- const val FOOTER_ACTIONS_INSET = 2
- const val FOOTER_ACTIONS_PADDING = 2
- const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
- const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
- }
-
- @Mock
- private lateinit var navigationModeController: NavigationModeController
- @Mock
- private lateinit var overviewProxyService: OverviewProxyService
- @Mock
- private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer
- @Mock
- private lateinit var mShadeHeaderController: ShadeHeaderController
- @Mock
- private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
- @Mock
- private lateinit var fragmentService: FragmentService
- @Mock
- private lateinit var fragmentHostManager: FragmentHostManager
- @Captor
- lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
- @Captor
- lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
- @Captor
- lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
- @Captor
- lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
- @Captor
- lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
-
- private lateinit var controller: NotificationsQSContainerController
- private lateinit var navigationModeCallback: ModeChangedListener
- private lateinit var taskbarVisibilityCallback: OverviewProxyListener
- private lateinit var windowInsetsCallback: Consumer<WindowInsets>
- private lateinit var delayableExecutor: FakeExecutor
- private lateinit var fakeSystemClock: FakeSystemClock
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- mContext.ensureTestableResources()
- whenever(notificationsQSContainer.context).thenReturn(mContext)
- whenever(notificationsQSContainer.resources).thenReturn(mContext.resources)
- whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
- fakeSystemClock = FakeSystemClock()
- delayableExecutor = FakeExecutor(fakeSystemClock)
-
- controller = NotificationsQSContainerController(
- notificationsQSContainer,
- navigationModeController,
- overviewProxyService,
- mShadeHeaderController,
- shadeExpansionStateManager,
- fragmentService,
- delayableExecutor
- )
-
- overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
- overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
- overrideResource(R.bool.config_use_split_notification_shade, false)
- overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
- overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
- whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
- .thenReturn(GESTURES_NAVIGATION)
- doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
- doNothing().`when`(notificationsQSContainer)
- .setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
- doNothing().`when`(notificationsQSContainer).applyConstraints(constraintSetCaptor.capture())
- doNothing().`when`(notificationsQSContainer)
- .addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
- controller.init()
- attachStateListenerCaptor.value.onViewAttachedToWindow(notificationsQSContainer)
-
- navigationModeCallback = navigationModeCaptor.value
- taskbarVisibilityCallback = taskbarVisibilityCaptor.value
- windowInsetsCallback = windowInsetsCallbackCaptor.value
- }
-
- @Test
- fun testTaskbarVisibleInSplitShade() {
- enableSplitShade()
-
- given(taskbarVisible = true,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
- expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-
- given(taskbarVisible = true,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = STABLE_INSET_BOTTOM,
- expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
- }
-
- @Test
- fun testTaskbarNotVisibleInSplitShade() {
- // when taskbar is not visible, it means we're on the home screen
- enableSplitShade()
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
- expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
- expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
- }
-
- @Test
- fun testTaskbarNotVisibleInSplitShadeWithCutout() {
- enableSplitShade()
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withCutout())
- then(expectedContainerPadding = CUTOUT_HEIGHT,
- expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withCutout().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
- expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
- }
-
- @Test
- fun testTaskbarVisibleInSinglePaneShade() {
- disableSplitShade()
-
- given(taskbarVisible = true,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedQsPadding = STABLE_INSET_BOTTOM)
-
- given(taskbarVisible = true,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = STABLE_INSET_BOTTOM,
- expectedQsPadding = STABLE_INSET_BOTTOM)
- }
-
- @Test
- fun testTaskbarNotVisibleInSinglePaneShade() {
- disableSplitShade()
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0)
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withCutout().withStableBottom())
- then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
- expectedQsPadding = STABLE_INSET_BOTTOM)
- }
-
- @Test
- fun testDetailShowingInSinglePaneShade() {
- disableSplitShade()
- controller.setDetailShowing(true)
-
- // always sets spacings to 0
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = 0)
-
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0,
- expectedNotificationsMargin = 0)
- }
-
- @Test
- fun testDetailShowingInSplitShade() {
- enableSplitShade()
- controller.setDetailShowing(true)
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = windowInsets().withStableBottom())
- then(expectedContainerPadding = 0)
-
- // should not influence spacing
- given(taskbarVisible = false,
- navigationMode = BUTTONS_NAVIGATION,
- insets = emptyInsets())
- then(expectedContainerPadding = 0)
- }
-
- @Test
- fun testNotificationsMarginBottomIsUpdated() {
- Mockito.clearInvocations(notificationsQSContainer)
- enableSplitShade()
- verify(notificationsQSContainer).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
-
- overrideResource(R.dimen.notification_panel_margin_bottom, 100)
- disableSplitShade()
- verify(notificationsQSContainer).setNotificationsMarginBottom(100)
- }
-
- @Test
- fun testSplitShadeLayout_isAlignedToGuideline() {
- enableSplitShade()
- controller.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
- .isEqualTo(R.id.qs_edge_guideline)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
- .isEqualTo(R.id.qs_edge_guideline)
- }
-
- @Test
- fun testSinglePaneLayout_childrenHaveEqualMargins() {
- disableSplitShade()
- controller.updateResources()
- val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
- val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
- val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin
- val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin
- assertThat(qsStartMargin == qsEndMargin &&
- notifStartMargin == notifEndMargin &&
- qsStartMargin == notifStartMargin
- ).isTrue()
- }
-
- @Test
- fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
- enableSplitShade()
- controller.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
- .isEqualTo(0)
- }
-
- @Test
- fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
- enableSplitShade()
- controller.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
- assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
- }
-
- @Test
- fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
- setLargeScreen()
- val largeScreenHeaderHeight = 100
- overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
-
- controller.updateResources()
-
- assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
- .isEqualTo(largeScreenHeaderHeight)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
- .isEqualTo(largeScreenHeaderHeight)
- }
-
- @Test
- fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
- setSmallScreen()
- controller.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
- .isEqualTo(0)
- }
-
- @Test
- fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
- disableSplitShade()
- controller.updateResources()
- val notificationPanelMarginHorizontal = context.resources
- .getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
- assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
- .isEqualTo(notificationPanelMarginHorizontal)
- assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
- .isEqualTo(notificationPanelMarginHorizontal)
- }
-
- @Test
- fun testSinglePaneShadeLayout_isAlignedToParent() {
- disableSplitShade()
- controller.updateResources()
- assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
- .isEqualTo(ConstraintSet.PARENT_ID)
- assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
- .isEqualTo(ConstraintSet.PARENT_ID)
- }
-
- @Test
- fun testAllChildrenOfNotificationContainer_haveIds() {
- // set dimen to 0 to avoid triggering updating bottom spacing
- overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
- val container = NotificationsQuickSettingsContainer(context, null)
- container.removeAllViews()
- container.addView(newViewWithId(1))
- container.addView(newViewWithId(View.NO_ID))
- val controller = NotificationsQSContainerController(
- container,
- navigationModeController,
- overviewProxyService,
- mShadeHeaderController,
- shadeExpansionStateManager,
- fragmentService,
- delayableExecutor
- )
- controller.updateConstraints()
-
- assertThat(container.getChildAt(0).id).isEqualTo(1)
- assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
- }
-
- @Test
- fun testWindowInsetDebounce() {
- disableSplitShade()
-
- given(taskbarVisible = false,
- navigationMode = GESTURES_NAVIGATION,
- insets = emptyInsets(),
- applyImmediately = false)
- fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
- windowInsetsCallback.accept(windowInsets().withStableBottom())
-
- delayableExecutor.advanceClockToLast()
- delayableExecutor.runAllReady()
-
- verify(notificationsQSContainer, never()).setQSContainerPaddingBottom(0)
- verify(notificationsQSContainer).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
- }
-
- @Test
- fun testStartCustomizingWithDuration() {
- controller.setCustomizerShowing(true, 100L)
- verify(mShadeHeaderController).startCustomizingAnimation(true, 100L)
- }
-
- @Test
- fun testEndCustomizingWithDuration() {
- controller.setCustomizerShowing(true, 0L) // Only tracks changes
- reset(mShadeHeaderController)
-
- controller.setCustomizerShowing(false, 100L)
- verify(mShadeHeaderController).startCustomizingAnimation(false, 100L)
- }
-
- @Test
- fun testTagListenerAdded() {
- verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(notificationsQSContainer))
- }
-
- @Test
- fun testTagListenerRemoved() {
- attachStateListenerCaptor.value.onViewDetachedFromWindow(notificationsQSContainer)
- verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(notificationsQSContainer))
- }
-
- private fun disableSplitShade() {
- setSplitShadeEnabled(false)
- }
-
- private fun enableSplitShade() {
- setSplitShadeEnabled(true)
- }
-
- private fun setSplitShadeEnabled(enabled: Boolean) {
- overrideResource(R.bool.config_use_split_notification_shade, enabled)
- controller.updateResources()
- }
-
- private fun setSmallScreen() {
- setLargeScreenEnabled(false)
- }
-
- private fun setLargeScreen() {
- setLargeScreenEnabled(true)
- }
-
- private fun setLargeScreenEnabled(enabled: Boolean) {
- overrideResource(R.bool.config_use_large_screen_shade_header, enabled)
- }
-
- private fun given(
- taskbarVisible: Boolean,
- navigationMode: Int,
- insets: WindowInsets,
- applyImmediately: Boolean = true
- ) {
- Mockito.clearInvocations(notificationsQSContainer)
- taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
- navigationModeCallback.onNavigationModeChanged(navigationMode)
- windowInsetsCallback.accept(insets)
- if (applyImmediately) {
- delayableExecutor.advanceClockToLast()
- delayableExecutor.runAllReady()
- }
- }
-
- fun then(
- expectedContainerPadding: Int,
- expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
- expectedQsPadding: Int = 0
- ) {
- verify(notificationsQSContainer)
- .setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
- verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin)
- verify(notificationsQSContainer)
- .setQSContainerPaddingBottom(expectedQsPadding)
- Mockito.clearInvocations(notificationsQSContainer)
- }
-
- private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
-
- private fun emptyInsets() = mock(WindowInsets::class.java)
-
- private fun WindowInsets.withCutout(): WindowInsets {
- whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
- return this
- }
-
- private fun WindowInsets.withStableBottom(): WindowInsets {
- whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
- return this
- }
-
- private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
- return constraintSetCaptor.value.getConstraint(id).layout
- }
-
- private fun newViewWithId(id: Int): View {
- val view = View(mContext)
- view.id = id
- val layoutParams = ConstraintLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
- // required as cloning ConstraintSet fails if view doesn't have layout params
- view.layoutParams = layoutParams
- return view
- }
-}
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 5fb3a79..2a398c55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -111,6 +111,8 @@
@Mock private lateinit var lockIconViewController: LockIconViewController
@Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
+ @Mock
+ private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -147,6 +149,7 @@
featureFlags.set(Flags.DUAL_SHADE, false)
featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
val inputProxy = MultiShadeInputProxy()
testScope = TestScope()
@@ -183,6 +186,7 @@
notificationInsetsController,
ambientState,
pulsingGestureListener,
+ mLockscreenHostedDreamGestureListener,
keyguardBouncerViewModel,
keyguardBouncerComponentFactory,
mock(KeyguardMessageAreaController.Factory::class.java),
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 544137e..d9eb9b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -113,6 +113,8 @@
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock private lateinit var ambientState: AmbientState
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
+ @Mock
+ private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -161,6 +163,7 @@
featureFlags.set(Flags.DUAL_SHADE, false)
featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
val inputProxy = MultiShadeInputProxy()
testScope = TestScope()
val multiShadeInteractor =
@@ -196,6 +199,7 @@
notificationInsetsController,
ambientState,
pulsingGestureListener,
+ mLockscreenHostedDreamGestureListener,
keyguardBouncerViewModel,
keyguardBouncerComponentFactory,
Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
new file mode 100644
index 0000000..2bc112d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -0,0 +1,596 @@
+/*
+ * 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
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableResources
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManagerPolicyConstants
+import androidx.annotation.IdRes
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.fragments.FragmentService
+import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.plugins.qs.QS
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+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.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/** Uses Flags.MIGRATE_NSSL set to false. If all goes well, this set of tests will be deleted. */
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
+
+ @Mock lateinit var view: NotificationsQuickSettingsContainer
+ @Mock lateinit var navigationModeController: NavigationModeController
+ @Mock lateinit var overviewProxyService: OverviewProxyService
+ @Mock lateinit var shadeHeaderController: ShadeHeaderController
+ @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+ @Mock lateinit var fragmentService: FragmentService
+ @Mock lateinit var fragmentHostManager: FragmentHostManager
+ @Mock
+ lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
+ @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
+ @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
+ @Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
+ @Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
+ @Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
+
+ lateinit var underTest: NotificationsQSContainerController
+
+ private lateinit var fakeResources: TestableResources
+ private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var navigationModeCallback: ModeChangedListener
+ private lateinit var taskbarVisibilityCallback: OverviewProxyListener
+ private lateinit var windowInsetsCallback: Consumer<WindowInsets>
+ private lateinit var fakeSystemClock: FakeSystemClock
+ private lateinit var delayableExecutor: FakeExecutor
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ fakeSystemClock = FakeSystemClock()
+ delayableExecutor = FakeExecutor(fakeSystemClock)
+ featureFlags = FakeFeatureFlags().apply { set(Flags.MIGRATE_NSSL, false) }
+ mContext.ensureTestableResources()
+ whenever(view.context).thenReturn(mContext)
+ whenever(view.resources).thenReturn(mContext.resources)
+
+ whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
+
+ underTest =
+ NotificationsQSContainerController(
+ view,
+ navigationModeController,
+ overviewProxyService,
+ shadeHeaderController,
+ shadeExpansionStateManager,
+ fragmentService,
+ delayableExecutor,
+ featureFlags,
+ notificationStackScrollLayoutController,
+ )
+
+ overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
+ overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
+ overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
+ whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
+ .thenReturn(GESTURES_NAVIGATION)
+ doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
+ doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
+ doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture())
+ doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
+ underTest.init()
+ attachStateListenerCaptor.value.onViewAttachedToWindow(view)
+
+ navigationModeCallback = navigationModeCaptor.value
+ taskbarVisibilityCallback = taskbarVisibilityCaptor.value
+ windowInsetsCallback = windowInsetsCallbackCaptor.value
+
+ Mockito.clearInvocations(view)
+ }
+
+ @Test
+ fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
+ overrideResource(R.bool.config_use_large_screen_shade_header, false)
+ overrideResource(R.dimen.qs_header_height, 1)
+ overrideResource(R.dimen.large_screen_shade_header_height, 2)
+
+ underTest.updateResources()
+
+ val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+ verify(view).applyConstraints(capture(captor))
+ assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(1)
+ }
+
+ @Test
+ fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.qs_header_height, 1)
+ overrideResource(R.dimen.large_screen_shade_header_height, 2)
+
+ underTest.updateResources()
+
+ val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
+ verify(view).applyConstraints(capture(captor))
+ assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2)
+ }
+
+ @Test
+ fun testTaskbarVisibleInSplitShade() {
+ enableSplitShade()
+
+ given(
+ taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = STABLE_INSET_BOTTOM,
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSplitShade() {
+ // when taskbar is not visible, it means we're on the home screen
+ enableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSplitShadeWithCutout() {
+ enableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout()
+ )
+ then(
+ expectedContainerPadding = CUTOUT_HEIGHT,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarVisibleInSinglePaneShade() {
+ disableSplitShade()
+
+ given(
+ taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+ given(
+ taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = STABLE_INSET_BOTTOM,
+ expectedQsPadding = STABLE_INSET_BOTTOM
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSinglePaneShade() {
+ disableSplitShade()
+
+ given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom()
+ )
+ then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM
+ )
+ }
+
+ @Test
+ fun testDetailShowingInSinglePaneShade() {
+ disableSplitShade()
+ underTest.setDetailShowing(true)
+
+ // always sets spacings to 0
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+
+ given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+ }
+
+ @Test
+ fun testDetailShowingInSplitShade() {
+ enableSplitShade()
+ underTest.setDetailShowing(true)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0)
+
+ // should not influence spacing
+ given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0)
+ }
+
+ @Test
+ fun testNotificationsMarginBottomIsUpdated() {
+ Mockito.clearInvocations(view)
+ enableSplitShade()
+ verify(view).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
+
+ overrideResource(R.dimen.notification_panel_margin_bottom, 100)
+ disableSplitShade()
+ verify(view).setNotificationsMarginBottom(100)
+ }
+
+ @Test
+ fun testSplitShadeLayout_isAlignedToGuideline() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
+ .isEqualTo(R.id.qs_edge_guideline)
+ }
+
+ @Test
+ fun testSinglePaneLayout_childrenHaveEqualMargins() {
+ disableSplitShade()
+ underTest.updateResources()
+ val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
+ val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
+ val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin
+ val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin
+ assertThat(
+ qsStartMargin == qsEndMargin &&
+ notifStartMargin == notifEndMargin &&
+ qsStartMargin == notifStartMargin
+ )
+ .isTrue()
+ }
+
+ @Test
+ fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
+ .isEqualTo(0)
+ }
+
+ @Test
+ fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
+ }
+
+ @Test
+ fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+ setLargeScreen()
+ val largeScreenHeaderHeight = 100
+ overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+
+ underTest.updateResources()
+
+ assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+ .isEqualTo(largeScreenHeaderHeight)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
+ .isEqualTo(largeScreenHeaderHeight)
+ }
+
+ @Test
+ fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
+ setSmallScreen()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin).isEqualTo(0)
+ }
+
+ @Test
+ fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
+ disableSplitShade()
+ underTest.updateResources()
+ val notificationPanelMarginHorizontal =
+ mContext.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
+ .isEqualTo(notificationPanelMarginHorizontal)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
+ .isEqualTo(notificationPanelMarginHorizontal)
+ }
+
+ @Test
+ fun testSinglePaneShadeLayout_isAlignedToParent() {
+ disableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
+ .isEqualTo(ConstraintSet.PARENT_ID)
+ assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
+ .isEqualTo(ConstraintSet.PARENT_ID)
+ }
+
+ @Test
+ fun testAllChildrenOfNotificationContainer_haveIds() {
+ // set dimen to 0 to avoid triggering updating bottom spacing
+ overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
+ val container = NotificationsQuickSettingsContainer(mContext, null)
+ container.removeAllViews()
+ container.addView(newViewWithId(1))
+ container.addView(newViewWithId(View.NO_ID))
+ val controller =
+ NotificationsQSContainerController(
+ container,
+ navigationModeController,
+ overviewProxyService,
+ shadeHeaderController,
+ shadeExpansionStateManager,
+ fragmentService,
+ delayableExecutor,
+ featureFlags,
+ notificationStackScrollLayoutController,
+ )
+ controller.updateConstraints()
+
+ assertThat(container.getChildAt(0).id).isEqualTo(1)
+ assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
+ }
+
+ @Test
+ fun testWindowInsetDebounce() {
+ disableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = emptyInsets(),
+ applyImmediately = false
+ )
+ fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
+ windowInsetsCallback.accept(windowInsets().withStableBottom())
+
+ delayableExecutor.advanceClockToLast()
+ delayableExecutor.runAllReady()
+
+ verify(view, never()).setQSContainerPaddingBottom(0)
+ verify(view).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
+ }
+
+ @Test
+ fun testStartCustomizingWithDuration() {
+ underTest.setCustomizerShowing(true, 100L)
+ verify(shadeHeaderController).startCustomizingAnimation(true, 100L)
+ }
+
+ @Test
+ fun testEndCustomizingWithDuration() {
+ underTest.setCustomizerShowing(true, 0L) // Only tracks changes
+ reset(shadeHeaderController)
+
+ underTest.setCustomizerShowing(false, 100L)
+ verify(shadeHeaderController).startCustomizingAnimation(false, 100L)
+ }
+
+ @Test
+ fun testTagListenerAdded() {
+ verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(view))
+ }
+
+ @Test
+ fun testTagListenerRemoved() {
+ attachStateListenerCaptor.value.onViewDetachedFromWindow(view)
+ verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(view))
+ }
+
+ private fun disableSplitShade() {
+ setSplitShadeEnabled(false)
+ }
+
+ private fun enableSplitShade() {
+ setSplitShadeEnabled(true)
+ }
+
+ private fun setSplitShadeEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_split_notification_shade, enabled)
+ underTest.updateResources()
+ }
+
+ private fun setSmallScreen() {
+ setLargeScreenEnabled(false)
+ }
+
+ private fun setLargeScreen() {
+ setLargeScreenEnabled(true)
+ }
+
+ private fun setLargeScreenEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_large_screen_shade_header, enabled)
+ }
+
+ private fun given(
+ taskbarVisible: Boolean,
+ navigationMode: Int,
+ insets: WindowInsets,
+ applyImmediately: Boolean = true
+ ) {
+ Mockito.clearInvocations(view)
+ taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
+ navigationModeCallback.onNavigationModeChanged(navigationMode)
+ windowInsetsCallback.accept(insets)
+ if (applyImmediately) {
+ delayableExecutor.advanceClockToLast()
+ delayableExecutor.runAllReady()
+ }
+ }
+
+ fun then(
+ expectedContainerPadding: Int,
+ expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
+ expectedQsPadding: Int = 0
+ ) {
+ verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
+ verify(view).setNotificationsMarginBottom(expectedNotificationsMargin)
+ verify(view).setQSContainerPaddingBottom(expectedQsPadding)
+ Mockito.clearInvocations(view)
+ }
+
+ private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
+
+ private fun emptyInsets() = mock(WindowInsets::class.java)
+
+ private fun WindowInsets.withCutout(): WindowInsets {
+ whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+ return this
+ }
+
+ private fun WindowInsets.withStableBottom(): WindowInsets {
+ whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
+ return this
+ }
+
+ private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
+ return constraintSetCaptor.value.getConstraint(id).layout
+ }
+
+ private fun newViewWithId(id: Int): View {
+ val view = View(mContext)
+ view.id = id
+ val layoutParams =
+ ConstraintLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ // required as cloning ConstraintSet fails if view doesn't have layout params
+ view.layoutParams = layoutParams
+ return view
+ }
+
+ companion object {
+ const val STABLE_INSET_BOTTOM = 100
+ const val CUTOUT_HEIGHT = 50
+ const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+ const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ const val NOTIFICATIONS_MARGIN = 50
+ const val SCRIM_MARGIN = 10
+ const val FOOTER_ACTIONS_INSET = 2
+ const val FOOTER_ACTIONS_PADDING = 2
+ const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
+ const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index d4751c8..a504818 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -19,24 +19,47 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableResources
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManagerPolicyConstants
+import androidx.annotation.IdRes
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
+import com.android.systemui.plugins.qs.QS
import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
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.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -51,19 +74,37 @@
@Mock lateinit var shadeHeaderController: ShadeHeaderController
@Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
@Mock lateinit var fragmentService: FragmentService
+ @Mock lateinit var fragmentHostManager: FragmentHostManager
+ @Mock
+ lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
+
+ @Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
+ @Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
+ @Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
+ @Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
+ @Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
lateinit var underTest: NotificationsQSContainerController
private lateinit var fakeResources: TestableResources
-
- private val delayableExecutor: DelayableExecutor = FakeExecutor(FakeSystemClock())
+ private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var navigationModeCallback: ModeChangedListener
+ private lateinit var taskbarVisibilityCallback: OverviewProxyListener
+ private lateinit var windowInsetsCallback: Consumer<WindowInsets>
+ private lateinit var fakeSystemClock: FakeSystemClock
+ private lateinit var delayableExecutor: FakeExecutor
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- fakeResources = TestableResources(context.resources)
+ fakeSystemClock = FakeSystemClock()
+ delayableExecutor = FakeExecutor(fakeSystemClock)
+ featureFlags = FakeFeatureFlags().apply { set(Flags.MIGRATE_NSSL, true) }
+ mContext.ensureTestableResources()
+ whenever(view.context).thenReturn(mContext)
+ whenever(view.resources).thenReturn(mContext.resources)
- whenever(view.resources).thenReturn(fakeResources.resources)
+ whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
underTest =
NotificationsQSContainerController(
@@ -74,16 +115,36 @@
shadeExpansionStateManager,
fragmentService,
delayableExecutor,
+ featureFlags,
+ notificationStackScrollLayoutController,
)
+
+ overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
+ overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
+ overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
+ whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
+ .thenReturn(GESTURES_NAVIGATION)
+ doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
+ doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
+ doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture())
+ doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
+ underTest.init()
+ attachStateListenerCaptor.value.onViewAttachedToWindow(view)
+
+ navigationModeCallback = navigationModeCaptor.value
+ taskbarVisibilityCallback = taskbarVisibilityCaptor.value
+ windowInsetsCallback = windowInsetsCallbackCaptor.value
+
+ Mockito.clearInvocations(view)
}
@Test
fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
- with(fakeResources) {
- addOverride(R.bool.config_use_large_screen_shade_header, false)
- addOverride(R.dimen.qs_header_height, 1)
- addOverride(R.dimen.large_screen_shade_header_height, 2)
- }
+ overrideResource(R.bool.config_use_large_screen_shade_header, false)
+ overrideResource(R.dimen.qs_header_height, 1)
+ overrideResource(R.dimen.large_screen_shade_header_height, 2)
underTest.updateResources()
@@ -94,11 +155,9 @@
@Test
fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
- with(fakeResources) {
- addOverride(R.bool.config_use_large_screen_shade_header, true)
- addOverride(R.dimen.qs_header_height, 1)
- addOverride(R.dimen.large_screen_shade_header_height, 2)
- }
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.qs_header_height, 1)
+ overrideResource(R.dimen.large_screen_shade_header_height, 2)
underTest.updateResources()
@@ -106,4 +165,415 @@
verify(view).applyConstraints(capture(captor))
assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2)
}
+
+ @Test
+ fun testTaskbarVisibleInSplitShade() {
+ enableSplitShade()
+
+ given(
+ taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = STABLE_INSET_BOTTOM,
+ expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSplitShade() {
+ // when taskbar is not visible, it means we're on the home screen
+ enableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSplitShadeWithCutout() {
+ enableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout()
+ )
+ then(
+ expectedContainerPadding = CUTOUT_HEIGHT,
+ expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
+ )
+ }
+
+ @Test
+ fun testTaskbarVisibleInSinglePaneShade() {
+ disableSplitShade()
+
+ given(
+ taskbarVisible = true,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+ given(
+ taskbarVisible = true,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = STABLE_INSET_BOTTOM,
+ expectedQsPadding = STABLE_INSET_BOTTOM
+ )
+ }
+
+ @Test
+ fun testTaskbarNotVisibleInSinglePaneShade() {
+ disableSplitShade()
+
+ given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withCutout().withStableBottom()
+ )
+ then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = BUTTONS_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(
+ expectedContainerPadding = 0,
+ expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
+ expectedQsPadding = STABLE_INSET_BOTTOM
+ )
+ }
+
+ @Test
+ fun testDetailShowingInSinglePaneShade() {
+ disableSplitShade()
+ underTest.setDetailShowing(true)
+
+ // always sets spacings to 0
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+
+ given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
+ }
+
+ @Test
+ fun testDetailShowingInSplitShade() {
+ enableSplitShade()
+ underTest.setDetailShowing(true)
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = windowInsets().withStableBottom()
+ )
+ then(expectedContainerPadding = 0)
+
+ // should not influence spacing
+ given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
+ then(expectedContainerPadding = 0)
+ }
+
+ @Test
+ fun testNotificationsMarginBottomIsUpdated() {
+ Mockito.clearInvocations(view)
+ enableSplitShade()
+ verify(view).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
+
+ overrideResource(R.dimen.notification_panel_margin_bottom, 100)
+ disableSplitShade()
+ verify(view).setNotificationsMarginBottom(100)
+ }
+
+ @Test
+ fun testSplitShadeLayout_isAlignedToGuideline() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
+ }
+
+ @Test
+ fun testSinglePaneLayout_childrenHaveEqualMargins() {
+ disableSplitShade()
+ underTest.updateResources()
+ val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
+ val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
+ assertThat(qsStartMargin == qsEndMargin).isTrue()
+ }
+
+ @Test
+ fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+ }
+
+ @Test
+ fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
+ enableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
+ }
+
+ @Test
+ fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
+ setLargeScreen()
+ val largeScreenHeaderHeight = 100
+ overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
+
+ underTest.updateResources()
+
+ assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
+ .isEqualTo(largeScreenHeaderHeight)
+ }
+
+ @Test
+ fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
+ setSmallScreen()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
+ }
+
+ @Test
+ fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
+ disableSplitShade()
+ underTest.updateResources()
+ val notificationPanelMarginHorizontal =
+ mContext.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
+ .isEqualTo(notificationPanelMarginHorizontal)
+ assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
+ .isEqualTo(notificationPanelMarginHorizontal)
+ }
+
+ @Test
+ fun testSinglePaneShadeLayout_isAlignedToParent() {
+ disableSplitShade()
+ underTest.updateResources()
+ assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
+ .isEqualTo(ConstraintSet.PARENT_ID)
+ }
+
+ @Test
+ fun testAllChildrenOfNotificationContainer_haveIds() {
+ // set dimen to 0 to avoid triggering updating bottom spacing
+ overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
+ val container = NotificationsQuickSettingsContainer(mContext, null)
+ container.removeAllViews()
+ container.addView(newViewWithId(1))
+ container.addView(newViewWithId(View.NO_ID))
+ val controller =
+ NotificationsQSContainerController(
+ container,
+ navigationModeController,
+ overviewProxyService,
+ shadeHeaderController,
+ shadeExpansionStateManager,
+ fragmentService,
+ delayableExecutor,
+ featureFlags,
+ notificationStackScrollLayoutController,
+ )
+ controller.updateConstraints()
+
+ assertThat(container.getChildAt(0).id).isEqualTo(1)
+ assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
+ }
+
+ @Test
+ fun testWindowInsetDebounce() {
+ disableSplitShade()
+
+ given(
+ taskbarVisible = false,
+ navigationMode = GESTURES_NAVIGATION,
+ insets = emptyInsets(),
+ applyImmediately = false
+ )
+ fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
+ windowInsetsCallback.accept(windowInsets().withStableBottom())
+
+ delayableExecutor.advanceClockToLast()
+ delayableExecutor.runAllReady()
+
+ verify(view, never()).setQSContainerPaddingBottom(0)
+ verify(view).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
+ }
+
+ @Test
+ fun testStartCustomizingWithDuration() {
+ underTest.setCustomizerShowing(true, 100L)
+ verify(shadeHeaderController).startCustomizingAnimation(true, 100L)
+ }
+
+ @Test
+ fun testEndCustomizingWithDuration() {
+ underTest.setCustomizerShowing(true, 0L) // Only tracks changes
+ reset(shadeHeaderController)
+
+ underTest.setCustomizerShowing(false, 100L)
+ verify(shadeHeaderController).startCustomizingAnimation(false, 100L)
+ }
+
+ @Test
+ fun testTagListenerAdded() {
+ verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(view))
+ }
+
+ @Test
+ fun testTagListenerRemoved() {
+ attachStateListenerCaptor.value.onViewDetachedFromWindow(view)
+ verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(view))
+ }
+
+ private fun disableSplitShade() {
+ setSplitShadeEnabled(false)
+ }
+
+ private fun enableSplitShade() {
+ setSplitShadeEnabled(true)
+ }
+
+ private fun setSplitShadeEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_split_notification_shade, enabled)
+ underTest.updateResources()
+ }
+
+ private fun setSmallScreen() {
+ setLargeScreenEnabled(false)
+ }
+
+ private fun setLargeScreen() {
+ setLargeScreenEnabled(true)
+ }
+
+ private fun setLargeScreenEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_large_screen_shade_header, enabled)
+ }
+
+ private fun given(
+ taskbarVisible: Boolean,
+ navigationMode: Int,
+ insets: WindowInsets,
+ applyImmediately: Boolean = true
+ ) {
+ Mockito.clearInvocations(view)
+ taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
+ navigationModeCallback.onNavigationModeChanged(navigationMode)
+ windowInsetsCallback.accept(insets)
+ if (applyImmediately) {
+ delayableExecutor.advanceClockToLast()
+ delayableExecutor.runAllReady()
+ }
+ }
+
+ fun then(
+ expectedContainerPadding: Int,
+ expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
+ expectedQsPadding: Int = 0
+ ) {
+ verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
+ verify(view).setNotificationsMarginBottom(expectedNotificationsMargin)
+ verify(view).setQSContainerPaddingBottom(expectedQsPadding)
+ Mockito.clearInvocations(view)
+ }
+
+ private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
+
+ private fun emptyInsets() = mock(WindowInsets::class.java)
+
+ private fun WindowInsets.withCutout(): WindowInsets {
+ whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+ return this
+ }
+
+ private fun WindowInsets.withStableBottom(): WindowInsets {
+ whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
+ return this
+ }
+
+ private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
+ return constraintSetCaptor.value.getConstraint(id).layout
+ }
+
+ private fun newViewWithId(id: Int): View {
+ val view = View(mContext)
+ view.id = id
+ val layoutParams =
+ ConstraintLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ // required as cloning ConstraintSet fails if view doesn't have layout params
+ view.layoutParams = layoutParams
+ return view
+ }
+
+ companion object {
+ const val STABLE_INSET_BOTTOM = 100
+ const val CUTOUT_HEIGHT = 50
+ const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+ const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ const val NOTIFICATIONS_MARGIN = 50
+ const val SCRIM_MARGIN = 10
+ const val FOOTER_ACTIONS_INSET = 2
+ const val FOOTER_ACTIONS_PADDING = 2
+ const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
+ const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
new file mode 100644
index 0000000..a544cad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+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.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DreamCoordinatorTest : SysuiTestCase() {
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var notifPipeline: NotifPipeline
+ @Mock private lateinit var filterListener: Pluggable.PluggableListener<NotifFilter>
+
+ private val keyguardRepository = FakeKeyguardRepository()
+ private var fakeEntry: NotificationEntry = NotificationEntryBuilder().build()
+ val testDispatcher = UnconfinedTestDispatcher()
+ val testScope = TestScope(testDispatcher)
+
+ private lateinit var filter: NotifFilter
+ private lateinit var statusBarListener: StatusBarStateController.StateListener
+ private lateinit var dreamCoordinator: DreamCoordinator
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ // Build the coordinator
+ dreamCoordinator =
+ DreamCoordinator(
+ statusBarStateController,
+ testScope.backgroundScope,
+ keyguardRepository
+ )
+
+ // Attach the pipeline and capture the listeners/filters that it registers
+ dreamCoordinator.attach(notifPipeline)
+
+ filter = withArgCaptor { verify(notifPipeline).addPreGroupFilter(capture()) }
+ filter.setInvalidationListener(filterListener)
+
+ statusBarListener = withArgCaptor {
+ verify(statusBarStateController).addCallback(capture())
+ }
+ }
+
+ @Test
+ fun hideNotifications_whenDreamingAndOnKeyguard() =
+ testScope.runTest {
+ // GIVEN we are on keyguard and not dreaming
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+ runCurrent()
+
+ // THEN notifications are not filtered out
+ verifyPipelinesNotInvalidated()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+
+ // WHEN dreaming starts and the active dream is hosted in lockscreen
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ runCurrent()
+
+ // THEN pipeline is notified and notifications should all be filtered out
+ verifyPipelinesInvalidated()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+ }
+
+ @Test
+ fun showNotifications_whenDreamingAndNotOnKeyguard() =
+ testScope.runTest {
+ // GIVEN we are on the keyguard and active dream is hosted in lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ runCurrent()
+
+ // THEN pipeline is notified and notifications are all filtered out
+ verifyPipelinesInvalidated()
+ clearPipelineInvocations()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+ // WHEN we are no longer on the keyguard
+ statusBarListener.onStateChanged(StatusBarState.SHADE)
+
+ // THEN pipeline is notified and notifications are not filtered out
+ verifyPipelinesInvalidated()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+
+ @Test
+ fun showNotifications_whenOnKeyguardAndNotDreaming() =
+ testScope.runTest {
+ // GIVEN we are on the keyguard and active dream is hosted in lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ runCurrent()
+
+ // THEN pipeline is notified and notifications are all filtered out
+ verifyPipelinesInvalidated()
+ clearPipelineInvocations()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+ // WHEN the lockscreen hosted dream stops
+ keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+ runCurrent()
+
+ // THEN pipeline is notified and notifications are not filtered out
+ verifyPipelinesInvalidated()
+ assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+
+ private fun verifyPipelinesInvalidated() {
+ verify(filterListener).onPluggableInvalidated(eq(filter), any())
+ }
+
+ private fun verifyPipelinesNotInvalidated() {
+ verify(filterListener, never()).onPluggableInvalidated(eq(filter), any())
+ }
+
+ private fun clearPipelineInvocations() {
+ clearInvocations(filterListener)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index d3e5816..daa45db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -426,23 +426,8 @@
}
@Test
- public void testShouldHeadsUp_oldWhen_flagDisabled() throws Exception {
- ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(false);
-
- NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
- entry.getSbn().getNotification().when = makeWhenHoursAgo(25);
-
- assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
-
- verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
- verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
- }
-
- @Test
public void testShouldHeadsUp_oldWhen_whenNow() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
@@ -455,7 +440,6 @@
@Test
public void testShouldHeadsUp_oldWhen_whenRecent() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
entry.getSbn().getNotification().when = makeWhenHoursAgo(13);
@@ -469,7 +453,6 @@
@Test
public void testShouldHeadsUp_oldWhen_whenZero() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
entry.getSbn().getNotification().when = 0L;
@@ -484,7 +467,6 @@
@Test
public void testShouldHeadsUp_oldWhen_whenNegative() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
entry.getSbn().getNotification().when = -1L;
@@ -498,7 +480,6 @@
@Test
public void testShouldHeadsUp_oldWhen_hasFullScreenIntent() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
long when = makeWhenHoursAgo(25);
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silent= */ false);
@@ -514,7 +495,6 @@
@Test
public void testShouldHeadsUp_oldWhen_isForegroundService() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
long when = makeWhenHoursAgo(25);
NotificationEntry entry = createFgsNotification(IMPORTANCE_HIGH);
@@ -530,7 +510,6 @@
@Test
public void testShouldNotHeadsUp_oldWhen() throws Exception {
ensureStateForHeadsUpWhenAwake();
- when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
long when = makeWhenHoursAgo(25);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt
new file mode 100644
index 0000000..d46763d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.text.PrecomputedText
+import android.text.TextPaint
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class TextPrecomputerTest : SysuiTestCase() {
+
+ private lateinit var textPrecomputer: TextPrecomputer
+
+ private lateinit var textView: TextView
+
+ @Before
+ fun before() {
+ textPrecomputer = object : TextPrecomputer {}
+ textView = TextView(mContext)
+ }
+
+ @Test
+ fun precompute_returnRunnable() {
+ // WHEN
+ val precomputeResult = textPrecomputer.precompute(textView, TEXT)
+
+ // THEN
+ assertThat(precomputeResult).isInstanceOf(Runnable::class.java)
+ }
+
+ @Test
+ fun precomputeRunnable_anyText_setPrecomputedText() {
+ // WHEN
+ textPrecomputer.precompute(textView, TEXT).run()
+
+ // THEN
+ assertThat(textView.text).isInstanceOf(PrecomputedText::class.java)
+ }
+
+ @Test
+ fun precomputeRunnable_differentPrecomputedTextConfig_notSetPrecomputedText() {
+ // GIVEN
+ val precomputedTextRunnable =
+ textPrecomputer.precompute(textView, TEXT, logException = false)
+
+ // WHEN
+ textView.textMetricsParams = PrecomputedText.Params.Builder(PAINT).build()
+ precomputedTextRunnable.run()
+
+ // THEN
+ assertThat(textView.text).isInstanceOf(String::class.java)
+ }
+
+ @Test
+ fun precomputeRunnable_nullText_setNull() {
+ // GIVEN
+ textView.text = TEXT
+ val precomputedTextRunnable = textPrecomputer.precompute(textView, null)
+
+ // WHEN
+ precomputedTextRunnable.run()
+
+ // THEN
+ assertThat(textView.text).isEqualTo("")
+ }
+
+ private companion object {
+ private val PAINT = TextPaint()
+ private const val TEXT = "Example Notification Test"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
new file mode 100644
index 0000000..7bbb094
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.stack.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SharedNotificationContainerInteractorTest : SysuiTestCase() {
+ private lateinit var configurationRepository: FakeConfigurationRepository
+ private lateinit var underTest: SharedNotificationContainerInteractor
+
+ @Before
+ fun setUp() {
+ configurationRepository = FakeConfigurationRepository()
+ underTest =
+ SharedNotificationContainerInteractor(
+ configurationRepository,
+ mContext,
+ )
+ }
+
+ @Test
+ fun validateConfigValues() = runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ overrideResource(R.bool.config_use_large_screen_shade_header, false)
+ overrideResource(R.dimen.notification_panel_margin_horizontal, 0)
+ overrideResource(R.dimen.notification_panel_margin_bottom, 10)
+ overrideResource(R.dimen.notification_panel_margin_top, 10)
+ overrideResource(R.dimen.large_screen_shade_header_height, 0)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.useSplitShade).isTrue()
+ assertThat(lastDimens.useLargeScreenHeader).isFalse()
+ assertThat(lastDimens.marginHorizontal).isEqualTo(0)
+ assertThat(lastDimens.marginBottom).isGreaterThan(0)
+ assertThat(lastDimens.marginTop).isGreaterThan(0)
+ assertThat(lastDimens.marginTopLargeScreen).isEqualTo(0)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
new file mode 100644
index 0000000..afd9954
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.notification.stack.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SharedNotificationContainerViewModelTest : SysuiTestCase() {
+ private lateinit var configurationRepository: FakeConfigurationRepository
+ private lateinit var sharedNotificationContainerInteractor:
+ SharedNotificationContainerInteractor
+ private lateinit var underTest: SharedNotificationContainerViewModel
+
+ @Before
+ fun setUp() {
+ configurationRepository = FakeConfigurationRepository()
+ sharedNotificationContainerInteractor =
+ SharedNotificationContainerInteractor(
+ configurationRepository,
+ mContext,
+ )
+ underTest = SharedNotificationContainerViewModel(sharedNotificationContainerInteractor)
+ }
+
+ @Test
+ fun validateMarginStartInSplitShade() = runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginStart).isEqualTo(0)
+ }
+
+ @Test
+ fun validateMarginStart() = runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginStart).isEqualTo(20)
+ }
+
+ @Test
+ fun validateMarginEnd() = runTest {
+ overrideResource(R.dimen.notification_panel_margin_horizontal, 50)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginEnd).isEqualTo(50)
+ }
+
+ @Test
+ fun validateMarginBottom() = runTest {
+ overrideResource(R.dimen.notification_panel_margin_bottom, 50)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginBottom).isEqualTo(50)
+ }
+
+ @Test
+ fun validateMarginTopWithLargeScreenHeader() = runTest {
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.large_screen_shade_header_height, 50)
+ overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginTop).isEqualTo(50)
+ }
+
+ @Test
+ fun validateMarginTop() = runTest {
+ overrideResource(R.bool.config_use_large_screen_shade_header, false)
+ overrideResource(R.dimen.large_screen_shade_header_height, 50)
+ overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+ val dimens = collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+ runCurrent()
+
+ val lastDimens = dimens()!!
+
+ assertThat(lastDimens.marginTop).isEqualTo(0)
+ }
+}
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 89f8bdb..479803e 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
@@ -142,7 +142,8 @@
mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
mAuthController, mStatusBarStateController,
mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
- mSystemClock
+ mSystemClock,
+ mStatusBarKeyguardViewManager
);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mBiometricUnlockController.addListener(mBiometricUnlockEventsListener);
@@ -464,6 +465,69 @@
}
@Test
+ public void onSideFingerprintSuccess_dreaming_unlockThenWake() {
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+ when(mWakefulnessLifecycle.getLastWakeReason())
+ .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
+ final ArgumentCaptor<Runnable> afterKeyguardGoneRunnableCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+ givenDreamingLocked();
+ mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true);
+
+ // Make sure the BiometricUnlockController has registered a callback for when the keyguard
+ // is gone
+ verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(
+ afterKeyguardGoneRunnableCaptor.capture());
+ // Ensure that the power hasn't been told to wake up yet.
+ verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+ // Check that the keyguard has been told to unlock.
+ verify(mKeyguardViewMediator).onWakeAndUnlocking();
+
+ // Simulate the keyguard disappearing.
+ afterKeyguardGoneRunnableCaptor.getValue().run();
+ // Verify that the power manager has been told to wake up now.
+ verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
+ }
+
+ @Test
+ public void onSideFingerprintSuccess_dreaming_unlockIfStillLockedNotDreaming() {
+ when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+ when(mWakefulnessLifecycle.getLastWakeReason())
+ .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
+ final ArgumentCaptor<Runnable> afterKeyguardGoneRunnableCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+ givenDreamingLocked();
+ mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true);
+
+ // Make sure the BiometricUnlockController has registered a callback for when the keyguard
+ // is gone
+ verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(
+ afterKeyguardGoneRunnableCaptor.capture());
+ // Ensure that the power hasn't been told to wake up yet.
+ verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+ // Check that the keyguard has been told to unlock.
+ verify(mKeyguardViewMediator).onWakeAndUnlocking();
+
+ when(mUpdateMonitor.isDreaming()).thenReturn(false);
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+ // Simulate the keyguard disappearing.
+ afterKeyguardGoneRunnableCaptor.getValue().run();
+
+ final ArgumentCaptor<Runnable> dismissKeyguardRunnableCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+ verify(mHandler).post(dismissKeyguardRunnableCaptor.capture());
+
+ // Verify that the power manager was not told to wake up.
+ verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+
+ dismissKeyguardRunnableCaptor.getValue().run();
+ // Verify that the keyguard controller is told to unlock.
+ verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+ }
+
+
+ @Test
public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() {
// GIVEN side fingerprint enrolled, last wake reason was power button
when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
@@ -537,6 +601,11 @@
verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
}
+ private void givenDreamingLocked() {
+ when(mUpdateMonitor.isDreaming()).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ }
+
private void givenFingerprintModeUnlockCollapsing() {
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
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 3d35233..5300851 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
@@ -129,7 +129,6 @@
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelView;
import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
@@ -252,7 +251,6 @@
@Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private StatusBarSignalPolicy mStatusBarSignalPolicy;
- @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private AssistManager mAssistManager;
@Mock private NotificationGutsManager mNotificationGutsManager;
@@ -276,6 +274,8 @@
@Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@Mock private NotificationIconAreaController mNotificationIconAreaController;
@Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ @Mock private Lazy<NotificationShadeWindowViewController>
+ mNotificationShadeWindowViewControllerLazy;
@Mock private NotificationShelfController mNotificationShelfController;
@Mock private DozeParameters mDozeParameters;
@Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
@@ -428,10 +428,10 @@
when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher);
+ when(mNotificationShadeWindowViewControllerLazy.get())
+ .thenReturn(mNotificationShadeWindowViewController);
when(mStatusBarComponentFactory.create()).thenReturn(mCentralSurfacesComponent);
- when(mCentralSurfacesComponent.getNotificationShadeWindowViewController()).thenReturn(
- mNotificationShadeWindowViewController);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
@@ -510,6 +510,7 @@
() -> mAssistManager,
configurationController,
mNotificationShadeWindowController,
+ mNotificationShadeWindowViewControllerLazy,
mNotificationShelfController,
mStackScrollerController,
mDozeParameters,
@@ -586,9 +587,8 @@
when(mKeyguardViewMediator.getViewMediatorCallback()).thenReturn(
mKeyguardVieMediatorCallback);
- // TODO: we should be able to call mCentralSurfaces.start() and have all the below values
- // initialized automatically and make NPVC private.
- mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView;
+ // TODO(b/277764509): we should be able to call mCentralSurfaces.start() and have all the
+ // below values initialized automatically.
mCentralSurfaces.mDozeScrimController = mDozeScrimController;
mCentralSurfaces.mPresenter = mNotificationPresenter;
mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController;
@@ -823,8 +823,6 @@
*/
@Test
public void testPredictiveBackCallback_invocationCollapsesPanel() {
- mCentralSurfaces.setNotificationShadeWindowViewController(
- mNotificationShadeWindowViewController);
mCentralSurfaces.handleVisibleToUserChanged(true);
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
@@ -841,8 +839,6 @@
*/
@Test
public void testPredictiveBackAnimation_progressMaxScalesPanel() {
- mCentralSurfaces.setNotificationShadeWindowViewController(
- mNotificationShadeWindowViewController);
mCentralSurfaces.handleVisibleToUserChanged(true);
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
@@ -864,8 +860,6 @@
*/
@Test
public void testPredictiveBackAnimation_progressMinScalesPanel() {
- mCentralSurfaces.setNotificationShadeWindowViewController(
- mNotificationShadeWindowViewController);
mCentralSurfaces.handleVisibleToUserChanged(true);
verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 6a4b3c5..df3c1e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -22,7 +22,6 @@
import static junit.framework.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -45,8 +44,6 @@
import com.android.internal.view.AppearanceRegion;
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.navigationbar.NavigationModeController;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -65,20 +62,13 @@
private static final GradientColors COLORS_LIGHT = makeColors(Color.WHITE);
private static final GradientColors COLORS_DARK = makeColors(Color.BLACK);
- private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
private LightBarTransitionsController mLightBarTransitionsController;
private LightBarTransitionsController mNavBarController;
private SysuiDarkIconDispatcher mStatusBarIconController;
private LightBarController mLightBarController;
- /** Allow testing with NEW_LIGHT_BAR_LOGIC flag in different states */
- protected boolean testNewLightBarLogic() {
- return false;
- }
-
@Before
public void setup() {
- mFeatureFlags.set(Flags.NEW_LIGHT_BAR_LOGIC, testNewLightBarLogic());
mStatusBarIconController = mock(SysuiDarkIconDispatcher.class);
mNavBarController = mock(LightBarTransitionsController.class);
when(mNavBarController.supportsIconTintForNavMode(anyInt())).thenReturn(true);
@@ -90,7 +80,6 @@
mStatusBarIconController,
mock(BatteryController.class),
mock(NavigationModeController.class),
- mFeatureFlags,
mock(DumpManager.class),
new FakeDisplayTracker(mContext));
}
@@ -211,8 +200,6 @@
@Test
public void validateNavBarChangesUpdateIcons() {
- assumeTrue(testNewLightBarLogic()); // Only run in the new suite
-
// On the launcher in dark mode buttons are light
mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_DARK);
mLightBarController.onNavigationBarAppearanceChanged(
@@ -251,8 +238,6 @@
@Test
public void navBarHasDarkIconsInLockedShade_lightMode() {
- assumeTrue(testNewLightBarLogic()); // Only run in the new suite
-
// On the locked shade QS in light mode buttons are light
mLightBarController.setScrimState(ScrimState.SHADE_LOCKED, 1f, COLORS_LIGHT);
mLightBarController.onNavigationBarAppearanceChanged(
@@ -287,8 +272,6 @@
@Test
public void navBarHasLightIconsInLockedShade_darkMode() {
- assumeTrue(testNewLightBarLogic()); // Only run in the new suite
-
// On the locked shade QS in light mode buttons are light
mLightBarController.setScrimState(ScrimState.SHADE_LOCKED, 1f, COLORS_DARK);
mLightBarController.onNavigationBarAppearanceChanged(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt
deleted file mode 100644
index d9c2cfa..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerWithNewLogicTest.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.phone
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.flags.Flags.NEW_LIGHT_BAR_LOGIC
-
-/**
- * This file only needs to live as long as [NEW_LIGHT_BAR_LOGIC] does. When we delete that flag, we
- * can roll this back into the old test.
- */
-@SmallTest
-class LightBarControllerWithNewLogicTest : LightBarControllerTest() {
- override fun testNewLightBarLogic(): Boolean = true
-}
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 2d96e59..c8ec1bf 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
@@ -204,6 +204,7 @@
userChipViewModel,
centralSurfacesImpl,
shadeControllerImpl,
+ shadeViewController,
shadeLogger,
viewUtil,
configurationController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 0244e7e..0dc1d9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -65,7 +65,6 @@
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.KeyguardState;
@@ -150,7 +149,6 @@
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- @Mock private FeatureFlags mFeatureFlags;
private static class AnimatorListener implements Animator.AnimatorListener {
private int mNumStarts;
@@ -290,8 +288,7 @@
mKeyguardTransitionInteractor,
mWallpaperRepository,
mMainDispatcher,
- mLinearLargeScreenShadeInterpolator,
- mFeatureFlags);
+ mLinearLargeScreenShadeInterpolator);
mScrimController.start();
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
@@ -998,8 +995,7 @@
mKeyguardTransitionInteractor,
mWallpaperRepository,
mMainDispatcher,
- mLinearLargeScreenShadeInterpolator,
- mFeatureFlags);
+ mLinearLargeScreenShadeInterpolator);
mScrimController.start();
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 5bd6ff4..cd8aaa2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -42,7 +42,6 @@
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeNotificationPresenter;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -52,7 +51,6 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
@@ -86,14 +84,11 @@
mock(NotificationsInteractor.class);
private final KeyguardStateController mKeyguardStateController =
mock(KeyguardStateController.class);
- private final NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
private final InitController mInitController = new InitController();
@Before
public void setup() {
mMetricsLogger = new FakeMetricsLogger();
- LockscreenGestureLogger lockscreenGestureLogger = new LockscreenGestureLogger(
- mMetricsLogger);
mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext));
mDependency.injectTestDependency(StatusBarStateController.class,
mock(SysuiStatusBarStateController.class));
@@ -111,8 +106,6 @@
when(notificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
ShadeViewController shadeViewController = mock(ShadeViewController.class);
- when(shadeViewController.getShadeNotificationPresenter())
- .thenReturn(mock(ShadeNotificationPresenter.class));
mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
mContext,
shadeViewController,
@@ -135,11 +128,9 @@
mock(NotifShadeEventSource.class),
mock(NotificationMediaManager.class),
mock(NotificationGutsManager.class),
- lockscreenGestureLogger,
mInitController,
mNotificationInterruptStateProvider,
mock(NotificationRemoteInputManager.class),
- mNotifPipelineFlags,
mock(NotificationRemoteInputManager.Callback.class),
mock(NotificationListContainer.class));
mInitController.executePostInitTasks();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
index d8e418a..b13cb72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt
@@ -26,6 +26,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.eq
+import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -56,6 +57,7 @@
private lateinit var viewRootImpl: ViewRootImpl
@Mock
private lateinit var windowToken: IBinder
+ private val wallpaperRepository = FakeWallpaperRepository()
@JvmField
@Rule
@@ -69,7 +71,7 @@
`when`(root.windowToken).thenReturn(windowToken)
`when`(root.isAttachedToWindow).thenReturn(true)
- wallaperController = WallpaperController(wallpaperManager)
+ wallaperController = WallpaperController(wallpaperManager, wallpaperRepository)
wallaperController.rootView = root
}
@@ -90,9 +92,9 @@
@Test
fun setUnfoldTransitionZoom_defaultUnfoldTransitionIsDisabled_doesNotUpdateWallpaperZoom() {
- wallaperController.onWallpaperInfoUpdated(createWallpaperInfo(
+ wallpaperRepository.wallpaperInfo.value = createWallpaperInfo(
useDefaultTransition = false
- ))
+ )
wallaperController.setUnfoldTransitionZoom(0.5f)
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 0c77529..c819108 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.volume;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
@@ -51,6 +52,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialogController;
@@ -117,6 +119,8 @@
}
};
+ private FakeFeatureFlags mFeatureFlags;
+
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -132,6 +136,8 @@
mConfigurationController = new FakeConfigurationController();
+ mFeatureFlags = new FakeFeatureFlags();
+
mDialog = new VolumeDialogImpl(
getContext(),
mVolumeDialogController,
@@ -146,7 +152,8 @@
mCsdWarningDialogFactory,
mPostureController,
mTestableLooper.getLooper(),
- mDumpManager);
+ mDumpManager,
+ mFeatureFlags);
mDialog.init(0, null);
State state = createShellState();
mDialog.onStateChangedH(state);
@@ -254,6 +261,7 @@
@Test
public void testVibrateOnRingerChangedToVibrate() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
final State initialSilentState = new State();
initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
@@ -274,7 +282,30 @@
}
@Test
+ public void testControllerDoesNotVibrateOnRingerChangedToVibrate_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+ final State initialSilentState = new State();
+ initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT;
+
+ final State vibrateState = new State();
+ vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+
+ // change ringer to silent
+ mDialog.onStateChangedH(initialSilentState);
+
+ // expected: shouldn't call vibrate yet
+ verify(mVolumeDialogController, never()).vibrate(any());
+
+ // changed ringer to vibrate
+ mDialog.onStateChangedH(vibrateState);
+
+ // expected: vibrate method of controller is not used
+ verify(mVolumeDialogController, never()).vibrate(any());
+ }
+
+ @Test
public void testNoVibrateOnRingerInitialization() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = -1;
@@ -292,7 +323,42 @@
}
@Test
+ public void testControllerDoesNotVibrateOnRingerInitialization_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = -1;
+
+ // ringer not initialized yet:
+ mDialog.onStateChangedH(initialUnsetState);
+
+ final State vibrateState = new State();
+ vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+
+ // changed ringer to vibrate
+ mDialog.onStateChangedH(vibrateState);
+
+ // shouldn't call vibrate on the controller either
+ verify(mVolumeDialogController, never()).vibrate(any());
+ }
+
+ @Test
public void testSelectVibrateFromDrawer() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
+ mDialog.onStateChangedH(initialUnsetState);
+
+ mActiveRinger.performClick();
+ mDrawerVibrate.performClick();
+
+ // Make sure we've actually changed the ringer mode.
+ verify(mVolumeDialogController, times(1)).setRingerMode(
+ AudioManager.RINGER_MODE_VIBRATE, false);
+ }
+
+ @Test
+ public void testSelectVibrateFromDrawer_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
mDialog.onStateChangedH(initialUnsetState);
@@ -307,6 +373,22 @@
@Test
public void testSelectMuteFromDrawer() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
+ mDialog.onStateChangedH(initialUnsetState);
+
+ mActiveRinger.performClick();
+ mDrawerMute.performClick();
+
+ // Make sure we've actually changed the ringer mode.
+ verify(mVolumeDialogController, times(1)).setRingerMode(
+ AudioManager.RINGER_MODE_SILENT, false);
+ }
+
+ @Test
+ public void testSelectMuteFromDrawer_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL;
mDialog.onStateChangedH(initialUnsetState);
@@ -321,6 +403,22 @@
@Test
public void testSelectNormalFromDrawer() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+ final State initialUnsetState = new State();
+ initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
+ mDialog.onStateChangedH(initialUnsetState);
+
+ mActiveRinger.performClick();
+ mDrawerNormal.performClick();
+
+ // Make sure we've actually changed the ringer mode.
+ verify(mVolumeDialogController, times(1)).setRingerMode(
+ AudioManager.RINGER_MODE_NORMAL, false);
+ }
+
+ @Test
+ public void testSelectNormalFromDrawer_OnewayAPI_On() {
+ mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
final State initialUnsetState = new State();
initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE;
mDialog.onStateChangedH(initialUnsetState);
@@ -383,7 +481,8 @@
mCsdWarningDialogFactory,
devicePostureController,
mTestableLooper.getLooper(),
- mDumpManager
+ mDumpManager,
+ mFeatureFlags
);
dialog.init(0 , null);
@@ -423,7 +522,8 @@
mCsdWarningDialogFactory,
devicePostureController,
mTestableLooper.getLooper(),
- mDumpManager
+ mDumpManager,
+ mFeatureFlags
);
dialog.init(0, null);
@@ -462,7 +562,8 @@
mCsdWarningDialogFactory,
devicePostureController,
mTestableLooper.getLooper(),
- mDumpManager
+ mDumpManager,
+ mFeatureFlags
);
dialog.init(0, null);
@@ -503,7 +604,9 @@
mCsdWarningDialogFactory,
mPostureController,
mTestableLooper.getLooper(),
- mDumpManager);
+ mDumpManager,
+ mFeatureFlags
+ );
dialog.init(0, null);
verify(mPostureController, never()).removeCallback(any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
index 6fc36b0..fe5024f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/FakeWallpaperRepository.kt
@@ -16,9 +16,11 @@
package com.android.systemui.wallpapers.data.repository
+import android.app.WallpaperInfo
import kotlinx.coroutines.flow.MutableStateFlow
/** Fake implementation of the wallpaper repository. */
class FakeWallpaperRepository : WallpaperRepository {
+ override val wallpaperInfo = MutableStateFlow<WallpaperInfo?>(null)
override val wallpaperSupportsAmbientMode = MutableStateFlow(false)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
index 132b9b4..f8b096a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
@@ -65,6 +65,171 @@
}
@Test
+ fun wallpaperInfo_nullInfo() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(null)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun wallpaperInfo_hasInfoFromManager() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
+
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ assertThat(latest).isEqualTo(UNSUPPORTED_WP)
+ }
+
+ @Test
+ fun wallpaperInfo_initialValueIsFetched() =
+ testScope.runTest {
+ whenever(wallpaperManager.getWallpaperInfoForUser(USER_WITH_SUPPORTED_WP.id))
+ .thenReturn(SUPPORTED_WP)
+ userRepository.setUserInfos(listOf(USER_WITH_SUPPORTED_WP))
+ userRepository.setSelectedUserInfo(USER_WITH_SUPPORTED_WP)
+
+ // WHEN the repo initially starts up (underTest is lazy), then it fetches the current
+ // value for the wallpaper
+ assertThat(underTest.wallpaperInfo.value).isEqualTo(SUPPORTED_WP)
+ }
+
+ @Test
+ fun wallpaperInfo_updatesOnUserChanged() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ val user3 = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+ val user3Wp = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(user3.id)).thenReturn(user3Wp)
+
+ val user4 = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+ val user4Wp = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(user4.id)).thenReturn(user4Wp)
+
+ userRepository.setUserInfos(listOf(user3, user4))
+
+ // WHEN user3 is selected
+ userRepository.setSelectedUserInfo(user3)
+
+ // THEN user3's wallpaper is used
+ assertThat(latest).isEqualTo(user3Wp)
+
+ // WHEN the user is switched to user4
+ userRepository.setSelectedUserInfo(user4)
+
+ // THEN user4's wallpaper is used
+ assertThat(latest).isEqualTo(user4Wp)
+ }
+
+ @Test
+ fun wallpaperInfo_doesNotUpdateOnUserChanging() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ val user3 = UserInfo(/* id= */ 3, /* name= */ "user3", /* flags= */ 0)
+ val user3Wp = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(user3.id)).thenReturn(user3Wp)
+
+ val user4 = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
+ val user4Wp = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(user4.id)).thenReturn(user4Wp)
+
+ userRepository.setUserInfos(listOf(user3, user4))
+
+ // WHEN user3 is selected
+ userRepository.setSelectedUserInfo(user3)
+
+ // THEN user3's wallpaper is used
+ assertThat(latest).isEqualTo(user3Wp)
+
+ // WHEN the user has started switching to user4 but hasn't finished yet
+ userRepository.selectedUser.value =
+ SelectedUserModel(user4, SelectionStatus.SELECTION_IN_PROGRESS)
+
+ // THEN the wallpaper still matches user3
+ assertThat(latest).isEqualTo(user3Wp)
+ }
+
+ @Test
+ fun wallpaperInfo_updatesOnIntent() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wallpaperInfo)
+
+ val wp1 = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+
+ assertThat(latest).isEqualTo(wp1)
+
+ // WHEN the info is new and a broadcast is sent
+ val wp2 = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp2)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the flow updates
+ assertThat(latest).isEqualTo(wp2)
+ }
+
+ @Test
+ fun wallpaperInfo_wallpaperNotSupported_alwaysNull() =
+ testScope.runTest {
+ whenever(wallpaperManager.isWallpaperSupported).thenReturn(false)
+
+ val latest by collectLastValue(underTest.wallpaperInfo)
+ assertThat(latest).isNull()
+
+ // Even WHEN there *is* current wallpaper
+ val wp1 = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the value is still null because wallpaper isn't supported
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun wallpaperInfo_deviceDoesNotSupportAmbientWallpaper_alwaysFalse() =
+ testScope.runTest {
+ context.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dozeSupportsAodWallpaper,
+ false
+ )
+
+ val latest by collectLastValue(underTest.wallpaperInfo)
+ assertThat(latest).isNull()
+
+ // Even WHEN there *is* current wallpaper
+ val wp1 = mock<WallpaperInfo>()
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(wp1)
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_WALLPAPER_CHANGED),
+ )
+
+ // THEN the value is still null because wallpaper isn't supported
+ assertThat(latest).isNull()
+ }
+
+ @Test
fun wallpaperSupportsAmbientMode_nullInfo_false() =
testScope.runTest {
val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
@@ -190,14 +355,12 @@
testScope.runTest {
val latest by collectLastValue(underTest.wallpaperSupportsAmbientMode)
- val info: WallpaperInfo = mock()
- whenever(info.supportsAmbientMode()).thenReturn(false)
- whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(info)
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
assertThat(latest).isFalse()
// WHEN the info now supports ambient mode and a broadcast is sent
- whenever(info.supportsAmbientMode()).thenReturn(true)
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(SUPPORTED_WP)
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
Intent(Intent.ACTION_WALLPAPER_CHANGED),
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 28892ba..c2e1ac7 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
@@ -31,9 +31,6 @@
private val currentTime: () -> Long,
) : AuthenticationRepository {
- private val _isBypassEnabled = MutableStateFlow(false)
- override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled
-
private val _isAutoConfirmEnabled = MutableStateFlow(false)
override val isAutoConfirmEnabled: StateFlow<Boolean> = _isAutoConfirmEnabled.asStateFlow()
@@ -85,10 +82,6 @@
return (credentialOverride ?: DEFAULT_PIN).size
}
- override fun setBypassEnabled(isBypassEnabled: Boolean) {
- _isBypassEnabled.value = isBypassEnabled
- }
-
override suspend fun getFailedAuthenticationAttemptCount(): Int {
return failedAttemptCount
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index 2362a52..0c5e438 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -20,16 +20,12 @@
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeFingerprintPropertyRepository : FingerprintPropertyRepository {
- private val _isInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val isInitialized = _isInitialized.asStateFlow()
-
private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
- override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
+ override val sensorId = _sensorId.asStateFlow()
private val _strength: MutableStateFlow<SensorStrength> =
MutableStateFlow(SensorStrength.CONVENIENCE)
@@ -37,12 +33,11 @@
private val _sensorType: MutableStateFlow<FingerprintSensorType> =
MutableStateFlow(FingerprintSensorType.UNKNOWN)
- override val sensorType: StateFlow<FingerprintSensorType> = _sensorType.asStateFlow()
+ override val sensorType = _sensorType.asStateFlow()
private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
- override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
- _sensorLocations.asStateFlow()
+ override val sensorLocations = _sensorLocations.asStateFlow()
fun setProperties(
sensorId: Int,
@@ -54,6 +49,5 @@
_strength.value = strength
_sensorType.value = sensorType
_sensorLocations.value = sensorLocations
- _isInitialized.value = true
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index 2715aaa..548169e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -17,8 +17,8 @@
package com.android.systemui.keyguard.data.repository
import com.android.keyguard.FaceAuthUiEvent
-import com.android.systemui.keyguard.shared.model.AuthenticationStatus
-import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -29,16 +29,16 @@
override val isAuthenticated = MutableStateFlow(false)
override val canRunFaceAuth = MutableStateFlow(false)
- private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null)
- override val authenticationStatus: Flow<AuthenticationStatus> =
+ private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null)
+ override val authenticationStatus: Flow<FaceAuthenticationStatus> =
_authenticationStatus.filterNotNull()
- fun setAuthenticationStatus(status: AuthenticationStatus) {
+ fun setAuthenticationStatus(status: FaceAuthenticationStatus) {
_authenticationStatus.value = status
}
- private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
- override val detectionStatus: Flow<DetectionStatus>
+ private val _detectionStatus = MutableStateFlow<FaceDetectionStatus?>(null)
+ override val detectionStatus: Flow<FaceDetectionStatus>
get() = _detectionStatus.filterNotNull()
- fun setDetectionStatus(status: DetectionStatus) {
+ fun setDetectionStatus(status: FaceDetectionStatus) {
_detectionStatus.value = status
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
index 4bfd3d6..38791ca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -17,32 +17,38 @@
package com.android.systemui.keyguard.data.repository
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository {
private val _isLockedOut = MutableStateFlow(false)
override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow()
-
- private val _isRunning = MutableStateFlow(false)
- override val isRunning: Flow<Boolean>
- get() = _isRunning
-
- private var fpSensorType = MutableStateFlow<BiometricType?>(null)
- override val availableFpSensorType: Flow<BiometricType?>
- get() = fpSensorType
-
fun setLockedOut(lockedOut: Boolean) {
_isLockedOut.value = lockedOut
}
+ private val _isRunning = MutableStateFlow(false)
+ override val isRunning: Flow<Boolean>
+ get() = _isRunning
fun setIsRunning(value: Boolean) {
_isRunning.value = value
}
+ private var fpSensorType = MutableStateFlow<BiometricType?>(null)
+ override val availableFpSensorType: Flow<BiometricType?>
+ get() = fpSensorType
fun setAvailableFpSensorType(value: BiometricType?) {
fpSensorType.value = value
}
+
+ private var _authenticationStatus = MutableStateFlow<FingerprintAuthenticationStatus?>(null)
+ override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
+ get() = _authenticationStatus.filterNotNull()
+ fun setAuthenticationStatus(status: FingerprintAuthenticationStatus) {
+ _authenticationStatus.value = status
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 6309740..8428566 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -22,11 +22,14 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.ScreenModel
+import com.android.systemui.keyguard.shared.model.ScreenState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakeSleepReason
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -56,6 +59,9 @@
private val _isDozing = MutableStateFlow(false)
override val isDozing: StateFlow<Boolean> = _isDozing
+ private val _dozeTimeTick = MutableSharedFlow<Unit>()
+ override val dozeTimeTick = _dozeTimeTick
+
private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
@@ -86,6 +92,9 @@
)
override val wakefulness = _wakefulnessModel
+ private val _screenModel = MutableStateFlow(ScreenModel(ScreenState.SCREEN_OFF))
+ override val screenModel = _screenModel
+
private val _isUdfpsSupported = MutableStateFlow(false)
private val _isKeyguardGoingAway = MutableStateFlow(false)
@@ -114,6 +123,11 @@
return _isKeyguardShowing.value
}
+ private var _isBypassEnabled = false
+ override fun isBypassEnabled(): Boolean {
+ return _isBypassEnabled
+ }
+
override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.tryEmit(animate)
}
@@ -142,6 +156,10 @@
_isDozing.value = isDozing
}
+ override fun dozeTimeTick() {
+ _dozeTimeTick.tryEmit(Unit)
+ }
+
override fun setLastDozeTapToWakePosition(position: Point) {
_lastDozeTapToWakePosition.value = position
}
@@ -198,6 +216,10 @@
_isKeyguardUnlocked.value = isUnlocked
}
+ fun setBypassEnabled(isEnabled: Boolean) {
+ _isBypassEnabled = isEnabled
+ }
+
override fun isUdfpsSupported(): Boolean {
return _isUdfpsSupported.value
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 47e1daf4..9317981 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -142,6 +142,7 @@
repository = repository,
backgroundDispatcher = testDispatcher,
userRepository = userRepository,
+ keyguardRepository = keyguardRepository,
clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
)
}
diff --git a/services/Android.bp b/services/Android.bp
index 7b64b47..53dc068 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -164,6 +164,7 @@
"services.coverage",
"services.credentials",
"services.devicepolicy",
+ "services.flags",
"services.midi",
"services.musicsearch",
"services.net",
diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java
index 7c3a75f..5a15f17 100644
--- a/services/core/java/com/android/server/MasterClearReceiver.java
+++ b/services/core/java/com/android/server/MasterClearReceiver.java
@@ -49,6 +49,7 @@
private static final String TAG = "MasterClear";
private boolean mWipeExternalStorage;
private boolean mWipeEsims;
+ private boolean mShowWipeProgress = true;
@Override
public void onReceive(final Context context, final Intent intent) {
@@ -77,8 +78,12 @@
return;
}
- final boolean shutdown = intent.getBooleanExtra("shutdown", false);
final String reason = intent.getStringExtra(Intent.EXTRA_REASON);
+
+ // Factory reset dialog has its own UI for the reset process, so suppress ours if indicated.
+ mShowWipeProgress = intent.getBooleanExtra(Intent.EXTRA_SHOW_WIPE_PROGRESS, true);
+
+ final boolean shutdown = intent.getBooleanExtra("shutdown", false);
mWipeExternalStorage = intent.getBooleanExtra(Intent.EXTRA_WIPE_EXTERNAL_STORAGE, false);
mWipeEsims = intent.getBooleanExtra(Intent.EXTRA_WIPE_ESIMS, false);
final boolean forceWipe = intent.getBooleanExtra(Intent.EXTRA_FORCE_MASTER_CLEAR, false)
@@ -190,15 +195,19 @@
public WipeDataTask(Context context, Thread chainedTask) {
mContext = context;
mChainedTask = chainedTask;
- mProgressDialog = new ProgressDialog(context, R.style.Theme_DeviceDefault_System);
+ mProgressDialog = mShowWipeProgress
+ ? new ProgressDialog(context, R.style.Theme_DeviceDefault_System)
+ : null;
}
@Override
protected void onPreExecute() {
- mProgressDialog.setIndeterminate(true);
- mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
- mProgressDialog.setMessage(mContext.getText(R.string.progress_erasing));
- mProgressDialog.show();
+ if (mProgressDialog != null) {
+ mProgressDialog.setIndeterminate(true);
+ mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ mProgressDialog.setMessage(mContext.getText(R.string.progress_erasing));
+ mProgressDialog.show();
+ }
}
@Override
@@ -214,7 +223,9 @@
@Override
protected void onPostExecute(Void result) {
- mProgressDialog.dismiss();
+ if (mProgressDialog != null && mProgressDialog.isShowing()) {
+ mProgressDialog.dismiss();
+ }
mChainedTask.start();
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index ecc0aa5..830d55a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1269,6 +1269,21 @@
}
}
+ /**
+ * This method checks if the volume is public and the volume is visible and the volume it is
+ * trying to mount doesn't have the same mount user id as the current user being maintained by
+ * StorageManagerService and change the mount Id. The checks are same as
+ * {@link StorageManagerService#maybeRemountVolumes(int)}
+ * @param VolumeInfo object to consider for changing the mountId
+ */
+ private void updateVolumeMountIdIfRequired(VolumeInfo vol) {
+ synchronized (mLock) {
+ if (!vol.isPrimary() && vol.isVisible() && vol.getMountUserId() != mCurrentUserId) {
+ vol.mountUserId = mCurrentUserId;
+ }
+ }
+ }
+
private boolean supportsBlockCheckpoint() throws RemoteException {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
return mVold.supportsBlockCheckpoint();
@@ -1382,13 +1397,14 @@
}
@Override
- public void onVolumeStateChanged(String volId, final int newState) {
+ public void onVolumeStateChanged(String volId, final int newState, final int userId) {
synchronized (mLock) {
final VolumeInfo vol = mVolumes.get(volId);
if (vol != null) {
final int oldState = vol.state;
vol.state = newState;
final VolumeInfo vInfo = new VolumeInfo(vol);
+ vInfo.mountUserId = userId;
final SomeArgs args = SomeArgs.obtain();
args.arg1 = vInfo;
args.argi1 = oldState;
@@ -2232,7 +2248,7 @@
if (isMountDisallowed(vol)) {
throw new SecurityException("Mounting " + volId + " restricted by policy");
}
-
+ updateVolumeMountIdIfRequired(vol);
mount(vol);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2f7c890..d199006 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -256,7 +256,7 @@
public final class ActiveServices {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActiveServices" : TAG_AM;
private static final String TAG_MU = TAG + POSTFIX_MU;
- private static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
+ static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
private static final String TAG_SERVICE_EXECUTING = TAG + POSTFIX_SERVICE_EXECUTING;
private static final boolean DEBUG_DELAYED_SERVICE = DEBUG_SERVICE;
@@ -850,8 +850,7 @@
// Service.startForeground()), at that point we will consult the BFSL check and the timeout
// and make the necessary decisions.
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, r, userId,
- backgroundStartPrivileges, false /* isBindService */,
- !fgRequired /* isStartService */);
+ backgroundStartPrivileges, false /* isBindService */);
if (!mAm.mUserController.exists(r.userId)) {
Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
@@ -894,7 +893,7 @@
if (fgRequired) {
logFgsBackgroundStart(r);
- if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) {
+ if (!r.isFgsAllowedStart() && isBgFgsRestrictionEnabled(r)) {
String msg = "startForegroundService() not allowed due to "
+ "mAllowStartForeground false: service "
+ r.shortInstanceName;
@@ -1060,7 +1059,7 @@
// Use that as a shortcut if possible to avoid having to recheck all the conditions.
final boolean whileInUseAllowsUiJobScheduling =
ActivityManagerService.doesReasonCodeAllowSchedulingUserInitiatedJobs(
- r.mAllowWhileInUsePermissionInFgsReason);
+ r.getFgsAllowWIU());
r.updateAllowUiJobScheduling(whileInUseAllowsUiJobScheduling
|| mAm.canScheduleUserInitiatedJobs(callingUid, callingPid, callingPackage));
} else {
@@ -2178,12 +2177,12 @@
// on a SHORT_SERVICE FGS.
// See if the app could start an FGS or not.
- r.mAllowStartForeground = REASON_DENIED;
+ r.clearFgsAllowStart();
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
r.appInfo.uid, r.intent.getIntent(), r, r.userId,
BackgroundStartPrivileges.NONE,
- false /* isBindService */, false /* isStartService */);
- if (r.mAllowStartForeground == REASON_DENIED) {
+ false /* isBindService */);
+ if (!r.isFgsAllowedStart()) {
Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: "
+ " BFSL DENIED.");
} else {
@@ -2191,13 +2190,13 @@
Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: "
+ " BFSL Allowed: "
+ PowerExemptionManager.reasonCodeToString(
- r.mAllowStartForeground));
+ r.getFgsAllowStart()));
}
}
final boolean fgsStartAllowed =
!isBgFgsRestrictionEnabledForService
- || (r.mAllowStartForeground != REASON_DENIED);
+ || r.isFgsAllowedStart();
if (fgsStartAllowed) {
if (isNewTypeShortFgs) {
@@ -2246,7 +2245,7 @@
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
r.appInfo.uid, r.intent.getIntent(), r, r.userId,
BackgroundStartPrivileges.NONE,
- false /* isBindService */, false /* isStartService */);
+ false /* isBindService */);
final String temp = "startForegroundDelayMs:" + delayMs;
if (r.mInfoAllowStartForeground != null) {
r.mInfoAllowStartForeground += "; " + temp;
@@ -2266,20 +2265,21 @@
setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
r.appInfo.uid, r.intent.getIntent(), r, r.userId,
BackgroundStartPrivileges.NONE,
- false /* isBindService */, false /* isStartService */);
+ false /* isBindService */);
}
// If the foreground service is not started from TOP process, do not allow it to
// have while-in-use location/camera/microphone access.
- if (!r.mAllowWhileInUsePermissionInFgs) {
+ if (!r.isFgsAllowedWIU()) {
Slog.w(TAG,
"Foreground service started from background can not have "
+ "location/camera/microphone access: service "
+ r.shortInstanceName);
}
+ r.maybeLogFgsLogicChange();
if (!bypassBfslCheck) {
logFgsBackgroundStart(r);
- if (r.mAllowStartForeground == REASON_DENIED
+ if (!r.isFgsAllowedStart()
&& isBgFgsRestrictionEnabledForService) {
final String msg = "Service.startForeground() not allowed due to "
+ "mAllowStartForeground false: service "
@@ -2378,9 +2378,9 @@
// The logging of FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER event could
// be deferred, make a copy of mAllowStartForeground and
// mAllowWhileInUsePermissionInFgs.
- r.mAllowStartForegroundAtEntering = r.mAllowStartForeground;
+ r.mAllowStartForegroundAtEntering = r.getFgsAllowStart();
r.mAllowWhileInUsePermissionInFgsAtEntering =
- r.mAllowWhileInUsePermissionInFgs;
+ r.isFgsAllowedWIU();
r.mStartForegroundCount++;
r.mFgsEnterTime = SystemClock.uptimeMillis();
if (!stopProcStatsOp) {
@@ -2558,7 +2558,7 @@
policy.getForegroundServiceTypePolicyInfo(type, defaultToType);
final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy(
mAm.mContext, r.packageName, r.app.uid, r.app.getPid(),
- r.mAllowWhileInUsePermissionInFgs, policyInfo);
+ r.isFgsAllowedWIU(), policyInfo);
RuntimeException exception = null;
switch (code) {
case FGS_TYPE_POLICY_CHECK_DEPRECATED: {
@@ -3744,8 +3744,7 @@
}
}
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, userId,
- BackgroundStartPrivileges.NONE, true /* isBindService */,
- false /* isStartService */);
+ BackgroundStartPrivileges.NONE, true /* isBindService */);
if (s.app != null) {
ProcessServiceRecord servicePsr = s.app.mServices;
@@ -7444,54 +7443,80 @@
* @param callingUid caller app's uid.
* @param intent intent to start/bind service.
* @param r the service to start.
- * @param isStartService True if it's called from Context.startService().
- * False if it's called from Context.startForegroundService() or
- * Service.startForeground().
+ * @param isBindService True if it's called from bindService().
* @return true if allow, false otherwise.
*/
private void setFgsRestrictionLocked(String callingPackage,
int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
- BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService,
- boolean isStartService) {
- // Check DeviceConfig flag.
- if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
- if (!r.mAllowWhileInUsePermissionInFgs) {
- // BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
- // Note REASON_OTHER since there's no other suitable reason.
- r.mAllowWhileInUsePermissionInFgsReason = REASON_OTHER;
- }
- r.mAllowWhileInUsePermissionInFgs = true;
+ BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService) {
+
+ @ReasonCode int allowWIU;
+ @ReasonCode int allowStart;
+
+ // If called from bindService(), do not update the actual fields, but instead
+ // keep it in a separate set of fields.
+ if (isBindService) {
+ allowWIU = r.mAllowWIUInBindService;
+ allowStart = r.mAllowStartInBindService;
+ } else {
+ allowWIU = r.mAllowWhileInUsePermissionInFgsReasonNoBinding;
+ allowStart = r.mAllowStartForegroundNoBinding;
}
- if (!r.mAllowWhileInUsePermissionInFgs
- || (r.mAllowStartForeground == REASON_DENIED)) {
+ // Check DeviceConfig flag.
+ if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
+ if (allowWIU == REASON_DENIED) {
+ // BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
+ // Note REASON_OTHER since there's no other suitable reason.
+ allowWIU = REASON_OTHER;
+ }
+ }
+
+ if ((allowWIU == REASON_DENIED)
+ || (allowStart == REASON_DENIED)) {
@ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges);
// We store them to compare the old and new while-in-use logics to each other.
// (They're not used for any other purposes.)
- if (!r.mAllowWhileInUsePermissionInFgs) {
- r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
- r.mAllowWhileInUsePermissionInFgsReason = allowWhileInUse;
+ if (allowWIU == REASON_DENIED) {
+ allowWIU = allowWhileInUse;
}
- if (r.mAllowStartForeground == REASON_DENIED) {
- r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked(
+ if (allowStart == REASON_DENIED) {
+ allowStart = shouldAllowFgsStartForegroundWithBindingCheckLocked(
allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
backgroundStartPrivileges, isBindService);
}
}
+
+ if (isBindService) {
+ r.mAllowWIUInBindService = allowWIU;
+ r.mAllowStartInBindService = allowStart;
+ } else {
+ r.mAllowWhileInUsePermissionInFgsReasonNoBinding = allowWIU;
+ r.mAllowStartForegroundNoBinding = allowStart;
+
+ // Also do a binding client check, unless called from bindService().
+ if (r.mAllowWIUByBindings == REASON_DENIED) {
+ r.mAllowWIUByBindings =
+ shouldAllowFgsWhileInUsePermissionByBindingsLocked(callingUid);
+ }
+ if (r.mAllowStartByBindings == REASON_DENIED) {
+ r.mAllowStartByBindings = r.mAllowWIUByBindings;
+ }
+ }
}
/**
* Reset various while-in-use and BFSL related information.
*/
void resetFgsRestrictionLocked(ServiceRecord r) {
- r.mAllowWhileInUsePermissionInFgs = false;
- r.mAllowWhileInUsePermissionInFgsReason = REASON_DENIED;
- r.mAllowStartForeground = REASON_DENIED;
+ r.clearFgsAllowWIU();
+ r.clearFgsAllowStart();
+
r.mInfoAllowStartForeground = null;
r.mInfoTempFgsAllowListReason = null;
r.mLoggedInfoAllowStartForeground = false;
- r.updateAllowUiJobScheduling(r.mAllowWhileInUsePermissionInFgs);
+ r.updateAllowUiJobScheduling(r.isFgsAllowedWIU());
}
boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
@@ -8063,10 +8088,10 @@
*/
if (!r.mLoggedInfoAllowStartForeground) {
final String msg = "Background started FGS: "
- + ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ")
+ + (r.isFgsAllowedStart() ? "Allowed " : "Disallowed ")
+ r.mInfoAllowStartForeground
+ (r.isShortFgs() ? " (Called on SHORT_SERVICE)" : "");
- if (r.mAllowStartForeground != REASON_DENIED) {
+ if (r.isFgsAllowedStart()) {
if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
mAm.mConstants.mFgsStartAllowedLogSampleRate)) {
Slog.wtfQuiet(TAG, msg);
@@ -8106,8 +8131,8 @@
allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
} else {
- allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgs;
- fgsStartReasonCode = r.mAllowStartForeground;
+ allowWhileInUsePermissionInFgs = r.isFgsAllowedWIU();
+ fgsStartReasonCode = r.getFgsAllowStart();
}
final int callerTargetSdkVersion = r.mRecentCallerApplicationInfo != null
? r.mRecentCallerApplicationInfo.targetSdkVersion : 0;
@@ -8296,8 +8321,7 @@
r.mFgsEnterTime = SystemClock.uptimeMillis();
r.foregroundServiceType = options.mForegroundServiceTypes;
setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId,
- BackgroundStartPrivileges.NONE, false /* isBindService */,
- false /* isStartService */);
+ BackgroundStartPrivileges.NONE, false /* isBindService */);
final ProcessServiceRecord psr = callerApp.mServices;
final boolean newService = psr.startService(r);
// updateOomAdj.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index eacc7c2..c9769b3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -559,6 +559,10 @@
// How long we wait for a launched process to attach to the activity manager
// before we decide it's never going to come up for real.
static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+ // How long we wait for a launched process to complete its app startup before we ANR.
+ static final int BIND_APPLICATION_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
// How long we wait to kill an application zygote, after the last process using
// it has gone away.
static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000;
@@ -1625,6 +1629,7 @@
static final int UPDATE_CACHED_APP_HIGH_WATERMARK = 79;
static final int ADD_UID_TO_OBSERVER_MSG = 80;
static final int REMOVE_UID_FROM_OBSERVER_MSG = 81;
+ static final int BIND_APPLICATION_TIMEOUT_MSG = 82;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1977,6 +1982,16 @@
case UPDATE_CACHED_APP_HIGH_WATERMARK: {
mAppProfiler.mCachedAppsWatermarkData.updateCachedAppsSnapshot((long) msg.obj);
} break;
+ case BIND_APPLICATION_TIMEOUT_MSG: {
+ ProcessRecord app = (ProcessRecord) msg.obj;
+
+ final String anrMessage;
+ synchronized (app) {
+ anrMessage = "Process " + app + " failed to complete startup";
+ }
+
+ mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage));
+ } break;
}
}
}
@@ -4737,6 +4752,12 @@
app.getDisabledCompatChanges(), serializedSystemFontMap,
app.getStartElapsedTime(), app.getStartUptime());
}
+
+ Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_MSG);
+ msg.obj = app;
+ mHandler.sendMessageDelayed(msg, BIND_APPLICATION_TIMEOUT);
+ mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+
if (profilerInfo != null) {
profilerInfo.closeFd();
profilerInfo = null;
@@ -4811,7 +4832,7 @@
}
if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
- mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+ mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_MSG, app);
} else {
Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
+ ". Uid: " + uid);
@@ -9721,6 +9742,7 @@
ResultReceiver resultReceiver) {
final int callingUid = Binder.getCallingUid();
if (callingUid != ROOT_UID && callingUid != Process.SHELL_UID) {
+ resultReceiver.send(-1, null);
throw new SecurityException("Shell commands are only callable by root or shell");
}
(new ActivityManagerShellCommand(this, false)).exec(
@@ -19893,7 +19915,8 @@
return superImpl.apply(code, new AttributionSource(shellUid,
Process.INVALID_PID, "com.android.shell",
attributionSource.getAttributionTag(), attributionSource.getToken(),
- /*renouncedPermissions*/ null, attributionSource.getNext()),
+ /*renouncedPermissions*/ null, attributionSource.getDeviceId(),
+ attributionSource.getNext()),
shouldCollectAsyncNotedOp, message, shouldCollectMessage,
skiProxyOperation);
} finally {
@@ -19946,7 +19969,8 @@
return superImpl.apply(clientId, code, new AttributionSource(shellUid,
Process.INVALID_PID, "com.android.shell",
attributionSource.getAttributionTag(), attributionSource.getToken(),
- /*renouncedPermissions*/ null, attributionSource.getNext()),
+ /*renouncedPermissions*/ null, attributionSource.getDeviceId(),
+ attributionSource.getNext()),
startIfModeDefault, shouldCollectAsyncNotedOp, message,
shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
proxiedAttributionFlags, attributionChainId);
@@ -19972,7 +19996,8 @@
superImpl.apply(clientId, code, new AttributionSource(shellUid,
Process.INVALID_PID, "com.android.shell",
attributionSource.getAttributionTag(), attributionSource.getToken(),
- /*renouncedPermissions*/ null, attributionSource.getNext()),
+ /*renouncedPermissions*/ null, attributionSource.getDeviceId(),
+ attributionSource.getNext()),
skipProxyOperation);
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index f5e2eb5..8d2edaa 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -249,6 +249,7 @@
private static final int MSG_CHECK_HEALTH = 5;
private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 6;
private static final int MSG_PROCESS_FREEZABLE_CHANGED = 7;
+ private static final int MSG_UID_STATE_CHANGED = 8;
private void enqueueUpdateRunningList() {
mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -295,6 +296,19 @@
}
return true;
}
+ case MSG_UID_STATE_CHANGED: {
+ final int uid = (int) msg.obj;
+ final int procState = msg.arg1;
+ synchronized (mService) {
+ if (procState == ActivityManager.PROCESS_STATE_TOP) {
+ mUidForeground.put(uid, true);
+ } else {
+ mUidForeground.delete(uid);
+ }
+ refreshProcessQueuesLocked(uid);
+ }
+ return true;
+ }
}
return false;
};
@@ -672,7 +686,7 @@
@Override
public void onProcessFreezableChangedLocked(@NonNull ProcessRecord app) {
mLocalHandler.removeMessages(MSG_PROCESS_FREEZABLE_CHANGED, app);
- mLocalHandler.sendMessage(mHandler.obtainMessage(MSG_PROCESS_FREEZABLE_CHANGED, app));
+ mLocalHandler.obtainMessage(MSG_PROCESS_FREEZABLE_CHANGED, app).sendToTarget();
}
@Override
@@ -1601,14 +1615,9 @@
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq,
int capability) {
- synchronized (mService) {
- if (procState == ActivityManager.PROCESS_STATE_TOP) {
- mUidForeground.put(uid, true);
- } else {
- mUidForeground.delete(uid);
- }
- refreshProcessQueuesLocked(uid);
- }
+ mLocalHandler.removeMessages(MSG_UID_STATE_CHANGED, uid);
+ mLocalHandler.obtainMessage(MSG_UID_STATE_CHANGED, procState, 0, uid)
+ .sendToTarget();
}
}, ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_TOP, "android");
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 38e7371..4f5b5e1 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -479,8 +479,8 @@
r.appInfo.uid,
r.shortInstanceName,
fgsState, // FGS State
- r.mAllowWhileInUsePermissionInFgs, // allowWhileInUsePermissionInFgs
- r.mAllowStartForeground, // fgsStartReasonCode
+ r.isFgsAllowedWIU(), // allowWhileInUsePermissionInFgs
+ r.getFgsAllowStart(), // fgsStartReasonCode
r.appInfo.targetSdkVersion,
r.mRecentCallingUid,
0, // callerTargetSdkVersion
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a682c85..459c6ff 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2222,7 +2222,7 @@
if (s.isForeground) {
final int fgsType = s.foregroundServiceType;
- if (s.mAllowWhileInUsePermissionInFgs) {
+ if (s.isFgsAllowedWIU()) {
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 50fe6d7..aabab61 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -21,9 +21,11 @@
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE;
import static android.os.PowerExemptionManager.REASON_DENIED;
+import static android.os.PowerExemptionManager.reasonCodeToString;
import static android.os.Process.INVALID_UID;
import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.server.am.ActiveServices.TAG_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -172,11 +174,11 @@
private BackgroundStartPrivileges mBackgroundStartPrivilegesByStartMerged =
BackgroundStartPrivileges.NONE;
- // allow while-in-use permissions in foreground service or not.
+ // Reason code for allow while-in-use permissions in foreground service.
+ // If it's not DENIED, while-in-use permissions are allowed.
// while-in-use permissions in FGS started from background might be restricted.
- boolean mAllowWhileInUsePermissionInFgs;
@PowerExemptionManager.ReasonCode
- int mAllowWhileInUsePermissionInFgsReason = REASON_DENIED;
+ int mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
// A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state.
boolean mAllowWhileInUsePermissionInFgsAtEntering;
@@ -205,15 +207,114 @@
// allow the service becomes foreground service? Service started from background may not be
// allowed to become a foreground service.
- @PowerExemptionManager.ReasonCode int mAllowStartForeground = REASON_DENIED;
+ @PowerExemptionManager.ReasonCode
+ int mAllowStartForegroundNoBinding = REASON_DENIED;
// A copy of mAllowStartForeground's value when the service is entering FGS state.
- @PowerExemptionManager.ReasonCode int mAllowStartForegroundAtEntering = REASON_DENIED;
+ @PowerExemptionManager.ReasonCode
+ int mAllowStartForegroundAtEntering = REASON_DENIED;
// Debug info why mAllowStartForeground is allowed or denied.
String mInfoAllowStartForeground;
// Debug info if mAllowStartForeground is allowed because of a temp-allowlist.
ActivityManagerService.FgsTempAllowListItem mInfoTempFgsAllowListReason;
// Is the same mInfoAllowStartForeground string has been logged before? Used for dedup.
boolean mLoggedInfoAllowStartForeground;
+
+ @PowerExemptionManager.ReasonCode
+ int mAllowWIUInBindService = REASON_DENIED;
+
+ @PowerExemptionManager.ReasonCode
+ int mAllowWIUByBindings = REASON_DENIED;
+
+ @PowerExemptionManager.ReasonCode
+ int mAllowStartInBindService = REASON_DENIED;
+
+ @PowerExemptionManager.ReasonCode
+ int mAllowStartByBindings = REASON_DENIED;
+
+ @PowerExemptionManager.ReasonCode
+ int getFgsAllowWIU() {
+ return mAllowWhileInUsePermissionInFgsReasonNoBinding != REASON_DENIED
+ ? mAllowWhileInUsePermissionInFgsReasonNoBinding
+ : mAllowWIUInBindService;
+ }
+
+ boolean isFgsAllowedWIU() {
+ return getFgsAllowWIU() != REASON_DENIED;
+ }
+
+ @PowerExemptionManager.ReasonCode
+ int getFgsAllowStart() {
+ return mAllowStartForegroundNoBinding != REASON_DENIED
+ ? mAllowStartForegroundNoBinding
+ : mAllowStartInBindService;
+ }
+
+ boolean isFgsAllowedStart() {
+ return getFgsAllowStart() != REASON_DENIED;
+ }
+
+ void clearFgsAllowWIU() {
+ mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
+ mAllowWIUInBindService = REASON_DENIED;
+ mAllowWIUByBindings = REASON_DENIED;
+ }
+
+ void clearFgsAllowStart() {
+ mAllowStartForegroundNoBinding = REASON_DENIED;
+ mAllowStartInBindService = REASON_DENIED;
+ mAllowStartByBindings = REASON_DENIED;
+ }
+
+ @PowerExemptionManager.ReasonCode
+ int reasonOr(@PowerExemptionManager.ReasonCode int first,
+ @PowerExemptionManager.ReasonCode int second) {
+ return first != REASON_DENIED ? first : second;
+ }
+
+ boolean allowedChanged(@PowerExemptionManager.ReasonCode int first,
+ @PowerExemptionManager.ReasonCode int second) {
+ return (first == REASON_DENIED) != (second == REASON_DENIED);
+ }
+
+ String changeMessage(@PowerExemptionManager.ReasonCode int first,
+ @PowerExemptionManager.ReasonCode int second) {
+ return reasonOr(first, second) == REASON_DENIED ? "DENIED"
+ : ("ALLOWED ("
+ + reasonCodeToString(first)
+ + "+"
+ + reasonCodeToString(second)
+ + ")");
+ }
+
+ void maybeLogFgsLogicChange() {
+ final int origWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+ mAllowWIUInBindService);
+ final int newWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+ mAllowWIUByBindings);
+ final int origStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartInBindService);
+ final int newStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+
+ final boolean wiuChanged = allowedChanged(origWiu, newWiu);
+ final boolean startChanged = allowedChanged(origStart, newStart);
+
+ if (!wiuChanged && !startChanged) {
+ return;
+ }
+ final String message = "FGS logic changed:"
+ + (wiuChanged ? " [WIU changed]" : "")
+ + (startChanged ? " [BFSL changed]" : "")
+ + " OW:" // Orig-WIU
+ + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding,
+ mAllowWIUInBindService)
+ + " NW:" // New-WIU
+ + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding, mAllowWIUByBindings)
+ + " OS:" // Orig-start
+ + changeMessage(mAllowStartForegroundNoBinding, mAllowStartInBindService)
+ + " NS:" // New-start
+ + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+ Slog.wtf(TAG_SERVICE, message);
+ }
+
// The number of times Service.startForeground() is called, after this service record is
// created. (i.e. due to "bound" or "start".) It never decreases, even when stopForeground()
// is called.
@@ -502,7 +603,7 @@
ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now);
proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg);
proto.write(ServiceRecordProto.ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS,
- mAllowWhileInUsePermissionInFgs);
+ isFgsAllowedWIU());
if (startRequested || delayedStop || lastStartId != 0) {
long startToken = proto.start(ServiceRecordProto.START);
@@ -618,7 +719,13 @@
pw.println(mBackgroundStartPrivilegesByStartMerged);
}
pw.print(prefix); pw.print("mAllowWhileInUsePermissionInFgsReason=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowWhileInUsePermissionInFgsReason));
+ pw.println(PowerExemptionManager.reasonCodeToString(
+ mAllowWhileInUsePermissionInFgsReasonNoBinding));
+
+ pw.print(prefix); pw.print("mAllowWIUInBindService=");
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUInBindService));
+ pw.print(prefix); pw.print("mAllowWIUByBindings=");
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUByBindings));
pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling);
pw.print(prefix); pw.print("recentCallingPackage=");
@@ -626,7 +733,12 @@
pw.print(prefix); pw.print("recentCallingUid=");
pw.println(mRecentCallingUid);
pw.print(prefix); pw.print("allowStartForeground=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForeground));
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForegroundNoBinding));
+ pw.print(prefix); pw.print("mAllowStartInBindService=");
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartInBindService));
+ pw.print(prefix); pw.print("mAllowStartByBindings=");
+ pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartByBindings));
+
pw.print(prefix); pw.print("startForegroundCount=");
pw.println(mStartForegroundCount);
pw.print(prefix); pw.print("infoAllowStartForeground=");
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index b6e58ad..4ee4c30 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3207,7 +3207,7 @@
return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
- attributionChainId, /*dryRun*/ false);
+ attributionChainId);
}
@Override
@@ -3275,11 +3275,10 @@
if (!skipProxyOperation) {
// Test if the proxied operation will succeed before starting the proxy operation
- final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code,
- proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
- resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- proxiedAttributionFlags, attributionChainId, /*dryRun*/ true);
+ final SyncNotedAppOp testProxiedOp = startOperationDryRun(code,
+ proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag,
+ resolvedProxyPackageName, proxiedFlags, startIfModeDefault);
+
if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
return testProxiedOp;
}
@@ -3290,8 +3289,7 @@
final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
- shouldCollectMessage, proxyAttributionFlags, attributionChainId,
- /*dryRun*/ false);
+ shouldCollectMessage, proxyAttributionFlags, attributionChainId);
if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
return proxyAppOp;
}
@@ -3300,8 +3298,7 @@
return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
- /*dryRun*/ false);
+ shouldCollectMessage, proxiedAttributionFlags, attributionChainId);
}
private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
@@ -3313,7 +3310,7 @@
String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
- int attributionChainId, boolean dryRun) {
+ int attributionChainId) {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
@@ -3332,11 +3329,9 @@
final Ops ops = getOpsLocked(uid, packageName, attributionTag,
pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
if (ops == null) {
- if (!dryRun) {
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
- attributionChainId);
- }
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
+ attributionChainId);
if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + " flags: "
+ AppOpsManager.flagsToString(flags));
@@ -3359,11 +3354,9 @@
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
}
- if (!dryRun) {
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, uidMode, startType, attributionFlags, attributionChainId);
- }
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, uidMode, startType, attributionFlags, attributionChainId);
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
} else {
@@ -3375,39 +3368,35 @@
if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName + " flags: " + AppOpsManager.flagsToString(flags));
- if (!dryRun) {
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, mode, startType, attributionFlags, attributionChainId);
- }
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, mode, startType, attributionFlags, attributionChainId);
return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
}
if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+ " package " + packageName + " restricted: " + isRestricted
+ " flags: " + AppOpsManager.flagsToString(flags));
- if (!dryRun) {
- try {
- if (isRestricted) {
- attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
- attributionFlags, attributionChainId);
- } else {
- attributedOp.started(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
- attributionFlags, attributionChainId);
- startType = START_TYPE_STARTED;
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
+ try {
+ if (isRestricted) {
+ attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
+ proxyAttributionTag, uidState.getState(), flags,
+ attributionFlags, attributionChainId);
+ } else {
+ attributedOp.started(clientId, proxyUid, proxyPackageName,
+ proxyAttributionTag, uidState.getState(), flags,
+ attributionFlags, attributionChainId);
+ startType = START_TYPE_STARTED;
}
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
- attributionChainId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
}
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+ attributionChainId);
}
- if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) {
+ if (shouldCollectAsyncNotedOp && !isRestricted) {
collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
message, shouldCollectMessage);
}
@@ -3416,6 +3405,88 @@
packageName);
}
+ /**
+ * Performs a dry run of the start operation i.e. determines the result of the start operation
+ * without actually updating the op state to be started.
+ *
+ * <p>This is used for proxy operations; before starting the op as the proxy, we must check that
+ * the proxied app can successfully start the operation.
+ */
+ private SyncNotedAppOp startOperationDryRun(int code, int uid,
+ @NonNull String packageName, @Nullable String attributionTag,
+ String proxyPackageName, @OpFlags int flags,
+ boolean startIfModeDefault) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+ if (!pvr.isAttributionTagValid) {
+ attributionTag = null;
+ }
+ } catch (SecurityException e) {
+ if (Process.isIsolated(uid)) {
+ Slog.e(TAG, "Cannot startOperation: isolated process");
+ } else {
+ Slog.e(TAG, "Cannot startOperation", e);
+ }
+ return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+ packageName);
+ }
+
+ boolean isRestricted = false;
+ synchronized (this) {
+ final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+ pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+ if (ops == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ + " package " + packageName + " flags: "
+ + AppOpsManager.flagsToString(flags));
+ }
+ return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
+ packageName);
+ }
+ final Op op = getOpLocked(ops, code, uid, true);
+ final UidState uidState = ops.uidState;
+ isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
+ false);
+ final int switchCode = AppOpsManager.opToSwitch(code);
+ // If there is a non-default mode per UID policy (we set UID op mode only if
+ // non-default) it takes over, otherwise use the per package policy.
+ if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+ final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+ if (!shouldStartForMode(uidMode, startIfModeDefault)) {
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
+ }
+ } else {
+ final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+ : op;
+ final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+ if (mode != AppOpsManager.MODE_ALLOWED
+ && (!startIfModeDefault || mode != MODE_DEFAULT)) {
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ return new SyncNotedAppOp(mode, code, attributionTag, packageName);
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+ + " package " + packageName + " restricted: " + isRestricted
+ + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ }
+
+ return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
+ packageName);
+ }
+
@Override
public void finishOperation(IBinder clientId, int code, int uid, String packageName,
String attributionTag) {
diff --git a/services/core/java/com/android/server/appop/OWNERS b/services/core/java/com/android/server/appop/OWNERS
index 999ea0e..2fe78c5 100644
--- a/services/core/java/com/android/server/appop/OWNERS
+++ b/services/core/java/com/android/server/appop/OWNERS
@@ -1 +1,2 @@
+#Bug component: 137825
include /core/java/android/permission/OWNERS
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/services/core/java/com/android/server/biometrics/BiometricCameraManager.java
similarity index 71%
rename from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
rename to services/core/java/com/android/server/biometrics/BiometricCameraManager.java
index 6727fbc..058ea6b 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/services/core/java/com/android/server/biometrics/BiometricCameraManager.java
@@ -17,9 +17,16 @@
package com.android.server.biometrics;
/**
- * Interface for biometric operations to get camera privacy state.
+ * Interface for biometrics to get camera status.
*/
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
+public interface BiometricCameraManager {
+ /**
+ * Returns true if any camera is in use.
+ */
+ boolean isAnyCameraUnavailable();
+
+ /**
+ * Returns true if privacy is enabled and camera access is disabled.
+ */
boolean isCameraPrivacyEnabled();
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java
new file mode 100644
index 0000000..000ee54
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricCameraManagerImpl.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+
+import android.annotation.NonNull;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.camera2.CameraManager;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class BiometricCameraManagerImpl implements BiometricCameraManager {
+
+ private final CameraManager mCameraManager;
+ private final SensorPrivacyManager mSensorPrivacyManager;
+ private final ConcurrentHashMap<String, Boolean> mIsCameraAvailable = new ConcurrentHashMap<>();
+
+ private final CameraManager.AvailabilityCallback mCameraAvailabilityCallback =
+ new CameraManager.AvailabilityCallback() {
+ @Override
+ public void onCameraAvailable(@NonNull String cameraId) {
+ mIsCameraAvailable.put(cameraId, true);
+ }
+
+ @Override
+ public void onCameraUnavailable(@NonNull String cameraId) {
+ mIsCameraAvailable.put(cameraId, false);
+ }
+ };
+
+ public BiometricCameraManagerImpl(@NonNull CameraManager cameraManager,
+ @NonNull SensorPrivacyManager sensorPrivacyManager) {
+ mCameraManager = cameraManager;
+ mSensorPrivacyManager = sensorPrivacyManager;
+ mCameraManager.registerAvailabilityCallback(mCameraAvailabilityCallback, null);
+ }
+
+ @Override
+ public boolean isAnyCameraUnavailable() {
+ for (String cameraId : mIsCameraAvailable.keySet()) {
+ if (!mIsCameraAvailable.get(cameraId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isCameraPrivacyEnabled() {
+ return mSensorPrivacyManager != null && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java
deleted file mode 100644
index b6701da..0000000
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics;
-
-import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
-
-import android.annotation.Nullable;
-import android.hardware.SensorPrivacyManager;
-
-public class BiometricSensorPrivacyImpl implements
- BiometricSensorPrivacy {
- private final SensorPrivacyManager mSensorPrivacyManager;
-
- public BiometricSensorPrivacyImpl(@Nullable SensorPrivacyManager sensorPrivacyManager) {
- mSensorPrivacyManager = sensorPrivacyManager;
- }
-
- @Override
- public boolean isCameraPrivacyEnabled() {
- return mSensorPrivacyManager != null && mSensorPrivacyManager
- .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 1fa97a3..279aaf9 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -17,6 +17,8 @@
package com.android.server.biometrics;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -48,6 +50,7 @@
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.camera2.CameraManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.net.Uri;
@@ -125,7 +128,7 @@
AuthSession mAuthSession;
private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final BiometricSensorPrivacy mBiometricSensorPrivacy;
+ private final BiometricCameraManager mBiometricCameraManager;
/**
* Tracks authenticatorId invalidation. For more details, see
@@ -368,7 +371,7 @@
public boolean getConfirmationAlwaysRequired(@BiometricAuthenticator.Modality int modality,
int userId) {
switch (modality) {
- case BiometricAuthenticator.TYPE_FACE:
+ case TYPE_FACE:
if (!mFaceAlwaysRequireConfirmation.containsKey(userId)) {
onChange(true /* selfChange */,
FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
@@ -566,22 +569,6 @@
Utils.combineAuthenticatorBundles(promptInfo);
- // Set the default title if necessary.
- if (promptInfo.isUseDefaultTitle()) {
- if (TextUtils.isEmpty(promptInfo.getTitle())) {
- promptInfo.setTitle(getContext()
- .getString(R.string.biometric_dialog_default_title));
- }
- }
-
- // Set the default subtitle if necessary.
- if (promptInfo.isUseDefaultSubtitle()) {
- if (TextUtils.isEmpty(promptInfo.getSubtitle())) {
- promptInfo.setSubtitle(getContext()
- .getString(R.string.biometric_dialog_default_subtitle));
- }
- }
-
final long requestId = mRequestCounter.get();
mHandler.post(() -> handleAuthenticate(
token, requestId, operationId, userId, receiver, opPackageName, promptInfo));
@@ -936,7 +923,7 @@
return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
- getContext(), mBiometricSensorPrivacy);
+ getContext(), mBiometricCameraManager);
}
/**
@@ -1030,9 +1017,9 @@
return context.getSystemService(UserManager.class);
}
- public BiometricSensorPrivacy getBiometricSensorPrivacy(Context context) {
- return new BiometricSensorPrivacyImpl(context.getSystemService(
- SensorPrivacyManager.class));
+ public BiometricCameraManager getBiometricCameraManager(Context context) {
+ return new BiometricCameraManagerImpl(context.getSystemService(CameraManager.class),
+ context.getSystemService(SensorPrivacyManager.class));
}
}
@@ -1062,7 +1049,7 @@
mRequestCounter = mInjector.getRequestGenerator();
mBiometricContext = injector.getBiometricContext(context);
mUserManager = injector.getUserManager(context);
- mBiometricSensorPrivacy = injector.getBiometricSensorPrivacy(context);
+ mBiometricCameraManager = injector.getBiometricCameraManager(context);
try {
injector.getActivityManagerService().registerUserSwitchObserver(
@@ -1299,7 +1286,34 @@
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
- getContext(), mBiometricSensorPrivacy);
+ getContext(), mBiometricCameraManager);
+
+ // Set the default title if necessary.
+ if (promptInfo.isUseDefaultTitle()) {
+ if (TextUtils.isEmpty(promptInfo.getTitle())) {
+ promptInfo.setTitle(getContext()
+ .getString(R.string.biometric_dialog_default_title));
+ }
+ }
+
+ final int eligible = preAuthInfo.getEligibleModalities();
+ final boolean hasEligibleFingerprintSensor =
+ (eligible & TYPE_FINGERPRINT) == TYPE_FINGERPRINT;
+ final boolean hasEligibleFaceSensor = (eligible & TYPE_FACE) == TYPE_FACE;
+
+ // Set the subtitle according to the modality.
+ if (promptInfo.isUseDefaultSubtitle()) {
+ if (hasEligibleFingerprintSensor && hasEligibleFaceSensor) {
+ promptInfo.setSubtitle(getContext()
+ .getString(R.string.biometric_dialog_default_subtitle));
+ } else if (hasEligibleFingerprintSensor) {
+ promptInfo.setSubtitle(getContext()
+ .getString(R.string.biometric_dialog_fingerprint_subtitle));
+ } else if (hasEligibleFaceSensor) {
+ promptInfo.setSubtitle(getContext()
+ .getString(R.string.biometric_dialog_face_subtitle));
+ }
+ }
final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index e6f25cb..b1740a7 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -72,16 +72,16 @@
final Context context;
private final boolean mBiometricRequested;
private final int mBiometricStrengthRequested;
- private final BiometricSensorPrivacy mBiometricSensorPrivacy;
+ private final BiometricCameraManager mBiometricCameraManager;
private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
boolean credentialRequested, List<BiometricSensor> eligibleSensors,
List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
- Context context, BiometricSensorPrivacy biometricSensorPrivacy) {
+ Context context, BiometricCameraManager biometricCameraManager) {
mBiometricRequested = biometricRequested;
mBiometricStrengthRequested = biometricStrengthRequested;
- mBiometricSensorPrivacy = biometricSensorPrivacy;
+ mBiometricCameraManager = biometricCameraManager;
this.credentialRequested = credentialRequested;
this.eligibleSensors = eligibleSensors;
@@ -99,7 +99,7 @@
List<BiometricSensor> sensors,
int userId, PromptInfo promptInfo, String opPackageName,
boolean checkDevicePolicyManager, Context context,
- BiometricSensorPrivacy biometricSensorPrivacy)
+ BiometricCameraManager biometricCameraManager)
throws RemoteException {
final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -127,7 +127,7 @@
checkDevicePolicyManager, requestedStrength,
promptInfo.getAllowedSensorIds(),
promptInfo.isIgnoreEnrollmentState(),
- biometricSensorPrivacy);
+ biometricCameraManager);
Slog.d(TAG, "Package: " + opPackageName
+ " Sensor ID: " + sensor.id
@@ -151,7 +151,7 @@
return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
- promptInfo.isIgnoreEnrollmentState(), userId, context, biometricSensorPrivacy);
+ promptInfo.isIgnoreEnrollmentState(), userId, context, biometricCameraManager);
}
/**
@@ -168,12 +168,16 @@
BiometricSensor sensor, int userId, String opPackageName,
boolean checkDevicePolicyManager, int requestedStrength,
@NonNull List<Integer> requestedSensorIds,
- boolean ignoreEnrollmentState, BiometricSensorPrivacy biometricSensorPrivacy) {
+ boolean ignoreEnrollmentState, BiometricCameraManager biometricCameraManager) {
if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
return BIOMETRIC_NO_HARDWARE;
}
+ if (sensor.modality == TYPE_FACE && biometricCameraManager.isAnyCameraUnavailable()) {
+ return BIOMETRIC_HARDWARE_NOT_DETECTED;
+ }
+
final boolean wasStrongEnough =
Utils.isAtLeastStrength(sensor.oemStrength, requestedStrength);
final boolean isStrongEnough =
@@ -195,8 +199,8 @@
return BIOMETRIC_NOT_ENROLLED;
}
- if (biometricSensorPrivacy != null && sensor.modality == TYPE_FACE) {
- if (biometricSensorPrivacy.isCameraPrivacyEnabled()) {
+ if (biometricCameraManager != null && sensor.modality == TYPE_FACE) {
+ if (biometricCameraManager.isCameraPrivacyEnabled()) {
//Camera privacy is enabled as the access is disabled
return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
}
@@ -307,8 +311,8 @@
@BiometricAuthenticator.Modality int modality = TYPE_NONE;
boolean cameraPrivacyEnabled = false;
- if (mBiometricSensorPrivacy != null) {
- cameraPrivacyEnabled = mBiometricSensorPrivacy.isCameraPrivacyEnabled();
+ if (mBiometricCameraManager != null) {
+ cameraPrivacyEnabled = mBiometricCameraManager.isCameraPrivacyEnabled();
}
if (mBiometricRequested && credentialRequested) {
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 63bb026..cba5039 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3346,7 +3346,7 @@
// Transforms do not need to be persisted; the IkeSession will keep
// them alive for us
mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
- } catch (IOException e) {
+ } catch (IOException | IllegalArgumentException e) {
Log.d(TAG, "Transform application failed for token " + token, e);
onSessionLost(token, e);
}
@@ -3440,7 +3440,7 @@
mTunnelIface, IpSecManager.DIRECTION_IN, inTransform);
mIpSecManager.applyTunnelModeTransform(
mTunnelIface, IpSecManager.DIRECTION_OUT, outTransform);
- } catch (IOException e) {
+ } catch (IOException | IllegalArgumentException e) {
Log.d(TAG, "Transform application failed for token " + token, e);
onSessionLost(token, e);
}
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index e27182f..dd5afa2 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import android.text.TextUtils;
+
import com.android.server.display.brightness.BrightnessReason;
import java.util.Objects;
@@ -29,12 +31,14 @@
private final float mSdrBrightness;
private final BrightnessReason mBrightnessReason;
private final String mDisplayBrightnessStrategyName;
+ private final boolean mShouldUseAutoBrightness;
private DisplayBrightnessState(Builder builder) {
- this.mBrightness = builder.getBrightness();
- this.mSdrBrightness = builder.getSdrBrightness();
- this.mBrightnessReason = builder.getBrightnessReason();
- this.mDisplayBrightnessStrategyName = builder.getDisplayBrightnessStrategyName();
+ mBrightness = builder.getBrightness();
+ mSdrBrightness = builder.getSdrBrightness();
+ mBrightnessReason = builder.getBrightnessReason();
+ mDisplayBrightnessStrategyName = builder.getDisplayBrightnessStrategyName();
+ mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
}
/**
@@ -66,6 +70,13 @@
return mDisplayBrightnessStrategyName;
}
+ /**
+ * @return {@code true} if the device is set up to run auto-brightness.
+ */
+ public boolean getShouldUseAutoBrightness() {
+ return mShouldUseAutoBrightness;
+ }
+
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -75,6 +86,8 @@
stringBuilder.append(getSdrBrightness());
stringBuilder.append("\n brightnessReason:");
stringBuilder.append(getBrightnessReason());
+ stringBuilder.append("\n shouldUseAutoBrightness:");
+ stringBuilder.append(getShouldUseAutoBrightness());
return stringBuilder.toString();
}
@@ -91,28 +104,20 @@
return false;
}
- DisplayBrightnessState
- displayBrightnessState = (DisplayBrightnessState) other;
+ DisplayBrightnessState otherState = (DisplayBrightnessState) other;
- if (mBrightness != displayBrightnessState.getBrightness()) {
- return false;
- }
- if (mSdrBrightness != displayBrightnessState.getSdrBrightness()) {
- return false;
- }
- if (!mBrightnessReason.equals(displayBrightnessState.getBrightnessReason())) {
- return false;
- }
- if (!mDisplayBrightnessStrategyName.equals(
- displayBrightnessState.getDisplayBrightnessStrategyName())) {
- return false;
- }
- return true;
+ return mBrightness == otherState.getBrightness()
+ && mSdrBrightness == otherState.getSdrBrightness()
+ && mBrightnessReason.equals(otherState.getBrightnessReason())
+ && TextUtils.equals(mDisplayBrightnessStrategyName,
+ otherState.getDisplayBrightnessStrategyName())
+ && mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness();
}
@Override
public int hashCode() {
- return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason);
+ return Objects.hash(
+ mBrightness, mSdrBrightness, mBrightnessReason, mShouldUseAutoBrightness);
}
/**
@@ -123,6 +128,23 @@
private float mSdrBrightness;
private BrightnessReason mBrightnessReason = new BrightnessReason();
private String mDisplayBrightnessStrategyName;
+ private boolean mShouldUseAutoBrightness;
+
+ /**
+ * Create a builder starting with the values from the specified {@link
+ * DisplayBrightnessState}.
+ *
+ * @param state The state from which to initialize.
+ */
+ public static Builder from(DisplayBrightnessState state) {
+ Builder builder = new Builder();
+ builder.setBrightness(state.getBrightness());
+ builder.setSdrBrightness(state.getSdrBrightness());
+ builder.setBrightnessReason(state.getBrightnessReason());
+ builder.setDisplayBrightnessStrategyName(state.getDisplayBrightnessStrategyName());
+ builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
+ return builder;
+ }
/**
* Gets the brightness
@@ -200,6 +222,21 @@
}
/**
+ * See {@link DisplayBrightnessState#getShouldUseAutoBrightness}.
+ */
+ public Builder setShouldUseAutoBrightness(boolean shouldUseAutoBrightness) {
+ this.mShouldUseAutoBrightness = shouldUseAutoBrightness;
+ return this;
+ }
+
+ /**
+ * See {@link DisplayBrightnessState#getShouldUseAutoBrightness}.
+ */
+ public boolean getShouldUseAutoBrightness() {
+ return mShouldUseAutoBrightness;
+ }
+
+ /**
* This is used to construct an immutable DisplayBrightnessState object from its builder
*/
public DisplayBrightnessState build() {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 1a8e2db..3a0b8b5 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -155,6 +155,7 @@
import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.display.utils.SensorUtils;
import com.android.server.input.InputManagerInternal;
+import com.android.server.utils.FoldSettingWrapper;
import com.android.server.wm.SurfaceAnimationThread;
import com.android.server.wm.WindowManagerInternal;
@@ -540,7 +541,8 @@
mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
mLogicalDisplayMapper = new LogicalDisplayMapper(mContext, mDisplayDeviceRepo,
- new LogicalDisplayListener(), mSyncRoot, mHandler);
+ new LogicalDisplayListener(), mSyncRoot, mHandler,
+ new FoldSettingWrapper(mContext.getContentResolver()));
mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
Resources resources = mContext.getResources();
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 2c354df..2694f55 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -501,7 +501,6 @@
mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(),
() -> updatePowerState(), mDisplayId, mSensorManager);
mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
- mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(context, mDisplayId);
mTag = "DisplayPowerController2[" + mDisplayId + "]";
mThermalBrightnessThrottlingDataId =
logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
@@ -572,6 +571,8 @@
));
// Seed the cached brightness
saveBrightnessInfo(getScreenBrightnessSetting());
+ mAutomaticBrightnessStrategy =
+ mDisplayBrightnessController.getAutomaticBrightnessStrategy();
DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null;
DisplayWhiteBalanceController displayWhiteBalanceController = null;
@@ -615,7 +616,7 @@
setUpAutoBrightness(resources, handler);
- mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic();
+ mColorFadeEnabled = mInjector.isColorFadeEnabled();
mColorFadeFadesConfig = resources.getBoolean(
R.bool.config_animateScreenLights);
@@ -1280,14 +1281,6 @@
int state = mDisplayStateController
.updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController
- .setLightSensorEnabled(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
- && mIsEnabled && (state == Display.STATE_OFF || (state == Display.STATE_DOZE
- && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
- && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
- }
-
// Initialize things the first time the power state is changed.
if (mustInitialize) {
initialize(readyToUpdateDisplayState() ? state : Display.STATE_UNKNOWN);
@@ -1313,6 +1306,17 @@
slowChange = mBrightnessToFollowSlowChange;
}
+ // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
+ // doesn't yet have a valid lux value to use with auto-brightness.
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController
+ .setLightSensorEnabled(displayBrightnessState.getShouldUseAutoBrightness()
+ && mIsEnabled && (state == Display.STATE_OFF
+ || (state == Display.STATE_DOZE
+ && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
+ && mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
+ }
+
// Take note if the short term model was already active before applying the current
// request changes.
final boolean wasShortTermModelActive =
@@ -1588,7 +1592,7 @@
notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
wasShortTermModelActive, mAutomaticBrightnessStrategy.isAutoBrightnessEnabled(),
- brightnessIsTemporary);
+ brightnessIsTemporary, displayBrightnessState.getShouldUseAutoBrightness());
// We save the brightness info *after* the brightness setting has been changed and
// adjustments made so that the brightness info reflects the latest value.
@@ -1632,8 +1636,8 @@
mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
.getDisplayBrightnessStrategyName());
- mTempBrightnessEvent.setAutomaticBrightnessEnabled(mAutomaticBrightnessStrategy
- .shouldUseAutoBrightness());
+ mTempBrightnessEvent.setAutomaticBrightnessEnabled(
+ displayBrightnessState.getShouldUseAutoBrightness());
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -2271,7 +2275,7 @@
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean wasShortTermModelActive, boolean autobrightnessEnabled,
- boolean brightnessIsTemporary) {
+ boolean brightnessIsTemporary, boolean shouldUseAutoBrightness) {
final float brightnessInNits =
mDisplayBrightnessController.convertToAdjustedNits(brightness);
@@ -2286,7 +2290,7 @@
|| mAutomaticBrightnessController.isInIdleMode()
|| !autobrightnessEnabled
|| mBrightnessTracker == null
- || !mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
+ || !shouldUseAutoBrightness
|| brightnessInNits < 0.0f) {
return;
}
@@ -2989,6 +2993,10 @@
return DisplayWhiteBalanceFactory.create(handler,
sensorManager, resources);
}
+
+ boolean isColorFadeEnabled() {
+ return !ActivityManager.isLowRamDeviceStatic();
+ }
}
static class CachedBrightnessInfo {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index d01b03f..26f8029 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -42,6 +42,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
+import com.android.server.utils.FoldSettingWrapper;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -142,6 +143,7 @@
private final Listener mListener;
private final DisplayManagerService.SyncRoot mSyncRoot;
private final LogicalDisplayMapperHandler mHandler;
+ private final FoldSettingWrapper mFoldSettingWrapper;
private final PowerManager mPowerManager;
/**
@@ -189,21 +191,23 @@
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
- @NonNull Handler handler) {
+ @NonNull Handler handler, FoldSettingWrapper foldSettingWrapper) {
this(context, repo, listener, syncRoot, handler,
new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY
- : sNextNonDefaultDisplayId++));
+ : sNextNonDefaultDisplayId++), foldSettingWrapper);
}
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
- @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap) {
+ @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap,
+ FoldSettingWrapper foldSettingWrapper) {
mSyncRoot = syncRoot;
mPowerManager = context.getSystemService(PowerManager.class);
mInteractive = mPowerManager.isInteractive();
mHandler = new LogicalDisplayMapperHandler(handler.getLooper());
mDisplayDeviceRepo = repo;
mListener = listener;
+ mFoldSettingWrapper = foldSettingWrapper;
mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
mSupportsConcurrentInternalDisplays = context.getResources().getBoolean(
com.android.internal.R.bool.config_supportsConcurrentInternalDisplays);
@@ -531,9 +535,10 @@
* Returns if the device should be put to sleep or not.
*
* Includes a check to verify that the device state that we are moving to, {@code pendingState},
- * is the same as the physical state of the device, {@code baseState}. Different values for
- * these parameters indicate a device state override is active, and we shouldn't put the device
- * to sleep to provide a better user experience.
+ * is the same as the physical state of the device, {@code baseState}. Also if the
+ * 'Stay Awake On Fold' is not enabled. Different values for these parameters indicate a device
+ * state override is active, and we shouldn't put the device to sleep to provide a better user
+ * experience.
*
* @param pendingState device state we are moving to
* @param currentState device state we are currently in
@@ -551,7 +556,7 @@
&& mDeviceStatesOnWhichToSleep.get(pendingState)
&& !mDeviceStatesOnWhichToSleep.get(currentState)
&& !isOverrideActive
- && isInteractive && isBootCompleted;
+ && isInteractive && isBootCompleted && !mFoldSettingWrapper.shouldStayAwakeOnFold();
}
private boolean areAllTransitioningDisplaysOffLocked() {
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 2f52b70..ffd62a3 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -29,6 +29,7 @@
import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.BrightnessSetting;
import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import java.io.PrintWriter;
@@ -134,11 +135,21 @@
public DisplayBrightnessState updateBrightness(
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
int targetDisplayState) {
+
+ DisplayBrightnessState state;
synchronized (mLock) {
mDisplayBrightnessStrategy = mDisplayBrightnessStrategySelector.selectStrategy(
displayPowerRequest, targetDisplayState);
- return mDisplayBrightnessStrategy.updateBrightness(displayPowerRequest);
+ state = mDisplayBrightnessStrategy.updateBrightness(displayPowerRequest);
}
+
+ // This is a temporary measure until AutomaticBrightnessStrategy works as a traditional
+ // strategy.
+ // TODO: Remove when AutomaticBrightnessStrategy is populating the values directly.
+ if (state != null) {
+ state = addAutomaticBrightnessState(state);
+ }
+ return state;
}
/**
@@ -322,6 +333,13 @@
}
/**
+ * TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
+ */
+ public AutomaticBrightnessStrategy getAutomaticBrightnessStrategy() {
+ return mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy();
+ }
+
+ /**
* Convert a brightness float scale value to a nit value. Adjustments, such as RBC, are not
* applied. This is used when storing the brightness in nits for the default display and when
* passing the brightness value to follower displays.
@@ -425,6 +443,18 @@
}
}
+ /**
+ * TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy.
+ */
+ private DisplayBrightnessState addAutomaticBrightnessState(DisplayBrightnessState state) {
+ AutomaticBrightnessStrategy autoStrat = getAutomaticBrightnessStrategy();
+
+ DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from(state);
+ builder.setShouldUseAutoBrightness(
+ autoStrat != null && autoStrat.shouldUseAutoBrightness());
+ return builder.build();
+ }
+
@GuardedBy("mLock")
private void setTemporaryBrightnessLocked(float temporaryBrightness) {
mDisplayBrightnessStrategySelector.getTemporaryDisplayBrightnessStrategy()
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 02ca2d3..45f1be0 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -25,6 +25,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
@@ -60,6 +61,8 @@
private final FollowerBrightnessStrategy mFollowerBrightnessStrategy;
// The brightness strategy used to manage the brightness state when the request is invalid.
private final InvalidBrightnessStrategy mInvalidBrightnessStrategy;
+ // Controls brightness when automatic (adaptive) brightness is running.
+ private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
// We take note of the old brightness strategy so that we can know when the strategy changes.
private String mOldBrightnessStrategyName;
@@ -81,6 +84,7 @@
mBoostBrightnessStrategy = injector.getBoostBrightnessStrategy();
mFollowerBrightnessStrategy = injector.getFollowerBrightnessStrategy(displayId);
mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
+ mAutomaticBrightnessStrategy = injector.getAutomaticBrightnessStrategy(context, displayId);
mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
R.bool.config_allowAutoBrightnessWhileDozing);
mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
@@ -130,6 +134,10 @@
return mFollowerBrightnessStrategy;
}
+ public AutomaticBrightnessStrategy getAutomaticBrightnessStrategy() {
+ return mAutomaticBrightnessStrategy;
+ }
+
/**
* Returns a boolean flag indicating if the light sensor is to be used to decide the screen
* brightness when dozing
@@ -198,5 +206,9 @@
InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
return new InvalidBrightnessStrategy();
}
+
+ AutomaticBrightnessStrategy getAutomaticBrightnessStrategy(Context context, int displayId) {
+ return new AutomaticBrightnessStrategy(context, displayId);
+ }
}
}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 080b947..c6a50ed 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -46,6 +46,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
+import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import java.io.File;
@@ -61,6 +62,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
/** A service for managing system fonts. */
public final class FontManagerService extends IFontManager.Stub {
@@ -136,10 +138,11 @@
/** Class to manage FontManagerService's lifecycle. */
public static final class Lifecycle extends SystemService {
private final FontManagerService mService;
+ private final CompletableFuture<Void> mServiceStarted = new CompletableFuture<>();
public Lifecycle(@NonNull Context context, boolean safeMode) {
super(context);
- mService = new FontManagerService(context, safeMode);
+ mService = new FontManagerService(context, safeMode, mServiceStarted);
}
@Override
@@ -152,11 +155,20 @@
if (!Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
return null;
}
+ mServiceStarted.join();
return mService.getCurrentFontMap();
}
});
publishBinderService(Context.FONT_SERVICE, mService);
}
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ // Wait for FontManagerService to start since it will be needed after this point.
+ mServiceStarted.join();
+ }
+ }
}
private static class FsverityUtilImpl implements UpdatableFontDir.FsverityUtil {
@@ -219,25 +231,30 @@
@Nullable
private SharedMemory mSerializedFontMap = null;
- private FontManagerService(Context context, boolean safeMode) {
+ private FontManagerService(
+ Context context, boolean safeMode, CompletableFuture<Void> serviceStarted) {
if (safeMode) {
Slog.i(TAG, "Entering safe mode. Deleting all font updates.");
UpdatableFontDir.deleteAllFiles(new File(FONT_FILES_DIR), new File(CONFIG_XML_FILE));
}
mContext = context;
mIsSafeMode = safeMode;
- initialize();
- // Set system font map only if there is updatable font directory.
- // If there is no updatable font directory, `initialize` will have already loaded the
- // system font map, so there's no need to set the system font map again here.
- if (mUpdatableFontDir != null) {
- try {
- Typeface.setSystemFontMap(getCurrentFontMap());
- } catch (IOException | ErrnoException e) {
- Slog.w(TAG, "Failed to set system font map of system_server");
+ SystemServerInitThreadPool.submit(() -> {
+ initialize();
+
+ // Set system font map only if there is updatable font directory.
+ // If there is no updatable font directory, `initialize` will have already loaded the
+ // system font map, so there's no need to set the system font map again here.
+ if (mUpdatableFontDir != null) {
+ try {
+ Typeface.setSystemFontMap(getCurrentFontMap());
+ } catch (IOException | ErrnoException e) {
+ Slog.w(TAG, "Failed to set system font map of system_server");
+ }
}
- }
+ serviceStarted.complete(null);
+ }, "FontManagerService_create");
}
@Nullable
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index a7e78be..dd45307 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4855,11 +4855,13 @@
@ServiceThreadOnly
void handleEarcStateChange(int status, int portId) {
assertRunOnServiceThread();
+ int oldEarcStatus = getEarcStatus();
if (!getPortInfo(portId).isEarcSupported()) {
Slog.w(TAG, "Tried to update eARC status on a port that doesn't support eARC.");
+ getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(), oldEarcStatus,
+ status, HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED_UNSUPPORTED_PORT);
return;
}
- int oldEarcStatus = getEarcStatus();
if (mEarcLocalDevice != null) {
mEarcLocalDevice.handleEarcStateChange(status);
getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
@@ -4872,6 +4874,10 @@
startArcAction(true, null);
getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
oldEarcStatus, status, HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED);
+ } else {
+ getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
+ oldEarcStatus, status,
+ HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED_WRONG_STATE);
}
}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 63fded1..7a8de34 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -16,6 +16,7 @@
package com.android.server.input;
+import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT;
import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE;
import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER;
import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
@@ -1272,7 +1273,7 @@
boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype,
noLayoutFound ? null : getKeyboardLayout(layoutInfo.mDescriptor),
- noLayoutFound ? LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+ noLayoutFound ? LAYOUT_SELECTION_CRITERIA_DEFAULT
: layoutInfo.mSelectionCriteria);
}
KeyboardMetricsCollector.logKeyboardConfiguredAtom(configurationEventBuilder.build());
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index 19fa7a8..eb2da34 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -53,7 +53,8 @@
@IntDef(prefix = { "LAYOUT_SELECTION_CRITERIA_" }, value = {
LAYOUT_SELECTION_CRITERIA_USER,
LAYOUT_SELECTION_CRITERIA_DEVICE,
- LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+ LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+ LAYOUT_SELECTION_CRITERIA_DEFAULT
})
public @interface LayoutSelectionCriteria {}
@@ -66,9 +67,15 @@
/** Auto-detection based on IME provided language tag and layout type */
public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 2;
+ /** Default selection */
+ public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 3;
+
@VisibleForTesting
static final String DEFAULT_LAYOUT = "Default";
+ @VisibleForTesting
+ static final String DEFAULT_LANGUAGE_TAG = "None";
+
/**
* Log keyboard system shortcuts for the proto
* {@link com.android.os.input.KeyboardSystemsEventReported}
@@ -131,6 +138,10 @@
layoutConfiguration.keyboardLayoutName);
proto.write(KeyboardLayoutConfig.LAYOUT_SELECTION_CRITERIA,
layoutConfiguration.layoutSelectionCriteria);
+ proto.write(KeyboardLayoutConfig.IME_LANGUAGE_TAG,
+ layoutConfiguration.imeLanguageTag);
+ proto.write(KeyboardLayoutConfig.IME_LAYOUT_TYPE,
+ layoutConfiguration.imeLayoutType);
proto.end(keyboardLayoutConfigToken);
}
@@ -231,27 +242,29 @@
@LayoutSelectionCriteria int layoutSelectionCriteria =
mLayoutSelectionCriteriaList.get(i);
InputMethodSubtype imeSubtype = mImeSubtypeList.get(i);
- String keyboardLanguageTag;
- String keyboardLayoutStringType;
- if (layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE) {
- keyboardLanguageTag = mInputDevice.getKeyboardLanguageTag();
- keyboardLayoutStringType = mInputDevice.getKeyboardLayoutType();
- } else {
- ULocale pkLocale = imeSubtype.getPhysicalKeyboardHintLanguageTag();
- keyboardLanguageTag = pkLocale != null ? pkLocale.toLanguageTag()
- : imeSubtype.getCanonicalizedLanguageTag();
- keyboardLayoutStringType = imeSubtype.getPhysicalKeyboardHintLayoutType();
- }
+ String keyboardLanguageTag = mInputDevice.getKeyboardLanguageTag();
+ keyboardLanguageTag = keyboardLanguageTag == null ? DEFAULT_LANGUAGE_TAG
+ : keyboardLanguageTag;
+ int keyboardLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
+ mInputDevice.getKeyboardLayoutType());
+
+ ULocale pkLocale = imeSubtype.getPhysicalKeyboardHintLanguageTag();
+ String canonicalizedLanguageTag =
+ imeSubtype.getCanonicalizedLanguageTag().equals("")
+ ? DEFAULT_LANGUAGE_TAG : imeSubtype.getCanonicalizedLanguageTag();
+ String imeLanguageTag = pkLocale != null ? pkLocale.toLanguageTag()
+ : canonicalizedLanguageTag;
+ int imeLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
+ imeSubtype.getPhysicalKeyboardHintLayoutType());
+
// Sanitize null values
String keyboardLayoutName =
selectedLayout == null ? DEFAULT_LAYOUT : selectedLayout.getLabel();
- keyboardLanguageTag = keyboardLanguageTag == null ? "" : keyboardLanguageTag;
- int keyboardLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
- keyboardLayoutStringType);
configurationList.add(
new LayoutConfiguration(keyboardLayoutType, keyboardLanguageTag,
- keyboardLayoutName, layoutSelectionCriteria));
+ keyboardLayoutName, layoutSelectionCriteria,
+ imeLayoutType, imeLanguageTag));
}
return new KeyboardConfigurationEvent(mInputDevice, mIsFirstConfiguration,
configurationList);
@@ -267,13 +280,18 @@
public final String keyboardLayoutName;
@LayoutSelectionCriteria
public final int layoutSelectionCriteria;
+ public final int imeLayoutType;
+ public final String imeLanguageTag;
private LayoutConfiguration(int keyboardLayoutType, String keyboardLanguageTag,
- String keyboardLayoutName, @LayoutSelectionCriteria int layoutSelectionCriteria) {
+ String keyboardLayoutName, @LayoutSelectionCriteria int layoutSelectionCriteria,
+ int imeLayoutType, String imeLanguageTag) {
this.keyboardLayoutType = keyboardLayoutType;
this.keyboardLanguageTag = keyboardLanguageTag;
this.keyboardLayoutName = keyboardLayoutName;
this.layoutSelectionCriteria = layoutSelectionCriteria;
+ this.imeLayoutType = imeLayoutType;
+ this.imeLanguageTag = imeLanguageTag;
}
@Override
@@ -281,7 +299,9 @@
return "{keyboardLanguageTag = " + keyboardLanguageTag + " keyboardLayoutType = "
+ KeyboardLayout.LayoutType.getLayoutNameFromValue(keyboardLayoutType)
+ " keyboardLayoutName = " + keyboardLayoutName + " layoutSelectionCriteria = "
- + getStringForSelectionCriteria(layoutSelectionCriteria) + "}";
+ + getStringForSelectionCriteria(layoutSelectionCriteria)
+ + "imeLanguageTag = " + imeLanguageTag + " imeLayoutType = "
+ + KeyboardLayout.LayoutType.getLayoutNameFromValue(imeLayoutType) + "}";
}
}
@@ -294,6 +314,8 @@
return "LAYOUT_SELECTION_CRITERIA_DEVICE";
case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD:
return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD";
+ case LAYOUT_SELECTION_CRITERIA_DEFAULT:
+ return "LAYOUT_SELECTION_CRITERIA_DEFAULT";
default:
return "INVALID_CRITERIA";
}
@@ -302,7 +324,8 @@
private static boolean isValidSelectionCriteria(int layoutSelectionCriteria) {
return layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER
|| layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE
- || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+ || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+ || layoutSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT;
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index fd4176a..b3ef869 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -118,7 +118,6 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.EventLog;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.Slog;
@@ -836,9 +835,6 @@
@Override // binder interface
public void systemReady() {
- if (mContext.checkCallingOrSelfPermission(PERMISSION) != PERMISSION_GRANTED) {
- EventLog.writeEvent(0x534e4554, "28251513", getCallingUid(), ""); // SafetyNet
- }
checkWritePermission();
mHasSecureLockScreen = mContext.getPackageManager()
@@ -847,6 +843,10 @@
getAuthSecretHal();
mDeviceProvisionedObserver.onSystemReady();
+ // Work around an issue in PropertyInvalidatedCache where the cache doesn't work until the
+ // first invalidation. This can be removed if PropertyInvalidatedCache is fixed.
+ LockPatternUtils.invalidateCredentialTypeCache();
+
// TODO: maybe skip this for split system user mode.
mStorage.prefetchUser(UserHandle.USER_SYSTEM);
mBiometricDeferredQueue.systemReady(mInjector.getFingerprintManager(),
@@ -1089,9 +1089,6 @@
}
private final void checkPasswordHavePermission() {
- if (mContext.checkCallingOrSelfPermission(PERMISSION) != PERMISSION_GRANTED) {
- EventLog.writeEvent(0x534e4554, "28251513", getCallingUid(), ""); // SafetyNet
- }
mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsHave");
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1bc681a..2db724f 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -322,7 +322,6 @@
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.PermissionPolicyInternal;
-import com.android.server.powerstats.StatsPullAtomCallbackImpl;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.Slogf;
@@ -1715,7 +1714,6 @@
return;
}
- boolean queryRestart = false;
boolean queryRemove = false;
boolean packageChanged = false;
boolean cancelNotifications = true;
@@ -1727,7 +1725,6 @@
|| (queryRemove=action.equals(Intent.ACTION_PACKAGE_REMOVED))
|| action.equals(Intent.ACTION_PACKAGE_RESTARTED)
|| (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED))
- || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
|| action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)
|| action.equals(Intent.ACTION_PACKAGES_SUSPENDED)
|| action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)
@@ -1768,10 +1765,6 @@
cancelNotifications = false;
unhideNotifications = true;
}
-
- } else if (queryRestart) {
- pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
- uidList = new int[] {intent.getIntExtra(Intent.EXTRA_UID, -1)};
} else {
Uri uri = intent.getData();
if (uri == null) {
@@ -1809,7 +1802,7 @@
if (cancelNotifications) {
for (String pkgName : pkgList) {
cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, null, 0, 0,
- !queryRestart, changeUserId, reason, null);
+ changeUserId, reason);
}
} else if (hideNotifications && uidList != null && (uidList.length > 0)) {
hideNotificationsForPackages(pkgList, uidList);
@@ -1843,14 +1836,14 @@
} else if (action.equals(Intent.ACTION_USER_STOPPED)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userHandle >= 0) {
- cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
- REASON_USER_STOPPED, null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
+ REASON_USER_STOPPED);
}
} else if (action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userHandle >= 0 && !mDpm.isKeepProfilesRunningEnabled()) {
- cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
- REASON_PROFILE_TURNED_OFF, null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
+ REASON_PROFILE_TURNED_OFF);
mSnoozeHelper.clearData(userHandle);
}
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
@@ -2468,7 +2461,6 @@
pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
- pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
pkgFilter.addDataScheme("package");
getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, pkgFilter, null,
null);
@@ -2499,6 +2491,16 @@
getContext().registerReceiver(mReviewNotificationPermissionsReceiver,
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));
+ }
+ });
}
/**
@@ -2855,17 +2857,17 @@
boolean fromListener) {
if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
// cancel
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
- UserHandle.getUserId(uid), REASON_CHANNEL_BANNED,
- null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0,
+ UserHandle.getUserId(uid), REASON_CHANNEL_BANNED
+ );
if (isUidSystemOrPhone(uid)) {
IntArray profileIds = mUserProfiles.getCurrentProfileIds();
int N = profileIds.size();
for (int i = 0; i < N; i++) {
int profileId = profileIds.get(i);
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
- profileId, REASON_CHANNEL_BANNED,
- null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0,
+ profileId, REASON_CHANNEL_BANNED
+ );
}
}
}
@@ -3539,7 +3541,7 @@
// Don't allow the app to cancel active FGS or UIJ notifications
cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
- true, userId, REASON_APP_CANCEL_ALL, null);
+ userId, REASON_APP_CANCEL_ALL);
}
@Override
@@ -3558,20 +3560,16 @@
}
mPermissionHelper.setNotificationPermission(
pkg, UserHandle.getUserId(uid), enabled, true);
- sendAppBlockStateChangedBroadcast(pkg, uid, !enabled);
mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_BAN_APP_NOTES)
.setType(MetricsEvent.TYPE_ACTION)
.setPackageName(pkg)
.setSubtype(enabled ? 1 : 0));
mNotificationChannelLogger.logAppNotificationsAllowed(uid, pkg, enabled);
- // Now, cancel any outstanding notifications that are part of a just-disabled app
- if (!enabled) {
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
- UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
- }
- handleSavePolicyFile();
+ // Outstanding notifications from this package will be cancelled, and the package will
+ // be sent the ACTION_APP_BLOCK_STATE_CHANGED broadcast, as soon as we get the
+ // callback from AppOpsManager.
}
/**
@@ -4030,8 +4028,8 @@
}
enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId);
enforceDeletingChannelHasNoUserInitiatedJob(pkg, callingUser, channelId);
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
- callingUser, REASON_CHANNEL_REMOVED, null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0,
+ callingUser, REASON_CHANNEL_REMOVED);
boolean previouslyExisted = mPreferencesHelper.deleteNotificationChannel(
pkg, callingUid, channelId, callingUid, isSystemOrSystemUi);
if (previouslyExisted) {
@@ -4085,9 +4083,8 @@
for (int i = 0; i < deletedChannels.size(); i++) {
final NotificationChannel deletedChannel = deletedChannels.get(i);
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0,
- true,
- userId, REASON_CHANNEL_REMOVED,
- null);
+ userId, REASON_CHANNEL_REMOVED
+ );
mListeners.notifyNotificationChannelChanged(pkg,
UserHandle.getUserHandleForUid(callingUid),
deletedChannel,
@@ -4256,8 +4253,8 @@
checkCallerIsSystem();
// Cancel posted notifications
final int userId = UserHandle.getUserId(uid);
- cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0, true,
- UserHandle.getUserId(Binder.getCallingUid()), REASON_CLEAR_DATA, null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0,
+ UserHandle.getUserId(Binder.getCallingUid()), REASON_CLEAR_DATA);
// Zen
packagesChanged |=
@@ -5892,6 +5889,21 @@
}
};
+ private void handleNotificationPermissionChange(String pkg, @UserIdInt int userId) {
+ int uid = mPackageManagerInternal.getPackageUid(pkg, 0, userId);
+ if (uid == INVALID_UID) {
+ Log.e(TAG, String.format("No uid found for %s, %s!", pkg, userId));
+ return;
+ }
+ boolean hasPermission = mPermissionHelper.hasPermission(uid);
+ sendAppBlockStateChangedBroadcast(pkg, uid, !hasPermission);
+ if (!hasPermission) {
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, /* channelId= */ null,
+ /* mustHaveFlags= */ 0, /* mustNotHaveFlags= */ 0, userId,
+ REASON_PACKAGE_BANNED);
+ }
+ }
+
protected void checkNotificationListenerAccess() {
if (!isCallerSystemOrPhone()) {
getContext().enforceCallingPermission(
@@ -6828,9 +6840,9 @@
mPreferencesHelper.deleteConversations(pkg, uid, shortcuts,
/* callingUid */ Process.SYSTEM_UID, /* is system */ true);
for (String channelId : deletedChannelIds) {
- cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
- UserHandle.getUserId(uid), REASON_CHANNEL_REMOVED,
- null);
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0,
+ UserHandle.getUserId(uid), REASON_CHANNEL_REMOVED
+ );
}
handleSavePolicyFile();
}
@@ -9532,25 +9544,18 @@
/**
* Cancels all notifications from a given package that have all of the
- * {@code mustHaveFlags}.
+ * {@code mustHaveFlags} and none of the {@code mustNotHaveFlags}.
*/
- void cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, String channelId,
- int mustHaveFlags, int mustNotHaveFlags, boolean doit, int userId, int reason,
- ManagedServiceInfo listener) {
+ void cancelAllNotificationsInt(int callingUid, int callingPid, String pkg,
+ @Nullable String channelId, int mustHaveFlags, int mustNotHaveFlags, int userId,
+ int reason) {
final long cancellationElapsedTimeMs = SystemClock.elapsedRealtime();
mHandler.post(new Runnable() {
@Override
public void run() {
- String listenerName = listener == null ? null : listener.component.toShortString();
EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
- listenerName);
-
- // Why does this parameter exist? Do we actually want to execute the above if doit
- // is false?
- if (!doit) {
- return;
- }
+ /* listener= */ null);
synchronized (mNotificationLock) {
FlagChecker flagChecker = (int flags) -> {
@@ -9562,14 +9567,15 @@
}
return true;
};
- cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
- pkg, true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
+ cancelAllNotificationsByListLocked(mNotificationList, pkg,
+ true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
- listenerName, true /* wasPosted */, cancellationElapsedTimeMs);
- cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
- callingPid, pkg, true /*nullPkgIndicatesUserSwitch*/, channelId,
- flagChecker, false /*includeCurrentProfiles*/, userId,
- false /*sendDelete*/, reason, listenerName, false /* wasPosted */,
+ null /* listenerName */, true /* wasPosted */,
+ cancellationElapsedTimeMs);
+ cancelAllNotificationsByListLocked(mEnqueuedNotifications, pkg,
+ true /*nullPkgIndicatesUserSwitch*/, channelId, flagChecker,
+ false /*includeCurrentProfiles*/, userId, false /*sendDelete*/, reason,
+ null /* listenerName */, false /* wasPosted */,
cancellationElapsedTimeMs);
mSnoozeHelper.cancel(userId, pkg);
}
@@ -9584,9 +9590,9 @@
@GuardedBy("mNotificationLock")
private void cancelAllNotificationsByListLocked(ArrayList<NotificationRecord> notificationList,
- int callingUid, int callingPid, String pkg, boolean nullPkgIndicatesUserSwitch,
- String channelId, FlagChecker flagChecker, boolean includeCurrentProfiles, int userId,
- boolean sendDelete, int reason, String listenerName, boolean wasPosted,
+ @Nullable String pkg, boolean nullPkgIndicatesUserSwitch, @Nullable String channelId,
+ FlagChecker flagChecker, boolean includeCurrentProfiles, int userId, boolean sendDelete,
+ int reason, String listenerName, boolean wasPosted,
@ElapsedRealtimeLong long cancellationElapsedTimeMs) {
Set<String> childNotifications = null;
for (int i = notificationList.size() - 1; i >= 0; --i) {
@@ -9703,12 +9709,12 @@
return true;
};
- cancelAllNotificationsByListLocked(mNotificationList, callingUid, callingPid,
+ cancelAllNotificationsByListLocked(mNotificationList,
null, false /*nullPkgIndicatesUserSwitch*/, null, flagChecker,
includeCurrentProfiles, userId, true /*sendDelete*/, reason,
listenerName, true, cancellationElapsedTimeMs);
- cancelAllNotificationsByListLocked(mEnqueuedNotifications, callingUid,
- callingPid, null, false /*nullPkgIndicatesUserSwitch*/, null,
+ cancelAllNotificationsByListLocked(mEnqueuedNotifications,
+ null, false /*nullPkgIndicatesUserSwitch*/, null,
flagChecker, includeCurrentProfiles, userId, true /*sendDelete*/,
reason, listenerName, false, cancellationElapsedTimeMs);
mSnoozeHelper.cancel(userId, includeCurrentProfiles);
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index f8ee6b0..c7c8136 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -372,7 +372,7 @@
return false;
}
- if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 5000) {
+ if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 10000) {
return false;
}
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 6505e8b..21610c9 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -1225,6 +1225,7 @@
setting.getPackageName(), siblingSetting, settings,
users, USER_ALL, settings.size());
}
+ break;
}
}
diff --git a/services/core/java/com/android/server/pm/ArchiveManager.java b/services/core/java/com/android/server/pm/ArchiveManager.java
index 5082be6..99479f0 100644
--- a/services/core/java/com/android/server/pm/ArchiveManager.java
+++ b/services/core/java/com/android/server/pm/ArchiveManager.java
@@ -16,14 +16,28 @@
package com.android.server.pm;
+import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
import android.content.IntentSender;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
import android.os.Binder;
+import android.os.UserHandle;
import android.text.TextUtils;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.pkg.ArchiveState;
+import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo;
import com.android.server.pm.pkg.PackageStateInternal;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
@@ -35,37 +49,103 @@
*/
final class ArchiveManager {
+ private final Context mContext;
private final PackageManagerService mPm;
- ArchiveManager(PackageManagerService mPm) {
+ @Nullable
+ private LauncherApps mLauncherApps;
+
+ ArchiveManager(Context context, PackageManagerService mPm) {
+ this.mContext = context;
this.mPm = mPm;
}
void archiveApp(
@NonNull String packageName,
@NonNull String callerPackageName,
- int userId,
+ @NonNull UserHandle user,
@NonNull IntentSender intentSender) throws PackageManager.NameNotFoundException {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callerPackageName);
+ Objects.requireNonNull(user);
Objects.requireNonNull(intentSender);
Computer snapshot = mPm.snapshotComputer();
int callingUid = Binder.getCallingUid();
+ int userId = user.getIdentifier();
String callingPackageName = snapshot.getNameForUid(callingUid);
- snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "archiveApp");
+ snapshot.enforceCrossUserPermission(callingUid, userId, true, true,
+ "archiveApp");
verifyCaller(callerPackageName, callingPackageName);
- PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
+ PackageStateInternal ps = getPackageState(packageName, snapshot, callingUid, user);
+ verifyInstallOwnership(packageName, callingPackageName, ps.getInstallSource());
+
+ List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList(
+ ps.getPackageName(),
+ new UserHandle(userId));
+ // TODO(b/291569242) Verify that this list is not empty and return failure with intentsender
+
+ storeArchiveState(ps, mainActivities, userId);
+
+ // TODO(b/278553670) Add special strings for the delete dialog
+ mPm.mInstallerService.uninstall(
+ new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+ callerPackageName, DELETE_KEEP_DATA, intentSender, userId);
+ }
+
+ @NonNull
+ private static PackageStateInternal getPackageState(String packageName,
+ Computer snapshot, int callingUid, UserHandle user)
+ throws PackageManager.NameNotFoundException {
+ PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid,
+ user.getIdentifier());
if (ps == null) {
throw new PackageManager.NameNotFoundException(
TextUtils.formatSimple("Package %s not found.", packageName));
}
+ return ps;
+ }
- verifyInstallOwnership(packageName, callingPackageName, ps);
+ private LauncherApps getLauncherApps() {
+ if (mLauncherApps == null) {
+ mLauncherApps = mContext.getSystemService(LauncherApps.class);
+ }
+ return mLauncherApps;
+ }
- // TODO(b/278553670) Complete implementations
- throw new UnsupportedOperationException("Method not implemented.");
+ private void storeArchiveState(PackageStateInternal ps,
+ List<LauncherActivityInfo> mainActivities, int userId)
+ throws PackageManager.NameNotFoundException {
+ List<ArchiveActivityInfo> activityInfos = new ArrayList<>();
+ for (int i = 0; i < mainActivities.size(); i++) {
+ // TODO(b/278553670) Extract and store launcher icons
+ ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
+ mainActivities.get(i).getLabel().toString(),
+ Path.of("/TODO"), null);
+ activityInfos.add(activityInfo);
+ }
+ // TODO(b/278553670) Adapt installer check verifyInstallOwnership and check for null there
+ InstallSource installSource = ps.getInstallSource();
+ String installerPackageName = installSource.mUpdateOwnerPackageName != null
+ ? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName;
+
+ synchronized (mPm.mLock) {
+ getPackageSetting(ps.getPackageName(), userId).modifyUserState(userId).setArchiveState(
+ new ArchiveState(activityInfos, installerPackageName));
+ }
+ }
+
+ @NonNull
+ @GuardedBy("mPm.mLock")
+ private PackageSetting getPackageSetting(String packageName, int userId)
+ throws PackageManager.NameNotFoundException {
+ PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+ if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) {
+ throw new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("Package %s not found.", packageName));
+ }
+ return ps;
}
private static void verifyCaller(String callerPackageName, String callingPackageName) {
@@ -80,14 +160,14 @@
}
private static void verifyInstallOwnership(String packageName, String callingPackageName,
- PackageStateInternal ps) {
- if (!TextUtils.equals(ps.getInstallSource().mInstallerPackageName,
+ InstallSource installSource) {
+ if (!TextUtils.equals(installSource.mInstallerPackageName,
callingPackageName)) {
throw new SecurityException(
TextUtils.formatSimple("Caller is not the installer of record for %s.",
packageName));
}
- String updateOwnerPackageName = ps.getInstallSource().mUpdateOwnerPackageName;
+ String updateOwnerPackageName = installSource.mUpdateOwnerPackageName;
if (updateOwnerPackageName != null
&& !TextUtils.equals(updateOwnerPackageName, callingPackageName)) {
throw new SecurityException(
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 4963dd4..a5a4594 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -633,7 +633,8 @@
null /*harmfulAppWarning*/,
null /*splashScreenTheme*/,
0 /*firstInstallTime*/,
- PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+ PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
+ null /*archiveState*/);
}
mPm.mSettings.writeKernelMappingLPr(ps);
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7d47762..ead26cc 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -110,6 +110,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.DataLoaderType;
@@ -119,7 +120,6 @@
import android.content.pm.PackageManager;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
-import android.content.pm.ResolveInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
@@ -184,7 +184,9 @@
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.SharedLibraryWrapper;
import com.android.server.pm.pkg.component.ComponentMutateUtils;
+import com.android.server.pm.pkg.component.ParsedActivity;
import com.android.server.pm.pkg.component.ParsedInstrumentation;
+import com.android.server.pm.pkg.component.ParsedIntentInfo;
import com.android.server.pm.pkg.component.ParsedPermission;
import com.android.server.pm.pkg.component.ParsedPermissionGroup;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
@@ -207,6 +209,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -1077,8 +1080,6 @@
final boolean isApex = ((installFlags & PackageManager.INSTALL_APEX) != 0);
final boolean isRollback =
request.getInstallReason() == PackageManager.INSTALL_REASON_ROLLBACK;
- final boolean extractProfile =
- ((installFlags & PackageManager.INSTALL_DONT_EXTRACT_BASELINE_PROFILES) == 0);
@PackageManagerService.ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
if (request.isInstallMove()) {
// moving a complete application; perform an initial scan on the new install location
@@ -1114,9 +1115,7 @@
@ParsingPackageUtils.ParseFlags final int parseFlags =
mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_CHATTY
| ParsingPackageUtils.PARSE_ENFORCE_CODE
- | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0)
- | (extractProfile
- ? ParsingPackageUtils.PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK : 0);
+ | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0);
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
final ParsedPackage parsedPackage;
@@ -1509,29 +1508,34 @@
} else {
// Enable SCAN_NO_DEX flag to skip dexopt at a later stage
scanFlags |= SCAN_NO_DEX;
+ // The native libs of Apex is located in apex_payload.img, don't need to parse it from
+ // the original apex file
+ if (!isApex) {
+ try {
+ PackageSetting pkgSetting;
+ synchronized (mPm.mLock) {
+ pkgSetting = mPm.mSettings.getPackageLPr(pkgName);
+ }
+ boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null
+ && pkgSetting.isUpdatedSystemApp();
+ final String abiOverride = deriveAbiOverride(request.getAbiOverride());
- try {
- PackageSetting pkgSetting;
- synchronized (mPm.mLock) {
- pkgSetting = mPm.mSettings.getPackageLPr(pkgName);
+ // TODO: Are these system flags actually set properly at this stage?
+ boolean isUpdatedSystemAppInferred =
+ pkgSetting != null && pkgSetting.isSystem();
+ final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
+ derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage,
+ systemApp, (isUpdatedSystemAppFromExistingSetting
+ || isUpdatedSystemAppInferred), abiOverride,
+ ScanPackageUtils.getAppLib32InstallDir());
+ derivedAbi.first.applyTo(parsedPackage);
+ derivedAbi.second.applyTo(parsedPackage);
+ } catch (PackageManagerException pme) {
+ Slog.e(TAG, "Error deriving application ABI", pme);
+ throw PrepareFailure.ofInternalError(
+ "Error deriving application ABI: " + pme.getMessage(),
+ PackageManagerException.INTERNAL_ERROR_DERIVING_ABI);
}
- boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null
- && pkgSetting.isUpdatedSystemApp();
- final String abiOverride = deriveAbiOverride(request.getAbiOverride());
-
- // TODO: Are these system flags actually set properly at this stage?
- boolean isUpdatedSystemAppInferred = pkgSetting != null && pkgSetting.isSystem();
- final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
- derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage, systemApp,
- isUpdatedSystemAppFromExistingSetting || isUpdatedSystemAppInferred,
- abiOverride, ScanPackageUtils.getAppLib32InstallDir());
- derivedAbi.first.applyTo(parsedPackage);
- derivedAbi.second.applyTo(parsedPackage);
- } catch (PackageManagerException pme) {
- Slog.e(TAG, "Error deriving application ABI", pme);
- throw PrepareFailure.ofInternalError(
- "Error deriving application ABI: " + pme.getMessage(),
- PackageManagerException.INTERNAL_ERROR_DERIVING_ABI);
}
}
@@ -3935,23 +3939,6 @@
}
}
- // If this is a system app we hadn't seen before, and this is a first boot or OTA,
- // we need to unstop it if it doesn't have a launcher entry.
- if (mPm.mShouldStopSystemPackagesByDefault && scanResult.mRequest.mPkgSetting == null
- && ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0)
- && ((scanFlags & SCAN_AS_SYSTEM) != 0)) {
- final Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
- launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- launcherIntent.setPackage(parsedPackage.getPackageName());
- final List<ResolveInfo> launcherActivities =
- mPm.snapshotComputer().queryIntentActivitiesInternal(launcherIntent, null,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 0);
- if (launcherActivities.isEmpty()) {
- scanResult.mPkgSetting.setStopped(false, 0);
- }
- }
-
if (mIncrementalManager != null && isIncrementalPath(parsedPackage.getPath())) {
if (scanResult.mPkgSetting != null && scanResult.mPkgSetting.isLoading()) {
// Continue monitoring loading progress of active incremental packages
@@ -4324,6 +4311,7 @@
// - It's an APEX or overlay package since stopped state does not affect them.
// - It is enumerated with a <initial-package-state> tag having the stopped attribute
// set to false
+ // - It doesn't have a launcher entry which means the user doesn't have a way to unstop it
final boolean isApexPkg = (scanFlags & SCAN_AS_APEX) != 0;
if (mPm.mShouldStopSystemPackagesByDefault
&& scanSystemPartition
@@ -4332,8 +4320,9 @@
&& !parsedPackage.isOverlayIsStatic()
) {
String packageName = parsedPackage.getPackageName();
- if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName)
- && !"android".contentEquals(packageName)) {
+ if (!"android".contentEquals(packageName)
+ && !mPm.mInitialNonStoppedSystemPackages.contains(packageName)
+ && hasLauncherEntry(parsedPackage)) {
scanFlags |= SCAN_AS_STOPPED_SYSTEM_APP;
}
}
@@ -4343,6 +4332,22 @@
return new Pair<>(scanResult, shouldHideSystemApp);
}
+ private static boolean hasLauncherEntry(ParsedPackage parsedPackage) {
+ final HashSet<String> categories = new HashSet<>();
+ categories.add(Intent.CATEGORY_LAUNCHER);
+ final List<ParsedActivity> activities = parsedPackage.getActivities();
+ for (int indexActivity = 0; indexActivity < activities.size(); indexActivity++) {
+ final List<ParsedIntentInfo> intents = activities.get(indexActivity).getIntents();
+ for (int indexIntent = 0; indexIntent < intents.size(); indexIntent++) {
+ final IntentFilter intentFilter = intents.get(indexIntent).getIntentFilter();
+ if (intentFilter != null && intentFilter.matchCategories(categories) == null) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/**
* Returns if forced apk verification can be skipped for the whole package, including splits.
*/
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 23608c5..6136197 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -461,6 +461,9 @@
private IntentSender mRemoteStatusReceiver;
@GuardedBy("mLock")
+ private IntentSender mPreapprovalRemoteStatusReceiver;
+
+ @GuardedBy("mLock")
private PreapprovalDetails mPreapprovalDetails;
/** Fields derived from commit parsing */
@@ -2040,11 +2043,11 @@
* {@link #setPermissionsResult(boolean)} is called and then
* {@link #MSG_PRE_APPROVAL_REQUEST} is handled to come back here to check again.
*/
- if (sendPendingUserActionIntentIfNeeded()) {
+ if (sendPendingUserActionIntentIfNeeded(/* forPreapproval= */true)) {
return;
}
- dispatchSessionPreappoved();
+ dispatchSessionPreapproved();
}
private final class FileSystemConnector extends
@@ -2499,14 +2502,16 @@
* @return true if the session set requires user action for the installation, otherwise false.
*/
@WorkerThread
- private boolean sendPendingUserActionIntentIfNeeded() {
+ private boolean sendPendingUserActionIntentIfNeeded(boolean forPreapproval) {
// To support pre-approval request of atomic install, we allow child session to handle
// the result by itself since it has the status receiver.
if (isCommitted()) {
assertNotChild("PackageInstallerSession#sendPendingUserActionIntentIfNeeded");
}
-
- final IntentSender statusReceiver = getRemoteStatusReceiver();
+ // Since there are separate status receivers for session preapproval and commit,
+ // check whether user action is requested for session preapproval or commit
+ final IntentSender statusReceiver = forPreapproval ? getPreapprovalRemoteStatusReceiver()
+ : getRemoteStatusReceiver();
return sessionContains(s -> checkUserActionRequirement(s, statusReceiver));
}
@@ -2529,7 +2534,8 @@
* install. Since control may come back here more than 1 time, we must ensure that it's
* value is not overwritten.
*/
- boolean wasUserActionIntentSent = sendPendingUserActionIntentIfNeeded();
+ boolean wasUserActionIntentSent =
+ sendPendingUserActionIntentIfNeeded(/* forPreapproval= */false);
if (mUserActionRequired == null) {
mUserActionRequired = wasUserActionIntentSent;
}
@@ -2591,6 +2597,18 @@
}
}
+ private IntentSender getPreapprovalRemoteStatusReceiver() {
+ synchronized (mLock) {
+ return mPreapprovalRemoteStatusReceiver;
+ }
+ }
+
+ private void setPreapprovalRemoteStatusReceiver(IntentSender remoteStatusReceiver) {
+ synchronized (mLock) {
+ mPreapprovalRemoteStatusReceiver = remoteStatusReceiver;
+ }
+ }
+
/**
* Prepares staged directory with any inherited APKs.
*/
@@ -2828,7 +2846,8 @@
private void onVerificationComplete() {
if (isStaged()) {
mStagingManager.commitSession(mStagedSession);
- sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged", null);
+ sendUpdateToRemoteStatusReceiver(INSTALL_SUCCEEDED, "Session staged",
+ /* extras= */ null, /* forPreapproval= */ false);
return;
}
install();
@@ -4585,7 +4604,11 @@
}
private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
- sendUpdateToRemoteStatusReceiver(returnCode, msg, extras);
+ // Session can be marked as finished due to user rejecting pre approval or commit request,
+ // any internal error or after successful completion. As such, check whether
+ // the session is in the preapproval stage or the commit stage.
+ sendUpdateToRemoteStatusReceiver(returnCode, msg, extras,
+ /* forPreapproval= */ isPreapprovalRequested() && !isCommitted());
synchronized (mLock) {
mFinalStatus = returnCode;
@@ -4607,8 +4630,10 @@
}
}
- private void sendUpdateToRemoteStatusReceiver(int returnCode, String msg, Bundle extras) {
- final IntentSender statusReceiver = getRemoteStatusReceiver();
+ private void sendUpdateToRemoteStatusReceiver(int returnCode, String msg, Bundle extras,
+ boolean forPreapproval) {
+ final IntentSender statusReceiver = forPreapproval ? getPreapprovalRemoteStatusReceiver()
+ : getRemoteStatusReceiver();
if (statusReceiver != null) {
// Execute observer.onPackageInstalled on different thread as we don't want callers
// inside the system server have to worry about catching the callbacks while they are
@@ -4624,8 +4649,8 @@
}
}
- private void dispatchSessionPreappoved() {
- final IntentSender target = getRemoteStatusReceiver();
+ private void dispatchSessionPreapproved() {
+ final IntentSender target = getPreapprovalRemoteStatusReceiver();
final Intent intent = new Intent();
intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_SUCCESS);
@@ -4646,7 +4671,8 @@
if (!mPm.isPreapprovalRequestAvailable()) {
sendUpdateToRemoteStatusReceiver(INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE,
- "Request user pre-approval is currently not available.", null /* extras */);
+ "Request user pre-approval is currently not available.", /* extras= */null,
+ /* preapproval= */true);
return;
}
@@ -4668,7 +4694,7 @@
synchronized (mLock) {
assertPreparedAndNotSealedLocked("request of session " + sessionId);
mPreapprovalDetails = details;
- setRemoteStatusReceiver(statusReceiver);
+ setPreapprovalRemoteStatusReceiver(statusReceiver);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 5cb480c0..8d64bd9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -319,6 +319,8 @@
return runCreateUser();
case "remove-user":
return runRemoveUser();
+ case "mark-guest-for-deletion":
+ return runMarkGuestForDeletion();
case "rename-user":
return runRenameUser();
case "set-user-restriction":
@@ -3203,6 +3205,24 @@
}
}
+ private int runMarkGuestForDeletion() throws RemoteException {
+ String arg = getNextArg();
+ if (arg == null) {
+ getErrPrintWriter().println("Error: no user id specified.");
+ return 1;
+ }
+ int userId = resolveUserId(UserHandle.parseUserArg(arg));
+
+ IUserManager um = IUserManager.Stub.asInterface(
+ ServiceManager.getService(Context.USER_SERVICE));
+ if (!um.markGuestForDeletion(userId)) {
+ getErrPrintWriter().println("Error: could not mark guest for deletion");
+ return 1;
+ }
+
+ return 0;
+ }
+
private int runRenameUser() throws RemoteException {
String arg = getNextArg();
if (arg == null) {
@@ -3436,10 +3456,6 @@
sessionParams.installFlags |=
PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK;
break;
- case "--no-profile":
- sessionParams.installFlags |=
- PackageManager.INSTALL_DONT_EXTRACT_BASELINE_PROFILES;
- break;
default:
throw new IllegalArgumentException("Unknown option " + opt);
}
@@ -4328,7 +4344,7 @@
pw.println(" [--install-reason 0/1/2/3/4] [--originating-uri URI]");
pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]");
pw.println(" [--preload] [--instant] [--full] [--dont-kill]");
- pw.println(" [--enable-rollback] [--no-profile]");
+ pw.println(" [--enable-rollback]");
pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
pw.println(" [--apex] [--force-non-staged] [--staged-ready-timeout TIMEOUT]");
pw.println(" [PATH [SPLIT...]|-]");
@@ -4361,7 +4377,6 @@
pw.println(" --apex: install an .apex file, not an .apk");
pw.println(" --force-non-staged: force the installation to run under a non-staged");
pw.println(" session, which may complete without requiring a reboot");
- pw.println(" --no-profile: don't extract the profiles from the apk");
pw.println(" --staged-ready-timeout: By default, staged sessions wait "
+ DEFAULT_STAGED_READY_TIMEOUT_MS);
pw.println(" milliseconds for pre-reboot verification to complete when");
@@ -4383,7 +4398,7 @@
pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]");
pw.println(" [--preload] [--instant] [--full] [--dont-kill]");
pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [--apex] [-S BYTES]");
- pw.println(" [--multi-package] [--staged] [--no-profile] [--update-ownership]");
+ pw.println(" [--multi-package] [--staged] [--update-ownership]");
pw.println(" Like \"install\", but starts an install session. Use \"install-write\"");
pw.println(" to push data into the session, and \"install-commit\" to finish.");
pw.println("");
@@ -4516,6 +4531,11 @@
pw.println(" switch or reboot)");
pw.println(" --wait: Wait until user is removed. Ignored if set-ephemeral-if-in-use");
pw.println("");
+ pw.println(" mark-guest-for-deletion USER_ID");
+ pw.println(" Mark the guest user for deletion. After this, it is possible to create a");
+ pw.println(" new guest user and switch to it. This allows resetting the guest user");
+ pw.println(" without switching to another user.");
+ pw.println("");
pw.println(" rename-user USER_ID [USER_NAME]");
pw.println(" Rename USER_ID with USER_NAME (or null when [USER_NAME] is not set)");
pw.println("");
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 3e9ccac..494709c 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -48,6 +48,7 @@
import com.android.server.pm.permission.LegacyPermissionDataProvider;
import com.android.server.pm.permission.LegacyPermissionState;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUnserialized;
@@ -171,7 +172,7 @@
@NonNull
private InstallSource installSource;
- /** @see PackageState#getVolumeUuid() */
+ /** @see PackageState#getVolumeUuid() */
@Nullable
private String volumeUuid;
@@ -448,6 +449,7 @@
/**
* Notify {@link #onChanged()} if the parameter {@code usesLibraryFiles} is different from
* {@link #getUsesLibraryFiles()}.
+ *
* @param usesLibraryFiles the new uses library files
* @return {@code this}
*/
@@ -875,7 +877,7 @@
ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
int installReason, int uninstallReason,
String harmfulAppWarning, String splashScreenTheme,
- long firstInstallTime, int aspectRatio) {
+ long firstInstallTime, int aspectRatio, ArchiveState archiveState) {
modifyUserState(userId)
.setSuspendParams(suspendParams)
.setCeDataInode(ceDataInode)
@@ -895,7 +897,8 @@
.setHarmfulAppWarning(harmfulAppWarning)
.setSplashScreenTheme(splashScreenTheme)
.setFirstInstallTimeMillis(firstInstallTime)
- .setMinAspectRatio(aspectRatio);
+ .setMinAspectRatio(aspectRatio)
+ .setArchiveState(archiveState);
onChanged();
}
@@ -913,7 +916,8 @@
? null : otherState.getDisabledComponentsNoCopy().untrackedStorage(),
otherState.getInstallReason(), otherState.getUninstallReason(),
otherState.getHarmfulAppWarning(), otherState.getSplashScreenTheme(),
- otherState.getFirstInstallTimeMillis(), otherState.getMinAspectRatio());
+ otherState.getFirstInstallTimeMillis(), otherState.getMinAspectRatio(),
+ otherState.getArchiveState());
}
WatchedArraySet<String> getEnabledComponents(int userId) {
@@ -1108,10 +1112,36 @@
state.getLastDisableAppCaller());
proto.write(PackageProto.UserInfoProto.FIRST_INSTALL_TIME_MS,
state.getFirstInstallTimeMillis());
+ writeArchiveState(proto, state.getArchiveState());
proto.end(userToken);
}
}
+ private static void writeArchiveState(ProtoOutputStream proto, ArchiveState archiveState) {
+ long archiveStateToken = proto.start(PackageProto.UserInfoProto.ARCHIVE_STATE);
+ for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
+ long activityInfoToken = proto.start(
+ PackageProto.UserInfoProto.ArchiveState.ACTIVITY_INFOS);
+ proto.write(PackageProto.UserInfoProto.ArchiveState.ArchiveActivityInfo.TITLE,
+ activityInfo.getTitle());
+ proto.write(
+ PackageProto.UserInfoProto.ArchiveState.ArchiveActivityInfo.ICON_BITMAP_PATH,
+ activityInfo.getIconBitmap().toAbsolutePath().toString());
+ proto.write(
+ PackageProto
+ .UserInfoProto
+ .ArchiveState
+ .ArchiveActivityInfo
+ .MONOCHROME_ICON_BITMAP_PATH,
+ activityInfo.getMonochromeIconBitmap().toAbsolutePath().toString());
+ proto.end(activityInfoToken);
+ }
+
+ proto.write(PackageProto.UserInfoProto.ArchiveState.INSTALLER_TITLE,
+ archiveState.getInstallerTitle());
+ proto.end(archiveStateToken);
+ }
+
/**
* @see #mPath
*/
@@ -1165,8 +1195,11 @@
}
public PackageSetting setLoadingProgress(float progress) {
- mLoadingProgress = progress;
- onChanged();
+ // To prevent race conditions, we don't allow progress to ever go down
+ if (mLoadingProgress < progress) {
+ mLoadingProgress = progress;
+ onChanged();
+ }
return this;
}
@@ -1599,10 +1632,10 @@
}
@DataClass.Generated(
- time = 1680917079522L,
+ time = 1688743336932L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
- inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+ inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 80277d5..1cd44e6 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -149,6 +149,8 @@
String primaryCpuAbiFromSettings = null;
String secondaryCpuAbiFromSettings = null;
boolean needToDeriveAbi = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
+ boolean isApex = (scanFlags & SCAN_AS_APEX) != 0;
+
if (!needToDeriveAbi) {
if (pkgSetting != null) {
// TODO(b/154610922): if it is not first boot or upgrade, we should directly use
@@ -240,6 +242,7 @@
usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
parsedPackage.getMimeGroups(), newDomainSetId);
}
+
if (createNewPackage && originalPkgSetting != null) {
// This is the initial transition from the original package, so,
// fix up the new package's name now. We must do this after looking
@@ -279,85 +282,91 @@
final boolean isUpdatedSystemApp = pkgSetting.isUpdatedSystemApp();
final File appLib32InstallDir = getAppLib32InstallDir();
- if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
- if (needToDeriveAbi) {
- Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
- try {
- final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
- derivedAbi =
- packageAbiHelper.derivePackageAbi(
- parsedPackage,
- isSystemApp,
- isUpdatedSystemApp,
- cpuAbiOverride,
- appLib32InstallDir);
- derivedAbi.first.applyTo(parsedPackage);
- derivedAbi.second.applyTo(parsedPackage);
- } finally {
- Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
- }
+ // The native libs of Apex is located in apex_payload.img, don't need to parse it from
+ // the original apex file
+ if (!isApex) {
+ if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
+ if (needToDeriveAbi) {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
+ try {
+ final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
+ derivedAbi =
+ packageAbiHelper.derivePackageAbi(
+ parsedPackage,
+ isSystemApp,
+ isUpdatedSystemApp,
+ cpuAbiOverride,
+ appLib32InstallDir);
+ derivedAbi.first.applyTo(parsedPackage);
+ derivedAbi.second.applyTo(parsedPackage);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ }
- // Some system apps still use directory structure for native libraries
- // in which case we might end up not detecting abi solely based on apk
- // structure. Try to detect abi based on directory structure.
+ // Some system apps still use directory structure for native libraries
+ // in which case we might end up not detecting abi solely based on apk
+ // structure. Try to detect abi based on directory structure.
- String pkgRawPrimaryCpuAbi = AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage);
- if (isSystemApp && !isUpdatedSystemApp && pkgRawPrimaryCpuAbi == null) {
- final PackageAbiHelper.Abis abis = packageAbiHelper.getBundledAppAbis(
+ String pkgRawPrimaryCpuAbi = AndroidPackageUtils.getRawPrimaryCpuAbi(
parsedPackage);
- abis.applyTo(parsedPackage);
- abis.applyTo(pkgSetting);
+ if (isSystemApp && !isUpdatedSystemApp && pkgRawPrimaryCpuAbi == null) {
+ final PackageAbiHelper.Abis abis = packageAbiHelper.getBundledAppAbis(
+ parsedPackage);
+ abis.applyTo(parsedPackage);
+ abis.applyTo(pkgSetting);
+ final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
+ packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
+ isSystemApp, isUpdatedSystemApp, appLib32InstallDir);
+ nativeLibraryPaths.applyTo(parsedPackage);
+ }
+ } else {
+ // This is not a first boot or an upgrade, don't bother deriving the
+ // ABI during the scan. Instead, trust the value that was stored in the
+ // package setting.
+ parsedPackage.setPrimaryCpuAbi(primaryCpuAbiFromSettings)
+ .setSecondaryCpuAbi(secondaryCpuAbiFromSettings);
+
final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isSystemApp,
isUpdatedSystemApp, appLib32InstallDir);
nativeLibraryPaths.applyTo(parsedPackage);
+
+ if (DEBUG_ABI_SELECTION) {
+ Slog.i(TAG, "Using ABIS and native lib paths from settings : "
+ + parsedPackage.getPackageName() + " "
+ + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage)
+ + ", "
+ + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage));
+ }
}
} else {
- // This is not a first boot or an upgrade, don't bother deriving the
- // ABI during the scan. Instead, trust the value that was stored in the
- // package setting.
- parsedPackage.setPrimaryCpuAbi(primaryCpuAbiFromSettings)
- .setSecondaryCpuAbi(secondaryCpuAbiFromSettings);
+ if ((scanFlags & SCAN_MOVE) != 0) {
+ // We haven't run dex-opt for this move (since we've moved the compiled output
+ // too) but we already have this packages package info in the PackageSetting.
+ // We just use that and derive the native library path based on the new code
+ // path.
+ parsedPackage.setPrimaryCpuAbi(pkgSetting.getPrimaryCpuAbiLegacy())
+ .setSecondaryCpuAbi(pkgSetting.getSecondaryCpuAbiLegacy());
+ }
+ // Set native library paths again. For moves, the path will be updated based on the
+ // ABIs we've determined above. For non-moves, the path will be updated based on the
+ // ABIs we determined during compilation, but the path will depend on the final
+ // package path (after the rename away from the stage path).
final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isSystemApp,
isUpdatedSystemApp, appLib32InstallDir);
nativeLibraryPaths.applyTo(parsedPackage);
-
- if (DEBUG_ABI_SELECTION) {
- Slog.i(TAG, "Using ABIS and native lib paths from settings : "
- + parsedPackage.getPackageName() + " "
- + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage)
- + ", "
- + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage));
- }
- }
- } else {
- if ((scanFlags & SCAN_MOVE) != 0) {
- // We haven't run dex-opt for this move (since we've moved the compiled output too)
- // but we already have this packages package info in the PackageSetting. We just
- // use that and derive the native library path based on the new code path.
- parsedPackage.setPrimaryCpuAbi(pkgSetting.getPrimaryCpuAbiLegacy())
- .setSecondaryCpuAbi(pkgSetting.getSecondaryCpuAbiLegacy());
}
- // Set native library paths again. For moves, the path will be updated based on the
- // ABIs we've determined above. For non-moves, the path will be updated based on the
- // ABIs we determined during compilation, but the path will depend on the final
- // package path (after the rename away from the stage path).
- final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
- packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isSystemApp,
- isUpdatedSystemApp, appLib32InstallDir);
- nativeLibraryPaths.applyTo(parsedPackage);
- }
-
- // This is a special case for the "system" package, where the ABI is
- // dictated by the zygote configuration (and init.rc). We should keep track
- // of this ABI so that we can deal with "normal" applications that run under
- // the same UID correctly.
- if (isPlatformPackage) {
- parsedPackage.setPrimaryCpuAbi(VMRuntime.getRuntime().is64Bit()
- ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]);
+ // This is a special case for the "system" package, where the ABI is
+ // dictated by the zygote configuration (and init.rc). We should keep track
+ // of this ABI so that we can deal with "normal" applications that run under
+ // the same UID correctly.
+ if (isPlatformPackage) {
+ parsedPackage.setPrimaryCpuAbi(VMRuntime.getRuntime().is64Bit()
+ ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]);
+ }
}
// If there's a mismatch between the abi-override in the package setting
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 60c5852..e5ad01f 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -107,6 +107,7 @@
import com.android.server.pm.permission.LegacyPermissionState;
import com.android.server.pm.permission.LegacyPermissionState.PermissionState;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateInternal;
@@ -153,6 +154,7 @@
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -316,6 +318,8 @@
private static final String TAG_SUSPEND_PARAMS = "suspend-params";
private static final String TAG_MIME_GROUP = "mime-group";
private static final String TAG_MIME_TYPE = "mime-type";
+ private static final String TAG_ARCHIVE_STATE = "archive-state";
+ private static final String TAG_ARCHIVE_ACTIVITY_INFO = "archive-activity-info";
public static final String ATTR_NAME = "name";
public static final String ATTR_PACKAGE = "package";
@@ -362,6 +366,10 @@
private static final String ATTR_DATABASE_VERSION = "databaseVersion";
private static final String ATTR_VALUE = "value";
private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time";
+ private static final String ATTR_ARCHIVE_ACTIVITY_TITLE = "activity-title";
+ private static final String ATTR_ARCHIVE_INSTALLER_TITLE = "installer-title";
+ private static final String ATTR_ARCHIVE_ICON_PATH = "icon-path";
+ private static final String ATTR_ARCHIVE_MONOCHROME_ICON_PATH = "monochrome-icon-path";
private final Handler mHandler;
@@ -1131,7 +1139,8 @@
null /*harmfulAppWarning*/,
null /*splashscreenTheme*/,
0 /*firstInstallTime*/,
- PackageManager.USER_MIN_ASPECT_RATIO_UNSET
+ PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
+ null /*archiveState*/
);
}
}
@@ -1807,7 +1816,8 @@
null /*harmfulAppWarning*/,
null /* splashScreenTheme*/,
0 /*firstInstallTime*/,
- PackageManager.USER_MIN_ASPECT_RATIO_UNSET
+ PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
+ null /*archiveState*/
);
}
return;
@@ -1911,6 +1921,7 @@
PersistableBundle suspendedAppExtras = null;
PersistableBundle suspendedLauncherExtras = null;
SuspendDialogInfo oldSuspendDialogInfo = null;
+ ArchiveState archiveState = null;
int packageDepth = parser.getDepth();
ArrayMap<String, SuspendParams> suspendParamsMap = null;
@@ -1952,6 +1963,9 @@
suspendParamsMap.put(suspendingPackage,
SuspendParams.restoreFromXml(parser));
break;
+ case TAG_ARCHIVE_STATE:
+ archiveState = parseArchiveState(parser);
+ break;
default:
Slog.wtf(TAG, "Unknown tag " + parser.getName() + " under tag "
+ TAG_PACKAGE);
@@ -1982,7 +1996,7 @@
uninstallReason, harmfulAppWarning, splashScreenTheme,
firstInstallTime != 0 ? firstInstallTime :
origFirstInstallTimes.getOrDefault(name, 0L),
- minAspectRatio);
+ minAspectRatio, archiveState);
mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
} else if (tagName.equals("preferred-activities")) {
@@ -2011,6 +2025,64 @@
}
}
+ private static ArchiveState parseArchiveState(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ String installerTitle = parser.getAttributeValue(null,
+ ATTR_ARCHIVE_INSTALLER_TITLE);
+ List<ArchiveState.ArchiveActivityInfo> activityInfos =
+ parseArchiveActivityInfos(parser);
+
+ if (installerTitle == null) {
+ Slog.wtf(TAG, "parseArchiveState: installerTitle is null");
+ return null;
+ }
+
+ if (activityInfos.size() < 1) {
+ Slog.wtf(TAG, "parseArchiveState: activityInfos is empty");
+ return null;
+ }
+
+ return new ArchiveState(activityInfos, installerTitle);
+ }
+
+ private static List<ArchiveState.ArchiveActivityInfo> parseArchiveActivityInfos(
+ TypedXmlPullParser parser) throws XmlPullParserException, IOException {
+ List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
+ int type;
+ int outerDepth = parser.getDepth();
+ String tagName;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG
+ || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ tagName = parser.getName();
+ if (tagName.equals(TAG_ARCHIVE_ACTIVITY_INFO)) {
+ String title = parser.getAttributeValue(null,
+ ATTR_ARCHIVE_ACTIVITY_TITLE);
+ Path iconPath = Path.of(parser.getAttributeValue(null,
+ ATTR_ARCHIVE_ICON_PATH));
+ Path monochromeIconPath = Path.of(parser.getAttributeValue(null,
+ ATTR_ARCHIVE_MONOCHROME_ICON_PATH));
+
+ if (title == null || iconPath == null) {
+ Slog.wtf(TAG,
+ TextUtils.formatSimple("Missing attributes in tag %s. %s: %s, %s: %s",
+ TAG_ARCHIVE_ACTIVITY_INFO, ATTR_ARCHIVE_ACTIVITY_TITLE, title,
+ ATTR_ARCHIVE_ICON_PATH,
+ iconPath));
+ continue;
+ }
+
+ activityInfos.add(
+ new ArchiveState.ArchiveActivityInfo(title, iconPath, monochromeIconPath));
+ }
+ }
+ return activityInfos;
+ }
+
void setBlockUninstallLPw(int userId, String packageName, boolean blockUninstall) {
ArraySet<String> packages = mBlockUninstallPackages.get(userId);
if (blockUninstall) {
@@ -2323,6 +2395,7 @@
}
serializer.endTag(null, TAG_DISABLED_COMPONENTS);
}
+ writeArchiveStateLPr(serializer, ustate.getArchiveState());
serializer.endTag(null, TAG_PACKAGE);
}
@@ -2361,6 +2434,28 @@
}
}
+ private void writeArchiveStateLPr(TypedXmlSerializer serializer, ArchiveState archiveState)
+ throws IOException {
+ if (archiveState == null) {
+ return;
+ }
+
+ serializer.startTag(null, TAG_ARCHIVE_STATE);
+ serializer.attribute(null, ATTR_ARCHIVE_INSTALLER_TITLE, archiveState.getInstallerTitle());
+ for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
+ serializer.startTag(null, TAG_ARCHIVE_ACTIVITY_INFO);
+ serializer.attribute(null, ATTR_ARCHIVE_ACTIVITY_TITLE, activityInfo.getTitle());
+ serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH,
+ activityInfo.getIconBitmap().toAbsolutePath().toString());
+ if (activityInfo.getMonochromeIconBitmap() != null) {
+ serializer.attribute(null, ATTR_ARCHIVE_MONOCHROME_ICON_PATH,
+ activityInfo.getMonochromeIconBitmap().toAbsolutePath().toString());
+ }
+ serializer.endTag(null, TAG_ARCHIVE_ACTIVITY_INFO);
+ }
+ serializer.endTag(null, TAG_ARCHIVE_STATE);
+ }
+
void readInstallPermissionsLPr(TypedXmlPullParser parser,
LegacyPermissionState permissionsState, List<UserInfo> users)
throws IOException, XmlPullParserException {
@@ -5203,6 +5298,10 @@
}
}
}
+ ArchiveState archiveState = userState.getArchiveState();
+ if (archiveState != null) {
+ pw.print(archiveState.toString());
+ }
}
}
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 7609073..b01a89e 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1230,8 +1230,7 @@
}
if (!fromDatasource && !checkPermission(context, permissionManagerServiceInt,
- permission, attributionSource.getUid(),
- attributionSource.getRenouncedPermissions())) {
+ permission, attributionSource)) {
return PermissionChecker.PERMISSION_HARD_DENIED;
}
@@ -1292,12 +1291,11 @@
}
case AppOpsManager.MODE_DEFAULT: {
if (!skipCurrentChecks && !checkPermission(context,
- permissionManagerServiceInt, permission, attributionSource.getUid(),
- attributionSource.getRenouncedPermissions())) {
+ permissionManagerServiceInt, permission, attributionSource)) {
return PermissionChecker.PERMISSION_HARD_DENIED;
}
if (next != null && !checkPermission(context, permissionManagerServiceInt,
- permission, next.getUid(), next.getRenouncedPermissions())) {
+ permission, next)) {
return PermissionChecker.PERMISSION_HARD_DENIED;
}
}
@@ -1326,8 +1324,7 @@
// We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and
// every attributionSource in the chain is registered with the system.
final boolean isChainStartTrusted = !hasChain || checkPermission(context,
- permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current.getUid(),
- current.getRenouncedPermissions());
+ permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current);
while (true) {
final boolean skipCurrentChecks = (fromDatasource || next != null);
@@ -1342,12 +1339,12 @@
// If we already checked the permission for this one, skip the work
if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt,
- permission, current.getUid(), current.getRenouncedPermissions())) {
+ permission, current)) {
return PermissionChecker.PERMISSION_HARD_DENIED;
}
if (next != null && !checkPermission(context, permissionManagerServiceInt,
- permission, next.getUid(), next.getRenouncedPermissions())) {
+ permission, next)) {
return PermissionChecker.PERMISSION_HARD_DENIED;
}
@@ -1415,9 +1412,13 @@
private static boolean checkPermission(@NonNull Context context,
@NonNull PermissionManagerServiceInternal permissionManagerServiceInt,
- @NonNull String permission, int uid, @NonNull Set<String> renouncedPermissions) {
- boolean permissionGranted = context.checkPermission(permission, /*pid*/ -1,
- uid) == PackageManager.PERMISSION_GRANTED;
+ @NonNull String permission, AttributionSource attributionSource) {
+ int uid = attributionSource.getUid();
+ int deviceId = attributionSource.getDeviceId();
+ final Context deviceContext = context.getDeviceId() == deviceId ? context
+ : context.createDeviceContext(deviceId);
+ boolean permissionGranted = deviceContext.checkPermission(permission,
+ Process.INVALID_PID, uid) == PackageManager.PERMISSION_GRANTED;
// Override certain permissions checks for the shared isolated process for both
// HotwordDetectionService and VisualQueryDetectionService, which ordinarily cannot hold
@@ -1433,10 +1434,10 @@
permissionGranted = hotwordServiceProvider != null
&& uid == hotwordServiceProvider.getUid();
}
-
+ Set<String> renouncedPermissions = attributionSource.getRenouncedPermissions();
if (permissionGranted && renouncedPermissions.contains(permission)
- && context.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS,
- /*pid*/ -1, uid) == PackageManager.PERMISSION_GRANTED) {
+ && deviceContext.checkPermission(Manifest.permission.RENOUNCE_PERMISSIONS,
+ Process.INVALID_PID, uid) == PackageManager.PERMISSION_GRANTED) {
return false;
}
return permissionGranted;
@@ -1507,8 +1508,7 @@
// We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and
// every attributionSource in the chain is registered with the system.
final boolean isChainStartTrusted = !hasChain || checkPermission(context,
- permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current.getUid(),
- current.getRenouncedPermissions());
+ permissionManagerServiceInt, UPDATE_APP_OPS_STATS, current);
while (true) {
final boolean skipCurrentChecks = (next != null);
diff --git a/services/core/java/com/android/server/pm/pkg/ArchiveState.java b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
new file mode 100644
index 0000000..d44ae16
--- /dev/null
+++ b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.DataClass;
+
+import java.nio.file.Path;
+import java.util.List;
+
+/**
+ * Information about the state of an archived app. All fields are gathered at the time of
+ * archival.
+ *
+ * @hide
+ */
+@DataClass(genEqualsHashCode = true, genToString = true)
+public class ArchiveState {
+
+ /**
+ * Information about main activities.
+ *
+ * <p> This list has at least one entry. In the vast majority of cases, this list has only one
+ * entry.
+ */
+ @NonNull
+ private final List<ArchiveActivityInfo> mActivityInfos;
+
+ /**
+ * Corresponds to android:label of the installer responsible for the unarchival of the app.
+ * Stored in the installer's locale .
+ */
+ @NonNull
+ private final String mInstallerTitle;
+
+ /** Information about a main activity of an archived app. */
+ @DataClass(genEqualsHashCode = true, genToString = true)
+ public static class ArchiveActivityInfo {
+ /** Corresponds to the activity's android:label in the app's locale. */
+ @NonNull
+ private final String mTitle;
+
+ /** The path to the stored icon of the activity in the app's locale. */
+ @NonNull
+ private final Path mIconBitmap;
+
+ /** See {@link #mIconBitmap}. Only set if the app defined a monochrome icon. */
+ @Nullable
+ private final Path mMonochromeIconBitmap;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new ArchiveActivityInfo.
+ *
+ * @param title
+ * Corresponds to the activity's android:label in the app's locale.
+ * @param iconBitmap
+ * The path to the stored icon of the activity in the app's locale.
+ * @param monochromeIconBitmap
+ * See {@link #mIconBitmap}. Only set if the app defined a monochrome icon.
+ */
+ @DataClass.Generated.Member
+ public ArchiveActivityInfo(
+ @NonNull String title,
+ @NonNull Path iconBitmap,
+ @Nullable Path monochromeIconBitmap) {
+ this.mTitle = title;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTitle);
+ this.mIconBitmap = iconBitmap;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mIconBitmap);
+ this.mMonochromeIconBitmap = monochromeIconBitmap;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Corresponds to the activity's android:label in the app's locale.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * The path to the stored icon of the activity in the app's locale.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Path getIconBitmap() {
+ return mIconBitmap;
+ }
+
+ /**
+ * See {@link #mIconBitmap}. Only set if the app defined a monochrome icon.
+ */
+ @DataClass.Generated.Member
+ public @Nullable Path getMonochromeIconBitmap() {
+ return mMonochromeIconBitmap;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "ArchiveActivityInfo { " +
+ "title = " + mTitle + ", " +
+ "iconBitmap = " + mIconBitmap + ", " +
+ "monochromeIconBitmap = " + mMonochromeIconBitmap +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(ArchiveActivityInfo other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ ArchiveActivityInfo that = (ArchiveActivityInfo) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mTitle, that.mTitle)
+ && java.util.Objects.equals(mIconBitmap, that.mIconBitmap)
+ && java.util.Objects.equals(mMonochromeIconBitmap, that.mMonochromeIconBitmap);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mTitle);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mIconBitmap);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mMonochromeIconBitmap);
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1689169065133L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.NonNull java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+ }
+
+
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new ArchiveState.
+ *
+ * @param activityInfos
+ * Information about main activities.
+ *
+ * <p> This list has at least one entry. In the vast majority of cases, this list has only one
+ * entry.
+ * @param installerTitle
+ * Corresponds to android:label of the installer responsible for the unarchival of the app.
+ * Stored in the installer's locale .
+ */
+ @DataClass.Generated.Member
+ public ArchiveState(
+ @NonNull List<ArchiveActivityInfo> activityInfos,
+ @NonNull String installerTitle) {
+ this.mActivityInfos = activityInfos;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mActivityInfos);
+ this.mInstallerTitle = installerTitle;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mInstallerTitle);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Information about main activities.
+ *
+ * <p> This list has at least one entry. In the vast majority of cases, this list has only one
+ * entry.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<ArchiveActivityInfo> getActivityInfos() {
+ return mActivityInfos;
+ }
+
+ /**
+ * Corresponds to android:label of the installer responsible for the unarchival of the app.
+ * Stored in the installer's locale .
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getInstallerTitle() {
+ return mInstallerTitle;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "ArchiveState { " +
+ "activityInfos = " + mActivityInfos + ", " +
+ "installerTitle = " + mInstallerTitle +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(ArchiveState other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ ArchiveState that = (ArchiveState) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mActivityInfos, that.mActivityInfos)
+ && java.util.Objects.equals(mInstallerTitle, that.mInstallerTitle);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mActivityInfos);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mInstallerTitle);
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1689169065144L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
+ inputSignatures = "private final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.ArchiveActivityInfo> mActivityInfos\nprivate final @android.annotation.NonNull java.lang.String mInstallerTitle\nclass ArchiveState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index 3d056e8..ba274e0 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -380,6 +380,8 @@
@PackageManager.UserMinAspectRatio
private final int mMinAspectRatio;
private final long mFirstInstallTimeMillis;
+ @Nullable
+ private final ArchiveState mArchiveState;
private UserStateImpl(@NonNull PackageUserState userState) {
mCeDataInode = userState.getCeDataInode();
@@ -403,6 +405,7 @@
setBoolean(Booleans.SUSPENDED, userState.isSuspended());
setBoolean(Booleans.VIRTUAL_PRELOAD, userState.isVirtualPreload());
mFirstInstallTimeMillis = userState.getFirstInstallTimeMillis();
+ mArchiveState = userState.getArchiveState();
}
@Override
@@ -556,16 +559,21 @@
}
@DataClass.Generated.Member
+ public @Nullable ArchiveState getArchiveState() {
+ return mArchiveState;
+ }
+
+ @DataClass.Generated.Member
public @NonNull UserStateImpl setBooleans( int value) {
mBooleans = value;
return this;
}
@DataClass.Generated(
- time = 1687938966108L,
+ time = 1689171425723L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
- inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate final long mFirstInstallTimeMillis\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+ inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate final long mFirstInstallTimeMillis\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@Deprecated
private void __metadata() {}
@@ -737,7 +745,7 @@
}
@DataClass.Generated(
- time = 1671671043929L,
+ time = 1689171425753L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy int mHiddenApiEnforcementPolicy\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\nprivate final @android.annotation.Nullable java.lang.String mApexModuleName\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override boolean isApex()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nprivate static final int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
index f75d214..81915b4 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java
@@ -225,4 +225,12 @@
*/
@PackageManager.UserMinAspectRatio
int getMinAspectRatio();
+ /**
+ * Information about the archived state of an app. Set only if an app is archived.
+ *
+ * @hide
+ */
+ @Immutable.Ignore
+ @Nullable
+ ArchiveState getArchiveState();
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
index 1fb12a8..cce18a8 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
@@ -185,4 +185,10 @@
@NonNull ComponentName componentName) {
return null;
}
+
+ @Nullable
+ @Override
+ public ArchiveState getArchiveState() {
+ return null;
+ }
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index d911ac1..6ac7c34 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -101,6 +101,9 @@
@Nullable
private Watchable mWatchable;
+ @Nullable
+ private ArchiveState mArchiveState;
+
@NonNull
final SnapshotCache<PackageUserStateImpl> mSnapshot;
@@ -154,6 +157,7 @@
mComponentLabelIconOverrideMap = other.mComponentLabelIconOverrideMap == null
? null : other.mComponentLabelIconOverrideMap.snapshot();
mFirstInstallTimeMillis = other.mFirstInstallTimeMillis;
+ mArchiveState = other.mArchiveState;
mSnapshot = new SnapshotCache.Sealed<>();
}
@@ -563,6 +567,18 @@
return this;
}
+ /**
+ * Sets the value for {@link #getArchiveState()}.
+ *
+ * @hide
+ */
+ @NonNull
+ public PackageUserStateImpl setArchiveState(@NonNull ArchiveState archiveState) {
+ mArchiveState = archiveState;
+ onChanged();
+ return this;
+ }
+
@NonNull
@Override
public Map<String, OverlayPaths> getSharedLibraryOverlayPaths() {
@@ -720,6 +736,11 @@
}
@DataClass.Generated.Member
+ public @Nullable ArchiveState getArchiveState() {
+ return mArchiveState;
+ }
+
+ @DataClass.Generated.Member
public @NonNull SnapshotCache<PackageUserStateImpl> getSnapshot() {
return mSnapshot;
}
@@ -793,6 +814,7 @@
&& Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap)
&& mFirstInstallTimeMillis == that.mFirstInstallTimeMillis
&& watchableEquals(that.mWatchable)
+ && Objects.equals(mArchiveState, that.mArchiveState)
&& snapshotEquals(that.mSnapshot);
}
@@ -826,15 +848,16 @@
_hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap);
_hash = 31 * _hash + Long.hashCode(mFirstInstallTimeMillis);
_hash = 31 * _hash + watchableHashCode();
+ _hash = 31 * _hash + Objects.hashCode(mArchiveState);
_hash = 31 * _hash + snapshotHashCode();
return _hash;
}
@DataClass.Generated(
- time = 1687938397579L,
+ time = 1689171513404L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
- inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+ inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index d737b1c..e2cb87e 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -50,7 +50,6 @@
import android.content.pm.PackageManager.Property;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
-import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.FrameworkParsingPackageUtils;
import android.content.pm.parsing.PackageLite;
@@ -74,7 +73,6 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.ext.SdkExtensions;
-import android.os.incremental.IncrementalManager;
import android.permission.PermissionManager;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -242,11 +240,6 @@
public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
public static final int PARSE_APK_IN_APEX = 1 << 9;
- /**
- * This flag is to determine whether to extract the baseline profiles from the apk or not.
- */
- public static final int PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK = 1 << 10;
-
public static final int PARSE_CHATTY = 1 << 31;
/** The total maximum number of activities, services, providers and activity-aliases */
@@ -258,16 +251,14 @@
private static final int MAX_PERMISSION_NAME_LENGTH = 512;
@IntDef(flag = true, prefix = { "PARSE_" }, value = {
- PARSE_APK_IN_APEX,
PARSE_CHATTY,
PARSE_COLLECT_CERTIFICATES,
PARSE_ENFORCE_CODE,
PARSE_EXTERNAL_STORAGE,
- PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK,
- PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY,
PARSE_IGNORE_PROCESSES,
PARSE_IS_SYSTEM_DIR,
PARSE_MUST_BE_APK,
+ PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ParseFlags {}
@@ -568,26 +559,6 @@
pkg.setSigningDetails(SigningDetails.UNKNOWN);
}
- // 1. The apkFile is an apk file
- // 2. The flags include PARSE_EXTRACT_PROFILE_FROM_APK
- // 3. The apk patch is NOT an incremental path
- // 4. If the .dm file exists in the current apk directory, it means the caller
- // prepares the .dm file. Don't extract the profiles from the apk again.
- if (ApkLiteParseUtils.isApkFile(apkFile)
- && (flags & PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK) != 0
- && !IncrementalManager.isIncrementalPath(apkPath)
- && DexMetadataHelper.findDexMetadataForFile(apkFile) == null) {
- // Extract the baseline profiles from the apk if the profiles exist in the assets
- // directory in the apk.
- boolean extractedResult =
- DexMetadataHelper.extractBaselineProfilesToDexMetadataFileFromApk(assets,
- apkPath);
-
- if (DEBUG_JAR) {
- Slog.d(TAG, "Extract profiles " + (extractedResult ? "success" : "fail"));
- }
- }
-
return input.success(pkg);
} catch (Exception e) {
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 402bb59..7bbe878 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -6271,10 +6271,10 @@
pw.println(endcallBehaviorToString(mEndcallBehavior));
pw.print(prefix);
// TODO(b/117479243): handle it in InputPolicy
- pw.print("mDisplayHomeButtonHandlers=");
+ pw.println("mDisplayHomeButtonHandlers=");
for (int i = 0; i < mDisplayHomeButtonHandlers.size(); i++) {
final int key = mDisplayHomeButtonHandlers.keyAt(i);
- pw.println(mDisplayHomeButtonHandlers.get(key));
+ pw.print(prefix); pw.print(" "); pw.println(mDisplayHomeButtonHandlers.get(key));
}
pw.print(prefix); pw.print("mKeyguardOccluded="); pw.print(isKeyguardOccluded());
pw.print(" mKeyguardOccludedChanged="); pw.print(mKeyguardOccludedChanged);
@@ -6545,6 +6545,7 @@
private class HdmiVideoExtconUEventObserver extends ExtconStateObserver<Boolean> {
private static final String HDMI_EXIST = "HDMI=1";
+ private static final String DP_EXIST = "DP=1";
private static final String NAME = "hdmi";
private boolean init(ExtconInfo hdmi) {
@@ -6575,7 +6576,8 @@
public Boolean parseState(ExtconInfo extconIfno, String state) {
// extcon event state changes from kernel4.9
// new state will be like STATE=HDMI=1
- return state.contains(HDMI_EXIST);
+ // or like STATE=DP=1 for newer kernel
+ return state.contains(HDMI_EXIST) || state.contains(DP_EXIST);
}
}
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
index 8e78041..3b4b20f 100644
--- a/services/core/java/com/android/server/power/LowPowerStandbyController.java
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -1369,7 +1369,7 @@
* Otherwise, returns false, and the default policy will be used.
*/
public boolean enableCustomPolicy() {
- return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_POLICY, false);
+ return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_POLICY, true);
}
/**
@@ -1377,7 +1377,7 @@
* Otherwise, returns false, and {@link #getActiveStandbyPorts()} will always be empty.
*/
public boolean enableStandbyPorts() {
- return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_STANDBY_PORTS, false);
+ return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_STANDBY_PORTS, true);
}
/**
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index 5ae6973..9529621 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -184,13 +184,7 @@
}
private void loadAllCertificates() {
- // A better alternative to load certificates would be to read from .fs-verity kernel
- // keyring, which fsverity_init loads to during earlier boot time from the same sources
- // below. But since the read operation from keyring is not provided in kernel, we need to
- // duplicate the same loading logic here.
-
// Load certificates trusted by the device manufacturer.
- // NB: Directories need to be synced with system/security/fsverity_init/fsverity_init.cpp.
final String relativeDir = "etc/security/fsverity";
loadCertificatesFromDirectory(Environment.getRootDirectory().toPath()
.resolve(relativeDir));
diff --git a/services/core/java/com/android/server/utils/FoldSettingWrapper.java b/services/core/java/com/android/server/utils/FoldSettingWrapper.java
new file mode 100644
index 0000000..97a1ac0
--- /dev/null
+++ b/services/core/java/com/android/server/utils/FoldSettingWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.utils;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+/**
+ * A wrapper class for the {@link Settings.System#STAY_AWAKE_ON_FOLD} setting.
+ *
+ * This class provides a convenient way to access the {@link Settings.System#STAY_AWAKE_ON_FOLD}
+ * setting for testing.
+ */
+public class FoldSettingWrapper {
+ private final ContentResolver mContentResolver;
+
+ public FoldSettingWrapper(ContentResolver contentResolver) {
+ mContentResolver = contentResolver;
+ }
+
+ /**
+ * Returns whether the device should remain awake after folding.
+ */
+ public boolean shouldStayAwakeOnFold() {
+ try {
+ return (Settings.System.getIntForUser(
+ mContentResolver,
+ Settings.System.STAY_AWAKE_ON_FOLD,
+ 0) == 1);
+ } catch (Settings.SettingNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 25a0a52..7996434 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3629,10 +3629,15 @@
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.wallpaper_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, clientIntent);
- boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn,
- Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
+ int bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
| Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
- | Context.BIND_INCLUDE_CAPABILITIES,
+ | Context.BIND_INCLUDE_CAPABILITIES;
+
+ if (mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_wallpaperTopApp)) {
+ bindFlags |= Context.BIND_SCHEDULE_LIKE_TOP_APP;
+ }
+ boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn, bindFlags,
new UserHandle(serviceUserId));
if (!bindSuccess) {
String msg = "Unable to bind service: " + componentName;
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index c5f63ce..a6d5c19 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -285,9 +285,9 @@
final LaunchingState mLaunchingState;
/** The type can be cold (new process), warm (new activity), or hot (bring to front). */
- final int mTransitionType;
+ int mTransitionType;
/** Whether the process was already running when the transition started. */
- final boolean mProcessRunning;
+ boolean mProcessRunning;
/** whether the process of the launching activity didn't have any active activity. */
final boolean mProcessSwitch;
/** The process state of the launching activity prior to the launch */
@@ -972,6 +972,19 @@
// App isn't attached to record yet, so match with info.
if (info.mLastLaunchedActivity.info.applicationInfo == appInfo) {
info.mBindApplicationDelayMs = info.calculateCurrentDelay();
+ if (info.mProcessRunning) {
+ // It was HOT/WARM launch, but the process was died somehow right after the
+ // launch request.
+ info.mProcessRunning = false;
+ info.mTransitionType = TYPE_TRANSITION_COLD_LAUNCH;
+ final String msg = "Process " + info.mLastLaunchedActivity.info.processName
+ + " restarted";
+ Slog.i(TAG, msg);
+ if (info.mLaunchingState.mTraceName != null) {
+ Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, msg + "#"
+ + LaunchingState.sTraceSeqId);
+ }
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 35ff3ee..ea06b42 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5672,13 +5672,6 @@
final DisplayContent displayContent = getDisplayContent();
if (!visible) {
mImeInsetsFrozenUntilStartInput = true;
- if (usingShellTransitions) {
- final WindowState wallpaperTarget =
- displayContent.mWallpaperController.getWallpaperTarget();
- if (wallpaperTarget != null && wallpaperTarget.mActivityRecord == this) {
- displayContent.mWallpaperController.hideWallpapers(wallpaperTarget);
- }
- }
}
if (!displayContent.mClosingApps.contains(this)
@@ -7998,6 +7991,9 @@
mLastReportedConfiguration.getMergedConfiguration())) {
ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */,
false /* ignoreVisibility */, true /* isRequestedOrientationChanged */);
+ if (mTransitionController.inPlayingTransition(this)) {
+ mTransitionController.mValidateActivityCompat.add(this);
+ }
}
mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged(
@@ -9417,6 +9413,9 @@
if (info.applicationInfo == null) {
return info.getMinAspectRatio();
}
+ if (mLetterboxUiController.shouldApplyUserMinAspectRatioOverride()) {
+ return mLetterboxUiController.getUserMinAspectRatio();
+ }
if (!mLetterboxUiController.shouldOverrideMinAspectRatio()) {
return info.getMinAspectRatio();
}
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 0115877..4ce21bd 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -93,15 +93,12 @@
/** Whether the start transaction of the transition is committed (by shell). */
private boolean mIsStartTransactionCommitted;
- /** Whether all windows should wait for the start transaction. */
- private boolean mAlwaysWaitForStartTransaction;
-
/** Whether the target windows have been requested to sync their draw transactions. */
private boolean mIsSyncDrawRequested;
private SeamlessRotator mRotator;
- private final int mOriginalRotation;
+ private int mOriginalRotation;
private final boolean mHasScreenRotationAnimation;
AsyncRotationController(DisplayContent displayContent) {
@@ -147,15 +144,6 @@
if (mTransitionOp == OP_LEGACY) {
mIsStartTransactionCommitted = true;
} else if (displayContent.mTransitionController.isCollecting(displayContent)) {
- final Transition transition =
- mDisplayContent.mTransitionController.getCollectingTransition();
- if (transition != null) {
- final BLASTSyncEngine.SyncGroup syncGroup =
- mDisplayContent.mWmService.mSyncEngine.getSyncSet(transition.getSyncId());
- if (syncGroup != null && syncGroup.mSyncMethod == BLASTSyncEngine.METHOD_BLAST) {
- mAlwaysWaitForStartTransaction = true;
- }
- }
keepAppearanceInPreviousRotation();
}
}
@@ -279,10 +267,12 @@
// The previous animation leash will be dropped when preparing fade-in animation, so
// simply apply new animation without restoring the transformation.
fadeWindowToken(true /* show */, windowToken, ANIMATION_TYPE_TOKEN_TRANSFORM);
- } else if (op.mAction == Operation.ACTION_SEAMLESS && mRotator != null
+ } else if (op.mAction == Operation.ACTION_SEAMLESS
&& op.mLeash != null && op.mLeash.isValid()) {
if (DEBUG) Slog.d(TAG, "finishOp undo seamless " + windowToken.getTopChild());
- mRotator.setIdentityMatrix(windowToken.getSyncTransaction(), op.mLeash);
+ final SurfaceControl.Transaction t = windowToken.getSyncTransaction();
+ t.setMatrix(op.mLeash, 1, 0, 0, 1);
+ t.setPosition(op.mLeash, 0, 0);
}
}
@@ -365,6 +355,32 @@
}
}
+ /**
+ * Re-initialize the states if the current display rotation has changed to a different rotation.
+ * This is mainly for seamless rotation to update the transform based on new rotation.
+ */
+ void updateRotation() {
+ if (mRotator == null) return;
+ final int currentRotation = mDisplayContent.getWindowConfiguration().getRotation();
+ if (mOriginalRotation == currentRotation) {
+ return;
+ }
+ Slog.d(TAG, "Update original rotation " + currentRotation);
+ mOriginalRotation = currentRotation;
+ mDisplayContent.forAllWindows(w -> {
+ if (w.mForceSeamlesslyRotate && w.mHasSurface
+ && !mTargetWindowTokens.containsKey(w.mToken)) {
+ final Operation op = new Operation(Operation.ACTION_SEAMLESS);
+ op.mLeash = w.mToken.mSurfaceControl;
+ mTargetWindowTokens.put(w.mToken, op);
+ }
+ }, true /* traverseTopToBottom */);
+ mRotator = null;
+ mIsStartTransactionCommitted = false;
+ mIsSyncDrawRequested = false;
+ keepAppearanceInPreviousRotation();
+ }
+
private void scheduleTimeout() {
if (mTimeoutRunnable == null) {
mTimeoutRunnable = () -> {
@@ -589,7 +605,7 @@
* start transaction of rotation transition is applied.
*/
private boolean canDrawBeforeStartTransaction(Operation op) {
- return !mAlwaysWaitForStartTransaction && op.mAction != Operation.ACTION_SEAMLESS;
+ return op.mAction != Operation.ACTION_SEAMLESS;
}
/** The operation to control the rotation appearance associated with window token. */
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index b3a05f1..349d115 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3457,6 +3457,11 @@
this, this, null /* remoteTransition */, displayChange);
if (t != null) {
mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+ if (mAsyncRotationController != null) {
+ // Give a chance to update the transform if the current rotation is changed when
+ // some windows haven't finished previous rotation.
+ mAsyncRotationController.updateRotation();
+ }
if (mFixedRotationLaunchingApp != null) {
// A fixed-rotation transition is done, then continue to start a seamless display
// transition.
@@ -6399,9 +6404,9 @@
// Don't do recursive work.
return;
}
- mInEnsureActivitiesVisible = true;
mAtmService.mTaskSupervisor.beginActivityVisibilityUpdate();
try {
+ mInEnsureActivitiesVisible = true;
forAllRootTasks(rootTask -> {
rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,
notifyClients);
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 7a201a7..e945bc1 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -294,16 +294,15 @@
@NonNull private final SynchedDeviceConfig mDeviceConfig;
LetterboxConfiguration(@NonNull final Context systemUiContext) {
- this(systemUiContext,
- new LetterboxConfigurationPersister(systemUiContext,
- () -> readLetterboxHorizontalReachabilityPositionFromConfig(
- systemUiContext, /* forBookMode */ false),
- () -> readLetterboxVerticalReachabilityPositionFromConfig(
- systemUiContext, /* forTabletopMode */ false),
- () -> readLetterboxHorizontalReachabilityPositionFromConfig(
- systemUiContext, /* forBookMode */ true),
- () -> readLetterboxVerticalReachabilityPositionFromConfig(
- systemUiContext, /* forTabletopMode */ true)));
+ this(systemUiContext, new LetterboxConfigurationPersister(
+ () -> readLetterboxHorizontalReachabilityPositionFromConfig(
+ systemUiContext, /* forBookMode */ false),
+ () -> readLetterboxVerticalReachabilityPositionFromConfig(
+ systemUiContext, /* forTabletopMode */ false),
+ () -> readLetterboxHorizontalReachabilityPositionFromConfig(
+ systemUiContext, /* forBookMode */ true),
+ () -> readLetterboxVerticalReachabilityPositionFromConfig(
+ systemUiContext, /* forTabletopMode */ true)));
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
index 7563397..38aa903 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
@@ -23,7 +23,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.Context;
import android.os.Environment;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
@@ -53,10 +52,8 @@
private static final String TAG =
TAG_WITH_CLASS_NAME ? "LetterboxConfigurationPersister" : TAG_WM;
- @VisibleForTesting
- static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
+ private static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
- private final Context mContext;
private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier;
private final Supplier<Integer> mDefaultVerticalReachabilitySupplier;
private final Supplier<Integer> mDefaultBookModeReachabilitySupplier;
@@ -97,36 +94,32 @@
@NonNull
private final PersisterQueue mPersisterQueue;
- LetterboxConfigurationPersister(Context systemUiContext,
- Supplier<Integer> defaultHorizontalReachabilitySupplier,
- Supplier<Integer> defaultVerticalReachabilitySupplier,
- Supplier<Integer> defaultBookModeReachabilitySupplier,
- Supplier<Integer> defaultTabletopModeReachabilitySupplier) {
- this(systemUiContext, defaultHorizontalReachabilitySupplier,
- defaultVerticalReachabilitySupplier,
- defaultBookModeReachabilitySupplier,
- defaultTabletopModeReachabilitySupplier,
+ LetterboxConfigurationPersister(
+ @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier) {
+ this(defaultHorizontalReachabilitySupplier, defaultVerticalReachabilitySupplier,
+ defaultBookModeReachabilitySupplier, defaultTabletopModeReachabilitySupplier,
Environment.getDataSystemDirectory(), new PersisterQueue(),
- /* completionCallback */ null);
+ /* completionCallback */ null, LETTERBOX_CONFIGURATION_FILENAME);
}
@VisibleForTesting
- LetterboxConfigurationPersister(Context systemUiContext,
- Supplier<Integer> defaultHorizontalReachabilitySupplier,
- Supplier<Integer> defaultVerticalReachabilitySupplier,
- Supplier<Integer> defaultBookModeReachabilitySupplier,
- Supplier<Integer> defaultTabletopModeReachabilitySupplier,
- File configFolder,
- PersisterQueue persisterQueue, @Nullable Consumer<String> completionCallback) {
- mContext = systemUiContext.createDeviceProtectedStorageContext();
+ LetterboxConfigurationPersister(
+ @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier,
+ @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier,
+ @NonNull File configFolder, @NonNull PersisterQueue persisterQueue,
+ @Nullable Consumer<String> completionCallback,
+ @NonNull String letterboxConfigurationFileName) {
mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier;
mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier;
- mDefaultBookModeReachabilitySupplier =
- defaultBookModeReachabilitySupplier;
- mDefaultTabletopModeReachabilitySupplier =
- defaultTabletopModeReachabilitySupplier;
+ mDefaultBookModeReachabilitySupplier = defaultBookModeReachabilitySupplier;
+ mDefaultTabletopModeReachabilitySupplier = defaultTabletopModeReachabilitySupplier;
mCompletionCallback = completionCallback;
- final File prefFiles = new File(configFolder, LETTERBOX_CONFIGURATION_FILENAME);
+ final File prefFiles = new File(configFolder, letterboxConfigurationFileName);
mConfigurationFile = new AtomicFile(prefFiles);
mPersisterQueue = persisterQueue;
runWithDiskReadsThreadPolicy(this::readCurrentConfiguration);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 39f7570..394105a 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -40,6 +40,12 @@
import static android.content.pm.ActivityInfo.isFixedOrientation;
import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
import static android.content.pm.ActivityInfo.screenOrientationToString;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -103,6 +109,7 @@
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.RemoteException;
import android.util.Slog;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -237,6 +244,10 @@
// Counter for ActivityRecord#setRequestedOrientation
private int mSetOrientationRequestCounter = 0;
+ // The min aspect ratio override set by user
+ @PackageManager.UserMinAspectRatio
+ private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
+
// The CompatDisplayInsets of the opaque activity beneath the translucent one.
private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets;
@@ -1059,7 +1070,7 @@
private float getDefaultMinAspectRatioForUnresizableApps() {
if (!mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled()
- || mActivityRecord.getDisplayContent() == null) {
+ || mActivityRecord.getDisplayArea() == null) {
return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
> MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
@@ -1071,8 +1082,8 @@
float getSplitScreenAspectRatio() {
// Getting the same aspect ratio that apps get in split screen.
- final DisplayContent displayContent = mActivityRecord.getDisplayContent();
- if (displayContent == null) {
+ final DisplayArea displayArea = mActivityRecord.getDisplayArea();
+ if (displayArea == null) {
return getDefaultMinAspectRatioForUnresizableApps();
}
int dividerWindowWidth =
@@ -1080,7 +1091,7 @@
int dividerInsets =
getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
int dividerSize = dividerWindowWidth - dividerInsets * 2;
- final Rect bounds = new Rect(displayContent.getWindowConfiguration().getAppBounds());
+ final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
if (bounds.width() >= bounds.height()) {
bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
bounds.right = bounds.centerX();
@@ -1091,14 +1102,57 @@
return computeAspectRatio(bounds);
}
+ boolean shouldApplyUserMinAspectRatioOverride() {
+ if (!mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()) {
+ return false;
+ }
+
+ try {
+ final int userAspectRatio = mActivityRecord.mAtmService.getPackageManager()
+ .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
+ mUserAspectRatio = userAspectRatio;
+ return userAspectRatio != USER_MIN_ASPECT_RATIO_UNSET;
+ } catch (RemoteException e) {
+ // Don't apply user aspect ratio override
+ Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " + this, e);
+ return false;
+ }
+ }
+
+ float getUserMinAspectRatio() {
+ switch (mUserAspectRatio) {
+ case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
+ return getDisplaySizeMinAspectRatio();
+ case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN:
+ return getSplitScreenAspectRatio();
+ case USER_MIN_ASPECT_RATIO_16_9:
+ return 16 / 9f;
+ case USER_MIN_ASPECT_RATIO_4_3:
+ return 4 / 3f;
+ case USER_MIN_ASPECT_RATIO_3_2:
+ return 3 / 2f;
+ default:
+ throw new AssertionError("Unexpected user min aspect ratio override: "
+ + mUserAspectRatio);
+ }
+ }
+
+ private float getDisplaySizeMinAspectRatio() {
+ final DisplayArea displayArea = mActivityRecord.getDisplayArea();
+ if (displayArea == null) {
+ return mActivityRecord.info.getMinAspectRatio();
+ }
+ final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
+ return computeAspectRatio(bounds);
+ }
+
private float getDefaultMinAspectRatio() {
- final DisplayContent displayContent = mActivityRecord.getDisplayContent();
- if (displayContent == null
+ if (mActivityRecord.getDisplayArea() == null
|| !mLetterboxConfiguration
.getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) {
return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
}
- return computeAspectRatio(new Rect(displayContent.getBounds()));
+ return getDisplaySizeMinAspectRatio();
}
Resources getResources() {
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index b71d918b..f33ecaa 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -684,6 +684,26 @@
}
}
+ /**
+ * Removes the oldest recent task that is compatible with the given one. This is possible if
+ * the task windowing mode changed after being added to the Recents.
+ */
+ void removeCompatibleRecentTask(Task task) {
+ final int taskIndex = mTasks.indexOf(task);
+ if (taskIndex < 0) {
+ return;
+ }
+
+ final int candidateIndex = findRemoveIndexForTask(task, false /* includingSelf */);
+ if (candidateIndex == -1) {
+ // Nothing to trim
+ return;
+ }
+
+ final Task taskToRemove = taskIndex > candidateIndex ? task : mTasks.get(candidateIndex);
+ remove(taskToRemove);
+ }
+
void removeTasksByPackageName(String packageName, int userId) {
for (int i = mTasks.size() - 1; i >= 0; --i) {
final Task task = mTasks.get(i);
@@ -1546,6 +1566,10 @@
* list (if any).
*/
private int findRemoveIndexForAddTask(Task task) {
+ return findRemoveIndexForTask(task, true /* includingSelf */);
+ }
+
+ private int findRemoveIndexForTask(Task task, boolean includingSelf) {
final int recentsCount = mTasks.size();
final Intent intent = task.intent;
final boolean document = intent != null && intent.isDocument();
@@ -1601,6 +1625,8 @@
// existing task
continue;
}
+ } else if (!includingSelf) {
+ continue;
}
return i;
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 48420d2..2f0c303 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1849,9 +1849,8 @@
// Don't do recursive work.
return;
}
-
+ mTaskSupervisor.beginActivityVisibilityUpdate();
try {
- mTaskSupervisor.beginActivityVisibilityUpdate();
// First the front root tasks. In case any are not fullscreen and are in front of home.
for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
final DisplayContent display = getChildAt(displayNdx);
@@ -3226,6 +3225,10 @@
+ "not idle", rootTask.getRootTaskId(), resumedActivity);
return false;
}
+ if (mTransitionController.isTransientLaunch(resumedActivity)) {
+ // Not idle if the transient transition animation is running.
+ return false;
+ }
}
// End power mode launch when idle.
mService.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index c914fa1..fe3094e 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -293,26 +293,7 @@
throw new SecurityException(msg);
}
// Check if the caller is allowed to launch on the specified display area.
- final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();
- TaskDisplayArea taskDisplayArea = daToken != null
- ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;
-
- // If we do not have a task display area token, check if the launch task display area
- // feature id is specified.
- if (taskDisplayArea == null) {
- final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId();
- if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
- final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY
- ? DEFAULT_DISPLAY : options.getLaunchDisplayId();
- final DisplayContent dc = supervisor.mRootWindowContainer
- .getDisplayContent(launchDisplayId);
- if (dc != null) {
- taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda ->
- tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null);
- }
- }
- }
-
+ final TaskDisplayArea taskDisplayArea = getLaunchTaskDisplayArea(options, supervisor);
if (aInfo != null && taskDisplayArea != null
&& !supervisor.isCallerAllowedToLaunchOnTaskDisplayArea(callingPid, callingUid,
taskDisplayArea, aInfo)) {
@@ -428,6 +409,32 @@
}
}
+ @VisibleForTesting
+ TaskDisplayArea getLaunchTaskDisplayArea(ActivityOptions options,
+ ActivityTaskSupervisor supervisor) {
+ final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();
+ TaskDisplayArea taskDisplayArea = daToken != null
+ ? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;
+ if (taskDisplayArea != null) {
+ return taskDisplayArea;
+ }
+
+ // If we do not have a task display area token, check if the launch task display area
+ // feature id is specified.
+ final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId();
+ if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
+ final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY
+ ? DEFAULT_DISPLAY : options.getLaunchDisplayId();
+ final DisplayContent dc = supervisor.mRootWindowContainer
+ .getDisplayContent(launchDisplayId);
+ if (dc != null) {
+ taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda ->
+ tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null);
+ }
+ }
+ return taskDisplayArea;
+ }
+
private boolean isAssistant(ActivityTaskManagerService atmService, int callingUid) {
if (atmService.mActiveVoiceInteractionServiceComponent == null) {
return false;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 358ee87..a143540 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1191,16 +1191,6 @@
hasParticipatedDisplay = true;
continue;
}
- final WallpaperWindowToken wt = participant.asWallpaperToken();
- if (wt != null) {
- final boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(wt);
- if (!visibleAtTransitionEnd && !wt.isVisibleRequested()) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " Commit wallpaper becoming invisible: %s", wt);
- wt.commitVisibility(false /* visible */);
- }
- continue;
- }
final Task tr = participant.asTask();
if (tr != null && tr.isVisibleRequested() && tr.inPinnedWindowingMode()) {
final ActivityRecord top = tr.getTopNonFinishingActivity();
@@ -1220,6 +1210,20 @@
}
}
}
+ // Commit wallpaper visibility after activity, because usually the wallpaper target token is
+ // an activity, and wallpaper's visibility is depends on activity's visibility.
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
+ if (wt == null) continue;
+ final WindowState target = wt.mDisplayContent.mWallpaperController.getWallpaperTarget();
+ final boolean isTargetInvisible = target == null || !target.mToken.isVisible();
+ if (isTargetInvisible || (!wt.isVisibleRequested()
+ && !mVisibleAtTransitionEndTokens.contains(wt))) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " Commit wallpaper becoming invisible: %s", wt);
+ wt.commitVisibility(false /* visible */);
+ }
+ }
if (committedSomeInvisible) {
mController.onCommittedInvisibles();
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 881eddc..bfaf6fc 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -30,6 +30,7 @@
import android.app.ActivityManager;
import android.app.IApplicationThread;
import android.app.WindowConfiguration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
import android.os.IBinder;
@@ -141,6 +142,14 @@
final ArrayList<ActivityRecord> mValidateCommitVis = new ArrayList<>();
/**
+ * List of activity-level participants. ActivityRecord is not expected to change independently,
+ * however, recent compatibility logic can now cause this at arbitrary times determined by
+ * client code. If it happens during an animation, the surface can be left at the wrong spot.
+ * TODO(b/290237710) remove when compat logic is moved.
+ */
+ final ArrayList<ActivityRecord> mValidateActivityCompat = new ArrayList<>();
+
+ /**
* Currently playing transitions (in the order they were started). When finished, records are
* removed from this list.
*/
@@ -896,6 +905,14 @@
}
}
mValidateCommitVis.clear();
+ for (int i = 0; i < mValidateActivityCompat.size(); ++i) {
+ ActivityRecord ar = mValidateActivityCompat.get(i);
+ if (ar.getSurfaceControl() == null) continue;
+ final Point tmpPos = new Point();
+ ar.getRelativePosition(tmpPos);
+ ar.getSyncTransaction().setPosition(ar.getSurfaceControl(), tmpPos.x, tmpPos.y);
+ }
+ mValidateActivityCompat.clear();
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0eb452d..164c8b0 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1615,6 +1615,7 @@
final int count = tasksToReparent.size();
for (int i = 0; i < count; ++i) {
final Task task = tasksToReparent.get(i);
+ final int prevWindowingMode = task.getWindowingMode();
if (syncId >= 0) {
addToSyncSet(syncId, task);
}
@@ -1628,6 +1629,12 @@
hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
false /*moveParents*/, "processChildrenTaskReparentHierarchyOp");
}
+ // Trim the compatible Recent task (if any) after the Task is reparented and now has
+ // a different windowing mode, in order to prevent redundant Recent tasks after
+ // reparenting.
+ if (prevWindowingMode != task.getWindowingMode()) {
+ mService.mTaskSupervisor.mRecentTasks.removeCompatibleRecentTask(task);
+ }
}
if (transition != null) transition.collect(newParent);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7857b25..77f8de5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -24132,7 +24132,7 @@
// When listener is added onSubscriptionsChanged gets called immediately for once
// (even if subscriptions are not changed) and later on when subscriptions changes.
subscriptionManager.addOnSubscriptionsChangedListener(
- mSubscriptionsChangedListener.getHandlerExecutor(),
+ mHandler::post,
mSubscriptionsChangedListener);
} finally {
mInjector.binderRestoreCallingIdentity(id);
diff --git a/services/flags/Android.bp b/services/flags/Android.bp
new file mode 100644
index 0000000..2d0337dc
--- /dev/null
+++ b/services/flags/Android.bp
@@ -0,0 +1,17 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library_static {
+ name: "services.flags",
+ defaults: ["platform_service_defaults"],
+ srcs: [
+ "java/**/*.java",
+ ],
+ libs: ["services.core"],
+}
diff --git a/services/flags/OWNERS b/services/flags/OWNERS
new file mode 100644
index 0000000..3925b5c
--- /dev/null
+++ b/services/flags/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1306523
+
+mankoff@google.com
+
+pixel@google.com
+dsandler@android.com
diff --git a/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java b/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java
new file mode 100644
index 0000000..0db3287
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/DynamicFlagBinderDelegate.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import android.annotation.NonNull;
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.os.BackgroundThread;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * Handles DynamicFlags for {@link FeatureFlagsBinder}.
+ *
+ * Dynamic flags are simultaneously simpler and more complicated than process stable flags. We can
+ * return whatever value is last known for a flag is, without too much worry about the flags
+ * changing (they are dynamic after all). However, we have to alert all the relevant clients
+ * about those flag changes, and need to be able to restore to a default value if the flag gets
+ * reset/erased during runtime.
+ */
+class DynamicFlagBinderDelegate {
+
+ private final FlagOverrideStore mFlagStore;
+ private final FlagCache<DynamicFlagData> mDynamicFlags = new FlagCache<>();
+ private final Map<Integer, Set<IFeatureFlagsCallback>> mCallbacks = new HashMap<>();
+ private static final Function<Integer, Set<IFeatureFlagsCallback>> NEW_CALLBACK_SET =
+ k -> new HashSet<>();
+
+ private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener =
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+ String ns = properties.getNamespace();
+ for (String name : properties.getKeyset()) {
+ // Don't alert for flags we don't care about.
+ // Don't alert for flags that have been overridden locally.
+ if (!mDynamicFlags.contains(ns, name) || mFlagStore.contains(ns, name)) {
+ continue;
+ }
+ mFlagChangeCallback.onFlagChanged(
+ ns, name, properties.getString(name, null));
+ }
+ }
+ };
+
+ private final FlagOverrideStore.FlagChangeCallback mFlagChangeCallback =
+ (namespace, name, value) -> {
+ // Don't bother with callbacks for non-dynamic flags.
+ if (!mDynamicFlags.contains(namespace, name)) {
+ return;
+ }
+
+ // Don't bother with callbacks if nothing changed.
+ // Handling erasure (null) is special, as we may be restoring back to a value
+ // we were already at.
+ DynamicFlagData data = mDynamicFlags.getOrNull(namespace, name);
+ if (data == null) {
+ return; // shouldn't happen, but better safe than sorry.
+ }
+ if (value == null) {
+ if (data.getValue().equals(data.getDefaultValue())) {
+ return;
+ }
+ value = data.getDefaultValue();
+ } else if (data.getValue().equals(value)) {
+ return;
+ }
+ data.setValue(value);
+
+ final Set<IFeatureFlagsCallback> cbCopy;
+ synchronized (mCallbacks) {
+ cbCopy = new HashSet<>();
+
+ for (Integer pid : mCallbacks.keySet()) {
+ if (data.containsPid(pid)) {
+ cbCopy.addAll(mCallbacks.get(pid));
+ }
+ }
+ }
+ SyncableFlag sFlag = new SyncableFlag(namespace, name, value, true);
+ cbCopy.forEach(cb -> {
+ try {
+ cb.onFlagChange(sFlag);
+ } catch (RemoteException e) {
+ Slog.w(
+ FeatureFlagsService.TAG,
+ "Failed to communicate flag change to client.");
+ }
+ });
+ };
+
+ DynamicFlagBinderDelegate(FlagOverrideStore flagStore) {
+ mFlagStore = flagStore;
+ mFlagStore.setChangeCallback(mFlagChangeCallback);
+ }
+
+ SyncableFlag syncDynamicFlag(int pid, SyncableFlag sf) {
+ if (!sf.isDynamic()) {
+ return sf;
+ }
+
+ String ns = sf.getNamespace();
+ String name = sf.getName();
+
+ // Dynamic flags don't need any special threading or synchronization considerations.
+ // We simply give them whatever the current value is.
+ // However, we do need to keep track of dynamic flags, so that we can alert
+ // about changes coming in from adb, DeviceConfig, or other sources.
+ // And also so that we can keep flags relatively consistent across processes.
+
+ DynamicFlagData data = mDynamicFlags.getOrNull(ns, name);
+ String value = getFlagValue(ns, name, sf.getValue());
+ // DeviceConfig listeners are per-namespace.
+ if (!mDynamicFlags.containsNamespace(ns)) {
+ DeviceConfig.addOnPropertiesChangedListener(
+ ns, BackgroundThread.getExecutor(), mDeviceConfigListener);
+ }
+ data.addClientPid(pid);
+ data.setValue(value);
+ // Store the default value so that if an override gets erased, we can restore
+ // to something.
+ data.setDefaultValue(sf.getValue());
+
+ return new SyncableFlag(sf.getNamespace(), sf.getName(), value, true);
+ }
+
+
+ void registerCallback(int pid, IFeatureFlagsCallback callback) {
+ // Always add callback so that we don't end up with a possible race/leak.
+ // We remove the callback directly if we fail to call #linkToDeath.
+ // If we tried to add the callback after we linked, then we could end up in a
+ // scenario where we link, then the binder dies, firing our BinderGriever which tries
+ // to remove the callback (which has not yet been added), then finally we add the
+ // callback, creating a leak.
+ Set<IFeatureFlagsCallback> callbacks;
+ synchronized (mCallbacks) {
+ callbacks = mCallbacks.computeIfAbsent(pid, NEW_CALLBACK_SET);
+ callbacks.add(callback);
+ }
+ try {
+ callback.asBinder().linkToDeath(new BinderGriever(pid), 0);
+ } catch (RemoteException e) {
+ Slog.e(
+ FeatureFlagsService.TAG,
+ "Failed to link to binder death. Callback not registered.");
+ synchronized (mCallbacks) {
+ callbacks.remove(callback);
+ }
+ }
+ }
+
+ void unregisterCallback(int pid, IFeatureFlagsCallback callback) {
+ // No need to unlink, since the BinderGriever will essentially be a no-op.
+ // We would have to track our BinderGriever's in a map otherwise.
+ synchronized (mCallbacks) {
+ Set<IFeatureFlagsCallback> callbacks =
+ mCallbacks.computeIfAbsent(pid, NEW_CALLBACK_SET);
+ callbacks.remove(callback);
+ }
+ }
+
+ String getFlagValue(String namespace, String name, String defaultValue) {
+ // If we already have a value cached, just use that.
+ String value = null;
+ DynamicFlagData data = mDynamicFlags.getOrNull(namespace, name);
+ if (data != null) {
+ value = data.getValue();
+ } else {
+ // Put the value in the cache for future reference.
+ data = new DynamicFlagData(namespace, name);
+ mDynamicFlags.setIfChanged(namespace, name, data);
+ }
+ // If we're not in a release build, flags can be overridden locally on device.
+ if (!Build.IS_USER && value == null) {
+ value = mFlagStore.get(namespace, name);
+ }
+ // If we still don't have a value, maybe DeviceConfig does?
+ // Fallback to sf.getValue() here as well.
+ if (value == null) {
+ value = DeviceConfig.getString(namespace, name, defaultValue);
+ }
+
+ return value;
+ }
+
+ private static class DynamicFlagData {
+ private final String mNamespace;
+ private final String mName;
+ private final Set<Integer> mPids = new HashSet<>();
+ private String mValue;
+ private String mDefaultValue;
+
+ private DynamicFlagData(String namespace, String name) {
+ mNamespace = namespace;
+ mName = name;
+ }
+
+ String getValue() {
+ return mValue;
+ }
+
+ void setValue(String value) {
+ mValue = value;
+ }
+
+ String getDefaultValue() {
+ return mDefaultValue;
+ }
+
+ void setDefaultValue(String value) {
+ mDefaultValue = value;
+ }
+
+ void addClientPid(int pid) {
+ mPids.add(pid);
+ }
+
+ boolean containsPid(int pid) {
+ return mPids.contains(pid);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == null || !(other instanceof DynamicFlagData)) {
+ return false;
+ }
+
+ DynamicFlagData o = (DynamicFlagData) other;
+
+ return mName.equals(o.mName) && mNamespace.equals(o.mNamespace)
+ && mValue.equals(o.mValue) && mDefaultValue.equals(o.mDefaultValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return mName.hashCode() + mNamespace.hashCode()
+ + mValue.hashCode() + mDefaultValue.hashCode();
+ }
+ }
+
+
+ private class BinderGriever implements IBinder.DeathRecipient {
+ private final int mPid;
+
+ private BinderGriever(int pid) {
+ mPid = pid;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(mPid);
+ }
+ }
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java b/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java
new file mode 100644
index 0000000..1fa8532
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FeatureFlagsBinder.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.flags.IFeatureFlags;
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+
+import com.android.internal.flags.CoreFlags;
+import com.android.server.flags.FeatureFlagsService.PermissionsChecker;
+
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+class FeatureFlagsBinder extends IFeatureFlags.Stub {
+ private final FlagOverrideStore mFlagStore;
+ private final FlagsShellCommand mShellCommand;
+ private final FlagCache<String> mFlagCache = new FlagCache<>();
+ private final DynamicFlagBinderDelegate mDynamicFlagDelegate;
+ private final PermissionsChecker mPermissionsChecker;
+
+ FeatureFlagsBinder(
+ FlagOverrideStore flagStore,
+ FlagsShellCommand shellCommand,
+ PermissionsChecker permissionsChecker) {
+ mFlagStore = flagStore;
+ mShellCommand = shellCommand;
+ mDynamicFlagDelegate = new DynamicFlagBinderDelegate(flagStore);
+ mPermissionsChecker = permissionsChecker;
+ }
+
+ @Override
+ public void registerCallback(IFeatureFlagsCallback callback) {
+ mDynamicFlagDelegate.registerCallback(getCallingPid(), callback);
+ }
+
+ @Override
+ public void unregisterCallback(IFeatureFlagsCallback callback) {
+ mDynamicFlagDelegate.unregisterCallback(getCallingPid(), callback);
+ }
+
+ // Note: The internals of this method should be kept in sync with queryFlags
+ // as they both should return identical results. The difference is that this method
+ // caches any values it receives and/or reads, whereas queryFlags does not.
+
+ @Override
+ public List<SyncableFlag> syncFlags(List<SyncableFlag> incomingFlags) {
+ int pid = getCallingPid();
+ List<SyncableFlag> outputFlags = new ArrayList<>();
+
+ boolean hasFullSyncPrivileges = false;
+ SecurityException permissionFailureException = null;
+ try {
+ assertSyncPermission();
+ hasFullSyncPrivileges = true;
+ } catch (SecurityException e) {
+ permissionFailureException = e;
+ }
+
+ for (SyncableFlag sf : incomingFlags) {
+ if (!hasFullSyncPrivileges && !CoreFlags.isCoreFlag(sf)) {
+ throw permissionFailureException;
+ }
+
+ String ns = sf.getNamespace();
+ String name = sf.getName();
+ SyncableFlag outFlag;
+ if (sf.isDynamic()) {
+ outFlag = mDynamicFlagDelegate.syncDynamicFlag(pid, sf);
+ } else {
+ synchronized (mFlagCache) {
+ String value = mFlagCache.getOrNull(ns, name);
+ if (value == null) {
+ String overrideValue = Build.IS_USER ? null : mFlagStore.get(ns, name);
+ value = overrideValue != null ? overrideValue : sf.getValue();
+ mFlagCache.setIfChanged(ns, name, value);
+ }
+ outFlag = new SyncableFlag(sf.getNamespace(), sf.getName(), value, false);
+ }
+ }
+ outputFlags.add(outFlag);
+ }
+ return outputFlags;
+ }
+
+ @Override
+ public void overrideFlag(SyncableFlag flag) {
+ assertWritePermission();
+ mFlagStore.set(flag.getNamespace(), flag.getName(), flag.getValue());
+ }
+
+ @Override
+ public void resetFlag(SyncableFlag flag) {
+ assertWritePermission();
+ mFlagStore.erase(flag.getNamespace(), flag.getName());
+ }
+
+ @Override
+ public List<SyncableFlag> queryFlags(List<SyncableFlag> incomingFlags) {
+ assertSyncPermission();
+ List<SyncableFlag> outputFlags = new ArrayList<>();
+ for (SyncableFlag sf : incomingFlags) {
+ String ns = sf.getNamespace();
+ String name = sf.getName();
+ String value;
+ String storeValue = mFlagStore.get(ns, name);
+ boolean overridden = storeValue != null;
+
+ if (sf.isDynamic()) {
+ value = mDynamicFlagDelegate.getFlagValue(ns, name, sf.getValue());
+ } else {
+ value = mFlagCache.getOrNull(ns, name);
+ if (value == null) {
+ value = Build.IS_USER ? null : storeValue;
+ if (value == null) {
+ value = sf.getValue();
+ }
+ }
+ }
+ outputFlags.add(new SyncableFlag(
+ sf.getNamespace(), sf.getName(), value, sf.isDynamic(), overridden));
+ }
+
+ return outputFlags;
+ }
+
+ private void assertSyncPermission() {
+ mPermissionsChecker.assertSyncPermission();
+ clearCallingIdentity();
+ }
+
+ private void assertWritePermission() {
+ mPermissionsChecker.assertWritePermission();
+ clearCallingIdentity();
+ }
+
+
+ @SystemApi
+ public int handleShellCommand(
+ @NonNull ParcelFileDescriptor in,
+ @NonNull ParcelFileDescriptor out,
+ @NonNull ParcelFileDescriptor err,
+ @NonNull String[] args) {
+ FileOutputStream fout = new FileOutputStream(out.getFileDescriptor());
+ FileOutputStream ferr = new FileOutputStream(err.getFileDescriptor());
+
+ return mShellCommand.process(args, fout, ferr);
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FeatureFlagsService.java b/services/flags/java/com/android/server/flags/FeatureFlagsService.java
new file mode 100644
index 0000000..93b9e9e
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FeatureFlagsService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.flags;
+
+import static android.Manifest.permission.SYNC_FLAGS;
+import static android.Manifest.permission.WRITE_FLAGS;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.flags.FeatureFlags;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+/**
+ * A service that manages syncing {@link android.flags.FeatureFlags} across processes.
+ *
+ * This service holds flags stable for at least the lifetime of a process, meaning that if
+ * a process comes online with a flag set to true, any other process that connects here and
+ * tries to read the same flag will also receive the flag as true. The flag will remain stable
+ * until either all of the interested processes have died, or the device restarts.
+ *
+ * TODO(279054964): Add to dumpsys
+ * @hide
+ */
+public class FeatureFlagsService extends SystemService {
+
+ static final String TAG = "FeatureFlagsService";
+ private final FlagOverrideStore mFlagStore;
+ private final FlagsShellCommand mShellCommand;
+
+ /**
+ * Initializes the system service.
+ *
+ * @param context The system server context.
+ */
+ public FeatureFlagsService(Context context) {
+ super(context);
+ mFlagStore = new FlagOverrideStore(
+ new GlobalSettingsProxy(context.getContentResolver()));
+ mShellCommand = new FlagsShellCommand(mFlagStore);
+ }
+
+ @Override
+ public void onStart() {
+ Slog.d(TAG, "Started Feature Flag Service");
+ FeatureFlagsBinder service = new FeatureFlagsBinder(
+ mFlagStore, mShellCommand, new PermissionsChecker(getContext()));
+ publishBinderService(
+ Context.FEATURE_FLAGS_SERVICE, service);
+ publishLocalService(FeatureFlags.class, new FeatureFlags(service));
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ super.onBootPhase(phase);
+
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ // Immediately sync our core flags so that they get locked in. We don't want third-party
+ // apps to override them, and syncing immediately is the easiest way to prevent that.
+ FeatureFlags.getInstance().sync();
+ }
+ }
+
+ /**
+ * Delegate for checking flag permissions.
+ */
+ @VisibleForTesting
+ public static class PermissionsChecker {
+ private final Context mContext;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public PermissionsChecker(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Ensures that the caller has {@link SYNC_FLAGS} permission.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void assertSyncPermission() {
+ if (mContext.checkCallingOrSelfPermission(SYNC_FLAGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Non-core flag queried. Requires SYNC_FLAGS permission!");
+ }
+ }
+
+ /**
+ * Ensures that the caller has {@link WRITE_FLAGS} permission.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public void assertWritePermission() {
+ if (mContext.checkCallingPermission(WRITE_FLAGS) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires WRITE_FLAGS permission!");
+ }
+ }
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagCache.java b/services/flags/java/com/android/server/flags/FlagCache.java
new file mode 100644
index 0000000..cee1578
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagCache.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Threadsafe cache of values that stores the supplied default on cache miss.
+ *
+ * @param <V> The type of value to store.
+ */
+public class FlagCache<V> {
+ private final Function<String, HashMap<String, V>> mNewHashMap = k -> new HashMap<>();
+
+ // Cache is organized first by namespace, then by name. All values are stored as strings.
+ final Map<String, Map<String, V>> mCache = new HashMap<>();
+
+ FlagCache() {
+ }
+
+ /**
+ * Returns true if the namespace exists in the cache already.
+ */
+ boolean containsNamespace(String namespace) {
+ synchronized (mCache) {
+ return mCache.containsKey(namespace);
+ }
+ }
+
+ /**
+ * Returns true if the value is stored in the cache.
+ */
+ boolean contains(String namespace, String name) {
+ synchronized (mCache) {
+ Map<String, V> nsCache = mCache.get(namespace);
+ return nsCache != null && nsCache.containsKey(name);
+ }
+ }
+
+ /**
+ * Sets the value if it is different from what is currently stored.
+ *
+ * If the value is not set, or the current value is null, it will store the value and
+ * return true.
+ *
+ * @return True if the value was set. False if the value is the same.
+ */
+ boolean setIfChanged(String namespace, String name, V value) {
+ synchronized (mCache) {
+ Map<String, V> nsCache = mCache.computeIfAbsent(namespace, mNewHashMap);
+ V curValue = nsCache.get(name);
+ if (curValue == null || !curValue.equals(value)) {
+ nsCache.put(name, value);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Gets the current value from the cache, setting it if it is currently absent.
+ *
+ * @return The value that is now in the cache after the call to the method.
+ */
+ V getOrSet(String namespace, String name, V defaultValue) {
+ synchronized (mCache) {
+ Map<String, V> nsCache = mCache.computeIfAbsent(namespace, mNewHashMap);
+ V value = nsCache.putIfAbsent(name, defaultValue);
+ return value == null ? defaultValue : value;
+ }
+ }
+
+ /**
+ * Gets the current value from the cache, returning null if not present.
+ *
+ * @return The value that is now in the cache if there is one.
+ */
+ V getOrNull(String namespace, String name) {
+ synchronized (mCache) {
+ Map<String, V> nsCache = mCache.get(namespace);
+ if (nsCache == null) {
+ return null;
+ }
+ return nsCache.get(name);
+ }
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagOverrideStore.java b/services/flags/java/com/android/server/flags/FlagOverrideStore.java
new file mode 100644
index 0000000..b1ddc7e6
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagOverrideStore.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import android.database.Cursor;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Persistent storage for the {@link FeatureFlagsService}.
+ *
+ * The implementation stores data in Settings.<store> (generally {@link Settings.Global}
+ * is expected).
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class FlagOverrideStore {
+ private static final String KEYNAME_PREFIX = "flag|";
+ private static final String NAMESPACE_NAME_SEPARATOR = ".";
+
+ private final SettingsProxy mSettingsProxy;
+
+ private FlagChangeCallback mCallback;
+
+ FlagOverrideStore(SettingsProxy settingsProxy) {
+ mSettingsProxy = settingsProxy;
+ }
+
+ void setChangeCallback(FlagChangeCallback callback) {
+ mCallback = callback;
+ }
+
+ /** Returns true if a non-null value is in the store. */
+ boolean contains(String namespace, String name) {
+ return get(namespace, name) != null;
+ }
+
+ /** Put a value in the store. */
+ @VisibleForTesting
+ public void set(String namespace, String name, String value) {
+ mSettingsProxy.putString(getPropName(namespace, name), value);
+ mCallback.onFlagChanged(namespace, name, value);
+ }
+
+ /** Read a value out of the store. */
+ @VisibleForTesting
+ public String get(String namespace, String name) {
+ return mSettingsProxy.getString(getPropName(namespace, name));
+ }
+
+ /** Erase a value from the store. */
+ @VisibleForTesting
+ public void erase(String namespace, String name) {
+ set(namespace, name, null);
+ }
+
+ Map<String, Map<String, String>> getFlags() {
+ return getFlagsForNamespace(null);
+ }
+
+ Map<String, Map<String, String>> getFlagsForNamespace(String namespace) {
+ Cursor c = mSettingsProxy.getContentResolver().query(
+ Settings.Global.CONTENT_URI,
+ new String[]{Settings.NameValueTable.NAME, Settings.NameValueTable.VALUE},
+ null, // Doesn't support a "LIKE" query
+ null,
+ null
+ );
+
+ if (c == null) {
+ return Map.of();
+ }
+ int keynamePrefixLength = KEYNAME_PREFIX.length();
+ Map<String, Map<String, String>> results = new HashMap<>();
+ while (c.moveToNext()) {
+ String key = c.getString(0);
+ if (!key.startsWith(KEYNAME_PREFIX)
+ || key.indexOf(NAMESPACE_NAME_SEPARATOR, keynamePrefixLength) < 0) {
+ continue;
+ }
+ String value = c.getString(1);
+ if (value == null || value.isEmpty()) {
+ continue;
+ }
+ String ns = key.substring(keynamePrefixLength, key.indexOf(NAMESPACE_NAME_SEPARATOR));
+ if (namespace != null && !namespace.equals(ns)) {
+ continue;
+ }
+ String name = key.substring(key.indexOf(NAMESPACE_NAME_SEPARATOR) + 1);
+ results.putIfAbsent(ns, new HashMap<>());
+ results.get(ns).put(name, value);
+ }
+ c.close();
+ return results;
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static String getPropName(String namespace, String name) {
+ return KEYNAME_PREFIX + namespace + NAMESPACE_NAME_SEPARATOR + name;
+ }
+
+ interface FlagChangeCallback {
+ void onFlagChanged(String namespace, String name, String value);
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/FlagsShellCommand.java b/services/flags/java/com/android/server/flags/FlagsShellCommand.java
new file mode 100644
index 0000000..b7896ee
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/FlagsShellCommand.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Process command line input for the flags service.
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class FlagsShellCommand {
+ private final FlagOverrideStore mFlagStore;
+
+ FlagsShellCommand(FlagOverrideStore flagStore) {
+ mFlagStore = flagStore;
+ }
+
+ /**
+ * Interpret the command supplied in the constructor.
+ *
+ * @return Zero on success or non-zero on error.
+ */
+ public int process(
+ String[] args,
+ OutputStream out,
+ OutputStream err) {
+ PrintWriter outPw = new FastPrintWriter(out);
+ PrintWriter errPw = new FastPrintWriter(err);
+
+ if (args.length == 0) {
+ return printHelp(outPw);
+ }
+ switch (args[0].toLowerCase(Locale.ROOT)) {
+ case "help":
+ return printHelp(outPw);
+ case "list":
+ return listCmd(args, outPw, errPw);
+ case "set":
+ return setCmd(args, outPw, errPw);
+ case "get":
+ return getCmd(args, outPw, errPw);
+ case "erase":
+ return eraseCmd(args, outPw, errPw);
+ default:
+ return unknownCmd(outPw);
+ }
+ }
+
+ private int printHelp(PrintWriter outPw) {
+ outPw.println("Feature Flags command, allowing listing, setting, getting, and erasing of");
+ outPw.println("local flag overrides on a device.");
+ outPw.println();
+ outPw.println("Commands:");
+ outPw.println(" list [namespace]");
+ outPw.println(" List all flag overrides. Namespace is optional.");
+ outPw.println();
+ outPw.println(" get <namespace> <name>");
+ outPw.println(" Return the string value of a specific flag, or <unset>");
+ outPw.println();
+ outPw.println(" set <namespace> <name> <value>");
+ outPw.println(" Set a specific flag");
+ outPw.println();
+ outPw.println(" erase <namespace> <name>");
+ outPw.println(" Unset a specific flag");
+ outPw.flush();
+ return 0;
+ }
+
+ private int listCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+ if (!validateNumArguments(args, 0, 1, args[0], errPw)) {
+ errPw.println("Expected `" + args[0] + " [namespace]`");
+ errPw.flush();
+ return -1;
+ }
+ Map<String, Map<String, String>> overrides;
+ if (args.length == 2) {
+ overrides = mFlagStore.getFlagsForNamespace(args[1]);
+ } else {
+ overrides = mFlagStore.getFlags();
+ }
+ if (overrides.isEmpty()) {
+ outPw.println("No overrides set");
+ } else {
+ int longestNamespaceLen = "namespace".length();
+ int longestFlagLen = "flag".length();
+ int longestValLen = "value".length();
+ for (Map.Entry<String, Map<String, String>> namespace : overrides.entrySet()) {
+ longestNamespaceLen = Math.max(longestNamespaceLen, namespace.getKey().length());
+ for (Map.Entry<String, String> flag : namespace.getValue().entrySet()) {
+ longestFlagLen = Math.max(longestFlagLen, flag.getKey().length());
+ longestValLen = Math.max(longestValLen, flag.getValue().length());
+ }
+ }
+ outPw.print(String.format("%-" + longestNamespaceLen + "s", "namespace"));
+ outPw.print(' ');
+ outPw.print(String.format("%-" + longestFlagLen + "s", "flag"));
+ outPw.print(' ');
+ outPw.println("value");
+ for (int i = 0; i < longestNamespaceLen; i++) {
+ outPw.print('=');
+ }
+ outPw.print(' ');
+ for (int i = 0; i < longestFlagLen; i++) {
+ outPw.print('=');
+ }
+ outPw.print(' ');
+ for (int i = 0; i < longestValLen; i++) {
+ outPw.print('=');
+ }
+ outPw.println();
+ for (Map.Entry<String, Map<String, String>> namespace : overrides.entrySet()) {
+ for (Map.Entry<String, String> flag : namespace.getValue().entrySet()) {
+ outPw.print(
+ String.format("%-" + longestNamespaceLen + "s", namespace.getKey()));
+ outPw.print(' ');
+ outPw.print(String.format("%-" + longestFlagLen + "s", flag.getKey()));
+ outPw.print(' ');
+ outPw.println(flag.getValue());
+ }
+ }
+ }
+ outPw.flush();
+ return 0;
+ }
+
+ private int setCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+ if (!validateNumArguments(args, 3, args[0], errPw)) {
+ errPw.println("Expected `" + args[0] + " <namespace> <name> <value>`");
+ errPw.flush();
+ return -1;
+ }
+ mFlagStore.set(args[1], args[2], args[3]);
+ outPw.println("Flag " + args[1] + "." + args[2] + " is now " + args[3]);
+ outPw.flush();
+ return 0;
+ }
+
+ private int getCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+ if (!validateNumArguments(args, 2, args[0], errPw)) {
+ errPw.println("Expected `" + args[0] + " <namespace> <name>`");
+ errPw.flush();
+ return -1;
+ }
+
+ String value = mFlagStore.get(args[1], args[2]);
+ outPw.print(args[1] + "." + args[2] + " is ");
+ if (value == null || value.isEmpty()) {
+ outPw.println("<unset>");
+ } else {
+ outPw.println("\"" + value.translateEscapes() + "\"");
+ }
+ outPw.flush();
+ return 0;
+ }
+
+ private int eraseCmd(String[] args, PrintWriter outPw, PrintWriter errPw) {
+ if (!validateNumArguments(args, 2, args[0], errPw)) {
+ errPw.println("Expected `" + args[0] + " <namespace> <name>`");
+ errPw.flush();
+ return -1;
+ }
+ mFlagStore.erase(args[1], args[2]);
+ outPw.println("Erased " + args[1] + "." + args[2]);
+ return 0;
+ }
+
+ private int unknownCmd(PrintWriter outPw) {
+ outPw.println("This command is unknown.");
+ printHelp(outPw);
+ outPw.flush();
+ return -1;
+ }
+
+ private boolean validateNumArguments(
+ String[] args, int exactly, String cmdName, PrintWriter errPw) {
+ return validateNumArguments(args, exactly, exactly, cmdName, errPw);
+ }
+
+ private boolean validateNumArguments(
+ String[] args, int min, int max, String cmdName, PrintWriter errPw) {
+ int len = args.length - 1; // Discount the command itself.
+ if (len < min) {
+ errPw.println(
+ "Less than " + min + " arguments provided for \"" + cmdName + "\" command.");
+ return false;
+ } else if (len > max) {
+ errPw.println(
+ "More than " + max + " arguments provided for \"" + cmdName + "\" command.");
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java b/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java
new file mode 100644
index 0000000..acb7bb5
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/GlobalSettingsProxy.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.Settings;
+
+class GlobalSettingsProxy implements SettingsProxy {
+ private final ContentResolver mContentResolver;
+
+ GlobalSettingsProxy(ContentResolver contentResolver) {
+ mContentResolver = contentResolver;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mContentResolver;
+ }
+
+ @Override
+ public Uri getUriFor(String name) {
+ return Settings.Global.getUriFor(name);
+ }
+
+ @Override
+ public String getStringForUser(String name, int userHandle) {
+ return Settings.Global.getStringForUser(mContentResolver, name, userHandle);
+ }
+
+ @Override
+ public boolean putString(String name, String value, boolean overrideableByRestore) {
+ throw new UnsupportedOperationException(
+ "This method only exists publicly for Settings.System and Settings.Secure");
+ }
+
+ @Override
+ public boolean putStringForUser(String name, String value, int userHandle) {
+ return Settings.Global.putStringForUser(mContentResolver, name, value, userHandle);
+ }
+
+ @Override
+ public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
+ int userHandle, boolean overrideableByRestore) {
+ return Settings.Global.putStringForUser(
+ mContentResolver, name, value, tag, makeDefault, userHandle,
+ overrideableByRestore);
+ }
+
+ @Override
+ public boolean putString(String name, String value, String tag, boolean makeDefault) {
+ return Settings.Global.putString(mContentResolver, name, value, tag, makeDefault);
+ }
+}
diff --git a/services/flags/java/com/android/server/flags/SettingsProxy.java b/services/flags/java/com/android/server/flags/SettingsProxy.java
new file mode 100644
index 0000000..c6e85d5
--- /dev/null
+++ b/services/flags/java/com/android/server/flags/SettingsProxy.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings;
+
+/**
+ * Wrapper class meant to enable hermetic testing of {@link Settings}.
+ *
+ * Implementations of this class are expected to be constructed with a {@link ContentResolver} or,
+ * otherwise have access to an implicit one. All the proxy methods in this class exclude
+ * {@link ContentResolver} from their signature and rely on an internally defined one instead.
+ *
+ * Most methods in the {@link Settings} classes have default implementations defined.
+ * Implementations of this interfac need only concern themselves with getting and putting Strings.
+ * They should also override any methods for a class they are proxying that _are not_ defined, and
+ * throw an appropriate {@link UnsupportedOperationException}. For instance, {@link Settings.Global}
+ * does not define {@link #putString(String, String, boolean)}, so an implementation of this
+ * interface that proxies through to it should throw an exception when that method is called.
+ *
+ * This class adds in the following helpers as well:
+ * - {@link #getBool(String)}
+ * - {@link #putBool(String, boolean)}
+ * - {@link #registerContentObserver(Uri, ContentObserver)}
+ *
+ * ... and similar variations for all of those.
+ */
+public interface SettingsProxy {
+
+ /**
+ * Returns the {@link ContentResolver} this instance uses.
+ */
+ ContentResolver getContentResolver();
+
+ /**
+ * Construct the content URI for a particular name/value pair,
+ * useful for monitoring changes with a ContentObserver.
+ * @param name to look up in the table
+ * @return the corresponding content URI, or null if not present
+ */
+ Uri getUriFor(String name);
+
+ /**See {@link Settings.Secure#getString(ContentResolver, String)} */
+ String getStringForUser(String name, int userHandle);
+
+ /**See {@link Settings.Secure#putString(ContentResolver, String, String, boolean)} */
+ boolean putString(String name, String value, boolean overrideableByRestore);
+
+ /** See {@link Settings.Secure#putStringForUser(ContentResolver, String, String, int)} */
+ boolean putStringForUser(String name, String value, int userHandle);
+
+ /**
+ * See {@link Settings.Secure#putStringForUser(ContentResolver, String, String, String, boolean,
+ * int, boolean)}
+ */
+ boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag,
+ boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore);
+
+ /** See {@link Settings.Secure#putString(ContentResolver, String, String, String, boolean)} */
+ boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag,
+ boolean makeDefault);
+
+ /**
+ * Returns the user id for the associated {@link ContentResolver}.
+ */
+ default int getUserId() {
+ return getContentResolver().getUserId();
+ }
+
+ /** See {@link Settings.Secure#getString(ContentResolver, String)} */
+ default String getString(String name) {
+ return getStringForUser(name, getUserId());
+ }
+
+ /** See {@link Settings.Secure#putString(ContentResolver, String, String)} */
+ default boolean putString(String name, String value) {
+ return putStringForUser(name, value, getUserId());
+ }
+ /** See {@link Settings.Secure#getIntForUser(ContentResolver, String, int, int)} */
+ default int getIntForUser(String name, int def, int userHandle) {
+ String v = getStringForUser(name, userHandle);
+ try {
+ return v != null ? Integer.parseInt(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
+
+ /** See {@link Settings.Secure#getInt(ContentResolver, String)} */
+ default int getInt(String name) throws Settings.SettingNotFoundException {
+ return getIntForUser(name, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getIntForUser(ContentResolver, String, int)} */
+ default int getIntForUser(String name, int userHandle)
+ throws Settings.SettingNotFoundException {
+ String v = getStringForUser(name, userHandle);
+ try {
+ return Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ throw new Settings.SettingNotFoundException(name);
+ }
+ }
+
+ /** See {@link Settings.Secure#putInt(ContentResolver, String, int)} */
+ default boolean putInt(String name, int value) {
+ return putIntForUser(name, value, getUserId());
+ }
+
+ /** See {@link Settings.Secure#putIntForUser(ContentResolver, String, int, int)} */
+ default boolean putIntForUser(String name, int value, int userHandle) {
+ return putStringForUser(name, Integer.toString(value), userHandle);
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a boolean. Note that internally setting values are always
+ * stored as strings; this function converts the string to a boolean
+ * for you. The default value will be returned if the setting is
+ * not defined or not a boolean.
+ *
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid boolean.
+ */
+ default boolean getBool(String name, boolean def) {
+ return getBoolForUser(name, def, getUserId());
+ }
+
+ /** See {@link #getBool(String, boolean)}. */
+ default boolean getBoolForUser(String name, boolean def, int userHandle) {
+ return getIntForUser(name, def ? 1 : 0, userHandle) != 0;
+ }
+
+ /**
+ * Convenience function for retrieving a single settings value
+ * as a boolean. Note that internally setting values are always
+ * stored as strings; this function converts the string to a boolean
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link Settings.SettingNotFoundException}.
+ *
+ * @param name The name of the setting to retrieve.
+ *
+ * @throws Settings.SettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not a boolean.
+ *
+ * @return The setting's current value.
+ */
+ default boolean getBool(String name) throws Settings.SettingNotFoundException {
+ return getBoolForUser(name, getUserId());
+ }
+
+ /** See {@link #getBool(String)}. */
+ default boolean getBoolForUser(String name, int userHandle)
+ throws Settings.SettingNotFoundException {
+ return getIntForUser(name, userHandle) != 0;
+ }
+
+ /**
+ * Convenience function for updating a single settings value as a
+ * boolean. This will either create a new entry in the table if the
+ * given name does not exist, or modify the value of the existing row
+ * with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a
+ * string before storing it.
+ *
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ default boolean putBool(String name, boolean value) {
+ return putBoolForUser(name, value, getUserId());
+ }
+
+ /** See {@link #putBool(String, boolean)}. */
+ default boolean putBoolForUser(String name, boolean value, int userHandle) {
+ return putIntForUser(name, value ? 1 : 0, userHandle);
+ }
+
+ /** See {@link Settings.Secure#getLong(ContentResolver, String, long)} */
+ default long getLong(String name, long def) {
+ return getLongForUser(name, def, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getLongForUser(ContentResolver, String, long, int)} */
+ default long getLongForUser(String name, long def, int userHandle) {
+ String valString = getStringForUser(name, userHandle);
+ long value;
+ try {
+ value = valString != null ? Long.parseLong(valString) : def;
+ } catch (NumberFormatException e) {
+ value = def;
+ }
+ return value;
+ }
+
+ /** See {@link Settings.Secure#getLong(ContentResolver, String)} */
+ default long getLong(String name) throws Settings.SettingNotFoundException {
+ return getLongForUser(name, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getLongForUser(ContentResolver, String, int)} */
+ default long getLongForUser(String name, int userHandle)
+ throws Settings.SettingNotFoundException {
+ String valString = getStringForUser(name, userHandle);
+ try {
+ return Long.parseLong(valString);
+ } catch (NumberFormatException e) {
+ throw new Settings.SettingNotFoundException(name);
+ }
+ }
+
+ /** See {@link Settings.Secure#putLong(ContentResolver, String, long)} */
+ default boolean putLong(String name, long value) {
+ return putLongForUser(name, value, getUserId());
+ }
+
+ /** See {@link Settings.Secure#putLongForUser(ContentResolver, String, long, int)} */
+ default boolean putLongForUser(String name, long value, int userHandle) {
+ return putStringForUser(name, Long.toString(value), userHandle);
+ }
+
+ /** See {@link Settings.Secure#getFloat(ContentResolver, String, float)} */
+ default float getFloat(String name, float def) {
+ return getFloatForUser(name, def, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getFloatForUser(ContentResolver, String, int)} */
+ default float getFloatForUser(String name, float def, int userHandle) {
+ String v = getStringForUser(name, userHandle);
+ try {
+ return v != null ? Float.parseFloat(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
+
+
+ /** See {@link Settings.Secure#getFloat(ContentResolver, String)} */
+ default float getFloat(String name) throws Settings.SettingNotFoundException {
+ return getFloatForUser(name, getUserId());
+ }
+
+ /** See {@link Settings.Secure#getFloatForUser(ContentResolver, String, int)} */
+ default float getFloatForUser(String name, int userHandle)
+ throws Settings.SettingNotFoundException {
+ String v = getStringForUser(name, userHandle);
+ if (v == null) {
+ throw new Settings.SettingNotFoundException(name);
+ }
+ try {
+ return Float.parseFloat(v);
+ } catch (NumberFormatException e) {
+ throw new Settings.SettingNotFoundException(name);
+ }
+ }
+
+ /** See {@link Settings.Secure#putFloat(ContentResolver, String, float)} */
+ default boolean putFloat(String name, float value) {
+ return putFloatForUser(name, value, getUserId());
+ }
+
+ /** See {@link Settings.Secure#putFloatForUser(ContentResolver, String, float, int)} */
+ default boolean putFloatForUser(String name, float value, int userHandle) {
+ return putStringForUser(name, Float.toString(value), userHandle);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+ *
+ * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+ */
+ default void registerContentObserver(String name, ContentObserver settingsObserver) {
+ registerContentObserver(getUriFor(name), settingsObserver);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+ */
+ default void registerContentObserver(Uri uri, ContentObserver settingsObserver) {
+ registerContentObserverForUser(uri, settingsObserver, getUserId());
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.
+ *
+ * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+ */
+ default void registerContentObserver(String name, boolean notifyForDescendants,
+ ContentObserver settingsObserver) {
+ registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
+ */
+ default void registerContentObserver(Uri uri, boolean notifyForDescendants,
+ ContentObserver settingsObserver) {
+ registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId());
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+ *
+ * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+ */
+ default void registerContentObserverForUser(
+ String name, ContentObserver settingsObserver, int userHandle) {
+ registerContentObserverForUser(
+ getUriFor(name), settingsObserver, userHandle);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+ */
+ default void registerContentObserverForUser(
+ Uri uri, ContentObserver settingsObserver, int userHandle) {
+ registerContentObserverForUser(
+ uri, false, settingsObserver, userHandle);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+ *
+ * Implicitly calls {@link #getUriFor(String)} on the passed in name.
+ */
+ default void registerContentObserverForUser(
+ String name, boolean notifyForDescendants, ContentObserver settingsObserver,
+ int userHandle) {
+ registerContentObserverForUser(
+ getUriFor(name), notifyForDescendants, settingsObserver, userHandle);
+ }
+
+ /**
+ * Convenience wrapper around
+ * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
+ */
+ default void registerContentObserverForUser(
+ Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
+ int userHandle) {
+ getContentResolver().registerContentObserver(
+ uri, notifyForDescendants, settingsObserver, userHandle);
+ }
+
+ /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */
+ default void unregisterContentObserver(ContentObserver settingsObserver) {
+ getContentResolver().unregisterContentObserver(settingsObserver);
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 39d33f0..ee4bc12 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -133,6 +133,7 @@
import com.android.server.display.color.ColorDisplayService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.emergency.EmergencyAffordanceService;
+import com.android.server.flags.FeatureFlagsService;
import com.android.server.gpu.GpuService;
import com.android.server.grammaticalinflection.GrammaticalInflectionService;
import com.android.server.graphics.fonts.FontManagerService;
@@ -1117,6 +1118,12 @@
mSystemServiceManager.startService(DeviceIdentifiersPolicyService.class);
t.traceEnd();
+ // Starts a service for reading runtime flag overrides, and keeping processes
+ // in sync with one another.
+ t.traceBegin("StartFeatureFlagsService");
+ mSystemServiceManager.startService(FeatureFlagsService.class);
+ t.traceEnd();
+
// Uri Grants Manager.
t.traceBegin("UriGrantsManagerService");
mSystemServiceManager.startService(UriGrantsManagerService.Lifecycle.class);
diff --git a/services/permission/OWNERS b/services/permission/OWNERS
index 6c6c9fc..e464038 100644
--- a/services/permission/OWNERS
+++ b/services/permission/OWNERS
@@ -1,4 +1,5 @@
-ashfall@google.com
+#Bug component: 137825
+
joecastro@google.com
ntmyren@google.com
zhanghai@google.com
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 16fb012..dc2fbfc 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -68,6 +68,7 @@
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.permission.LegacyPermissionDataProvider;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageUserState;
import com.android.server.pm.pkg.PackageUserStateInternal;
import com.android.server.pm.pkg.SuspendParams;
@@ -864,6 +865,34 @@
}
@Test
+ public void testWriteReadArchiveState() {
+ Settings settings = makeSettings();
+ PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+ packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+ packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+ .setUid(packageSetting.getAppId())
+ .hideAsFinal());
+
+ ArchiveState archiveState = new ArchiveState(
+ List.of(new ArchiveState.ArchiveActivityInfo("title1", Path.of("/path1"),
+ Path.of("/monochromePath1")),
+ new ArchiveState.ArchiveActivityInfo("title2", Path.of("/path2"),
+ Path.of("/monochromePath2"))),
+ "installerTitle");
+ packageSetting.modifyUserState(UserHandle.SYSTEM.getIdentifier()).setArchiveState(
+ archiveState);
+ settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+ settings.writeLPr(computer, /*sync=*/true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+
+ Truth.assertThat(settings.getPackageLPr(PACKAGE_NAME_1).getStateForUser(
+ UserHandle.SYSTEM).getArchiveState()).isEqualTo(archiveState);
+ }
+
+ @Test
public void testPackageRestrictionsDistractionFlagsDefault() {
final PackageSetting defaultSetting = createPackageSetting(PACKAGE_NAME_1);
assertThat(defaultSetting.getDistractionFlags(0), is(PackageManager.RESTRICTION_NONE));
@@ -971,7 +1000,7 @@
origPkgSetting01.setUserState(0, 100, 1, true, false, false, false, 0, null, false,
false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}),
new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning",
- "splashScreenTheme", 1000L, PackageManager.USER_MIN_ASPECT_RATIO_UNSET);
+ "splashScreenTheme", 1000L, PackageManager.USER_MIN_ASPECT_RATIO_UNSET, null);
final PersistableBundle appExtras1 = createPersistableBundle(
PACKAGE_NAME_1, 1L, 0.01, true, "appString1");
final PersistableBundle launcherExtras1 = createPersistableBundle(
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
index 9ad503c..58ae740 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
@@ -35,6 +35,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageStateUnserialized;
import com.android.server.pm.pkg.PackageUserStateImpl;
import com.android.server.pm.pkg.SuspendParams;
@@ -44,6 +45,9 @@
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import java.nio.file.Path;
+import java.util.List;
+
@Presubmit
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -394,4 +398,14 @@
assertFalse(testState.setSharedLibraryOverlayPaths(LIB_ONE, null));
}
+ @Test
+ public void archiveState() {
+ PackageUserStateImpl packageUserState = new PackageUserStateImpl();
+ ArchiveState.ArchiveActivityInfo archiveActivityInfo = new ArchiveState.ArchiveActivityInfo(
+ "appTitle", Path.of("/path1"), Path.of("/path2"));
+ ArchiveState archiveState = new ArchiveState(List.of(archiveActivityInfo),
+ "installerTitle");
+ packageUserState.setArchiveState(archiveState);
+ assertEquals(archiveState, packageUserState.getArchiveState());
+ }
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index b5bd869..e2939c1 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -21,6 +21,7 @@
import static android.content.pm.SharedLibraryInfo.TYPE_STATIC;
import static android.content.pm.SharedLibraryInfo.VERSION_UNDEFINED;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX;
import static com.android.server.pm.PackageManagerService.SCAN_AS_FULL_APP;
import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
@@ -365,6 +366,46 @@
}
@Test
+ public void scanFirstBoot_apexDontDeriveAbis() throws Exception {
+ final PackageSetting pkgSetting =
+ createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME)
+ .setPkgFlags(ApplicationInfo.FLAG_SYSTEM).build();
+
+ final String codePath = "/data/apex/" + DUMMY_PACKAGE_NAME + ".apex";
+
+ // Create the ParsedPackage for the apex
+ final ParsedPackage basicPackage =
+ ((ParsedPackage) new PackageImpl(DUMMY_PACKAGE_NAME, codePath, codePath,
+ mock(TypedArray.class), false)
+ .setVolumeUuid(UUID_ONE.toString())
+ .hideAsParsed())
+ .setVersionCodeMajor(1)
+ .setVersionCode(2345)
+ .setSystem(true);
+
+ when(mMockInjector.getAbiHelper()).thenReturn(new PackageAbiHelperImpl());
+
+ // prepare the scan request with the scanflag (SCAN_FIRST_BOOT_OR_UPGRADE | SCAN_AS_APEX)
+ final ScanResult scanResult = executeScan(new ScanRequestBuilder(basicPackage)
+ .setPkgSetting(pkgSetting)
+ .addScanFlag(SCAN_FIRST_BOOT_OR_UPGRADE | SCAN_AS_APEX)
+ .build());
+
+ final PackageSetting resultSetting = scanResult.mPkgSetting;
+ final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
+ resultSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, resultSetting);
+ assertThat(applicationInfo.primaryCpuAbi, nullValue());
+ assertThat(applicationInfo.primaryCpuAbi, nullValue());
+ assertThat(applicationInfo.secondaryCpuAbi, nullValue());
+
+ assertThat(applicationInfo.nativeLibraryRootDir, nullValue());
+ assertThat(pkgSetting.getLegacyNativeLibraryPath(), nullValue());
+ assertThat(applicationInfo.nativeLibraryRootRequiresIsa, is(false));
+ assertThat(applicationInfo.nativeLibraryDir, nullValue());
+ assertThat(applicationInfo.secondaryNativeLibraryDir, nullValue());
+ }
+
+ @Test
public void scanFirstBoot_derivesAbis() throws Exception {
final PackageSetting pkgSetting =
createBasicPackageSettingBuilder(DUMMY_PACKAGE_NAME).build();
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index f1ff338..4f555d9 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -28,14 +28,15 @@
static_libs: [
"androidx.test.ext.junit",
- "display-core-libs",
"frameworks-base-testutils",
"junit",
"junit-params",
+ "mockingservicestests-utils-mockito",
"platform-compat-test-rules",
"platform-test-annotations",
"services.core",
"servicestests-utils",
+ "testables",
],
defaults: [
@@ -56,10 +57,3 @@
enabled: false,
},
}
-
-java_library {
- name: "display-core-libs",
- srcs: [
- "src/com/android/server/display/TestUtils.java",
- ],
-}
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index d2bd10d..55fde00 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -17,10 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.frameworks.displayservicetests">
- <!--
- Insert permissions here. eg:
- <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
- -->
+ <!-- Permissions -->
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
<uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
@@ -32,6 +29,10 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <!-- Permissions needed for DisplayTransformManagerTest -->
+ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+ <uses-permission android:name="android.permission.HARDWARE_TEST"/>
+
<application android:debuggable="true"
android:testOnly="true">
<uses-library android:name="android.test.mock" android:required="true" />
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/BrightnessSynchronizerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/BrightnessSynchronizerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DensityMappingTest.java b/services/tests/displayservicetests/src/com/android/server/display/DensityMappingTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/DensityMappingTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DensityMappingTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
new file mode 100644
index 0000000..95c62ae
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.server.display;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayBrightnessStateTest {
+ private static final float FLOAT_DELTA = 0.001f;
+
+ private DisplayBrightnessState.Builder mDisplayBrightnessStateBuilder;
+
+ @Before
+ public void before() {
+ mDisplayBrightnessStateBuilder = new DisplayBrightnessState.Builder();
+ }
+
+ @Test
+ public void validateAllDisplayBrightnessStateFieldsAreSetAsExpected() {
+ float brightness = 0.3f;
+ float sdrBrightness = 0.2f;
+ boolean shouldUseAutoBrightness = true;
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
+ brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED);
+ DisplayBrightnessState displayBrightnessState = mDisplayBrightnessStateBuilder
+ .setBrightness(brightness)
+ .setSdrBrightness(sdrBrightness)
+ .setBrightnessReason(brightnessReason)
+ .setShouldUseAutoBrightness(shouldUseAutoBrightness)
+ .build();
+
+ assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
+ assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA);
+ assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason);
+ assertEquals(displayBrightnessState.getShouldUseAutoBrightness(), shouldUseAutoBrightness);
+ assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState));
+ }
+
+ @Test
+ public void testFrom() {
+ BrightnessReason reason = new BrightnessReason();
+ reason.setReason(BrightnessReason.REASON_MANUAL);
+ reason.setModifier(BrightnessReason.MODIFIER_DIMMED);
+ DisplayBrightnessState state1 = new DisplayBrightnessState.Builder()
+ .setBrightnessReason(reason)
+ .setBrightness(0.26f)
+ .setSdrBrightness(0.23f)
+ .setShouldUseAutoBrightness(false)
+ .build();
+ DisplayBrightnessState state2 = DisplayBrightnessState.Builder.from(state1).build();
+ assertEquals(state1, state2);
+ }
+
+ private String getString(DisplayBrightnessState displayBrightnessState) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("DisplayBrightnessState:")
+ .append("\n brightness:")
+ .append(displayBrightnessState.getBrightness())
+ .append("\n sdrBrightness:")
+ .append(displayBrightnessState.getSdrBrightness())
+ .append("\n brightnessReason:")
+ .append(displayBrightnessState.getBrightnessReason())
+ .append("\n shouldUseAutoBrightness:")
+ .append(displayBrightnessState.getShouldUseAutoBrightness());
+ return sb.toString();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
similarity index 99%
rename from services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
rename to services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 0f323c3..8a9a851 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -715,6 +715,8 @@
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_OFF);
+
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_OFF;
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -752,6 +754,7 @@
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_DOZE;
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 3e695c9..0fe6e64 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -65,6 +65,7 @@
import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
+import com.android.server.utils.FoldSettingWrapper;
import org.junit.Before;
import org.junit.Test;
@@ -100,6 +101,7 @@
@Mock LogicalDisplayMapper.Listener mListenerMock;
@Mock Context mContextMock;
+ @Mock FoldSettingWrapper mFoldSettingWrapperMock;
@Mock Resources mResourcesMock;
@Mock IPowerManager mIPowerManagerMock;
@Mock IThermalService mIThermalServiceMock;
@@ -139,6 +141,7 @@
when(mContextMock.getSystemServiceName(PowerManager.class))
.thenReturn(Context.POWER_SERVICE);
+ when(mFoldSettingWrapperMock.shouldStayAwakeOnFold()).thenReturn(false);
when(mContextMock.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
when(mContextMock.getResources()).thenReturn(mResourcesMock);
when(mResourcesMock.getBoolean(
@@ -155,7 +158,7 @@
mHandler = new Handler(mLooper.getLooper());
mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
- mDeviceStateToLayoutMapSpy);
+ mDeviceStateToLayoutMapSpy, mFoldSettingWrapperMock);
}
@@ -571,6 +574,17 @@
}
@Test
+ public void testDeviceShouldNotSleepWhenFoldSettingTrue() {
+ when(mFoldSettingWrapperMock.shouldStayAwakeOnFold()).thenReturn(true);
+
+ assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
+ DEVICE_STATE_OPEN,
+ /* isOverrideActive= */false,
+ /* isInteractive= */true,
+ /* isBootCompleted= */true));
+ }
+
+ @Test
public void testDeviceShouldNotBePutToSleep() {
assertFalse(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_OPEN,
DEVICE_STATE_CLOSED,
@@ -978,4 +992,3 @@
}
}
}
-
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/WakelockControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index a9e616d..8497dab 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -18,17 +18,23 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.res.Resources;
import android.hardware.display.DisplayManagerInternal;
import android.view.Display;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy;
@@ -38,6 +44,7 @@
import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -64,15 +71,20 @@
@Mock
private FollowerBrightnessStrategy mFollowerBrightnessStrategy;
@Mock
- private Context mContext;
- @Mock
private Resources mResources;
private DisplayBrightnessStrategySelector mDisplayBrightnessStrategySelector;
+ private Context mContext;
+
+ @Rule
+ public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Before
public void before() {
MockitoAnnotations.initMocks(this);
+ mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContext);
+ when(mContext.getContentResolver()).thenReturn(contentResolver);
when(mContext.getResources()).thenReturn(mResources);
when(mInvalidBrightnessStrategy.getName()).thenReturn("InvalidBrightnessStrategy");
DisplayBrightnessStrategySelector.Injector injector =
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayTransformManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/color/DisplayTransformManagerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index e0bef1a..c280349 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -16,22 +16,49 @@
package com.android.server.display.color;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertArrayEquals;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.content.res.Resources;
import android.hardware.display.DisplayManagerInternal;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.SurfaceControl;
import androidx.test.InstrumentationRegistry;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.R;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.util.Arrays;
public class DisplayWhiteBalanceTintControllerTest {
+ @Mock
+ private Context mMockedContext;
+ @Mock
+ private Resources mMockedResources;
+ @Mock
+ private DisplayManagerInternal mDisplayManagerInternal;
- private DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
+ private MockitoSession mSession;
+ private Resources mResources;
+ IBinder mDisplayToken;
+ DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
@Before
public void setUp() {
@@ -40,6 +67,47 @@
new DisplayWhiteBalanceTintController(displayManagerInternal);
mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true);
mDisplayWhiteBalanceTintController.setActivated(true);
+
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .mockStatic(SurfaceControl.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ mResources = InstrumentationRegistry.getContext().getResources();
+ // These Resources are common to all tests.
+ doReturn(4000)
+ .when(mMockedResources)
+ .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
+ doReturn(8000)
+ .when(mMockedResources)
+ .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
+ doReturn(6500)
+ .when(mMockedResources)
+ .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
+ doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
+ .when(mMockedResources)
+ .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
+ doReturn(6500)
+ .when(mMockedResources)
+ .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
+ doReturn(new int[] {0})
+ .when(mMockedResources)
+ .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
+ doReturn(new int[] {20})
+ .when(mMockedResources)
+ .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
+
+ doReturn(mMockedResources).when(mMockedContext).getResources();
+
+ mDisplayToken = new Binder();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mSession != null) {
+ mSession.finishMocking();
+ }
}
@Test
@@ -98,4 +166,204 @@
})
).isTrue();
}
+
+
+ /**
+ * Setup should succeed when SurfaceControl setup results in a valid color transform.
+ */
+ @Test
+ public void displayWhiteBalance_setupWithSurfaceControl() {
+ // Make SurfaceControl return sRGB primaries
+ SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+ displayPrimaries.red = new SurfaceControl.CieXyz();
+ displayPrimaries.red.X = 0.412315f;
+ displayPrimaries.red.Y = 0.212600f;
+ displayPrimaries.red.Z = 0.019327f;
+ displayPrimaries.green = new SurfaceControl.CieXyz();
+ displayPrimaries.green.X = 0.357600f;
+ displayPrimaries.green.Y = 0.715200f;
+ displayPrimaries.green.Z = 0.119200f;
+ displayPrimaries.blue = new SurfaceControl.CieXyz();
+ displayPrimaries.blue.X = 0.180500f;
+ displayPrimaries.blue.Y = 0.072200f;
+ displayPrimaries.blue.Z = 0.950633f;
+ displayPrimaries.white = new SurfaceControl.CieXyz();
+ displayPrimaries.white.X = 0.950456f;
+ displayPrimaries.white.Y = 1.000000f;
+ displayPrimaries.white.Z = 1.089058f;
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
+
+ setUpTintController();
+ assertWithMessage("Setup with valid SurfaceControl failed")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isTrue();
+ }
+
+ /**
+ * Setup should fail when SurfaceControl setup results in an invalid color transform.
+ */
+ @Test
+ public void displayWhiteBalance_setupWithInvalidSurfaceControlData() {
+ // Make SurfaceControl return invalid display primaries
+ SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+ displayPrimaries.red = new SurfaceControl.CieXyz();
+ displayPrimaries.green = new SurfaceControl.CieXyz();
+ displayPrimaries.blue = new SurfaceControl.CieXyz();
+ displayPrimaries.white = new SurfaceControl.CieXyz();
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
+
+ setUpTintController();
+ assertWithMessage("Setup with invalid SurfaceControl succeeded")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isFalse();
+ }
+
+ /**
+ * Setup should succeed when SurfaceControl setup fails and Resources result in a valid color
+ * transform.
+ */
+ @Test
+ public void displayWhiteBalance_setupWithResources() {
+ // Use default (valid) Resources
+ doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries))
+ .when(mMockedResources)
+ .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
+ // Make SurfaceControl setup fail
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
+
+ setUpTintController();
+ assertWithMessage("Setup with valid Resources failed")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isTrue();
+ }
+
+ /**
+ * Setup should fail when SurfaceControl setup fails and Resources result in an invalid color
+ * transform.
+ */
+ @Test
+ public void displayWhiteBalance_setupWithInvalidResources() {
+ // Use Resources with invalid color data
+ doReturn(new String[] {
+ "0", "0", "0", // Red X, Y, Z
+ "0", "0", "0", // Green X, Y, Z
+ "0", "0", "0", // Blue X, Y, Z
+ "0", "0", "0", // White X, Y, Z
+ })
+ .when(mMockedResources)
+ .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
+ // Make SurfaceControl setup fail
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
+
+ setUpTintController();
+ assertWithMessage("Setup with invalid Resources succeeded")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isFalse();
+ }
+
+ /**
+ * Matrix should match the precalculated one for given cct and display primaries.
+ */
+ @Test
+ public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
+ SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+ displayPrimaries.red = new SurfaceControl.CieXyz();
+ displayPrimaries.red.X = 0.412315f;
+ displayPrimaries.red.Y = 0.212600f;
+ displayPrimaries.red.Z = 0.019327f;
+ displayPrimaries.green = new SurfaceControl.CieXyz();
+ displayPrimaries.green.X = 0.357600f;
+ displayPrimaries.green.Y = 0.715200f;
+ displayPrimaries.green.Z = 0.119200f;
+ displayPrimaries.blue = new SurfaceControl.CieXyz();
+ displayPrimaries.blue.X = 0.180500f;
+ displayPrimaries.blue.Y = 0.072200f;
+ displayPrimaries.blue.Z = 0.950633f;
+ displayPrimaries.white = new SurfaceControl.CieXyz();
+ displayPrimaries.white.X = 0.950456f;
+ displayPrimaries.white.Y = 1.000000f;
+ displayPrimaries.white.Z = 1.089058f;
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
+
+ setUpTintController();
+ assertWithMessage("Setup with valid SurfaceControl failed")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isTrue();
+
+ final int cct = 6500;
+ mDisplayWhiteBalanceTintController.setMatrix(cct);
+ mDisplayWhiteBalanceTintController.setAppliedCct(
+ mDisplayWhiteBalanceTintController.getTargetCct());
+
+ assertWithMessage("Failed to set temperature")
+ .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+ .isEqualTo(cct);
+ float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
+ final float[] expectedMatrixDwb = {
+ 0.971848f, -0.001421f, 0.000491f, 0.0f,
+ 0.028193f, 0.945798f, 0.003207f, 0.0f,
+ -0.000042f, -0.000989f, 0.988659f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+ assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+ 1e-6f /* tolerance */);
+ }
+
+ /**
+ * Matrix should match the precalculated one for given cct and display primaries.
+ */
+ @Test
+ public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
+ SurfaceControl.DisplayPrimaries displayPrimaries = new SurfaceControl.DisplayPrimaries();
+ displayPrimaries.red = new SurfaceControl.CieXyz();
+ displayPrimaries.red.X = 0.412315f;
+ displayPrimaries.red.Y = 0.212600f;
+ displayPrimaries.red.Z = 0.019327f;
+ displayPrimaries.green = new SurfaceControl.CieXyz();
+ displayPrimaries.green.X = 0.357600f;
+ displayPrimaries.green.Y = 0.715200f;
+ displayPrimaries.green.Z = 0.119200f;
+ displayPrimaries.blue = new SurfaceControl.CieXyz();
+ displayPrimaries.blue.X = 0.180500f;
+ displayPrimaries.blue.Y = 0.072200f;
+ displayPrimaries.blue.Z = 0.950633f;
+ displayPrimaries.white = new SurfaceControl.CieXyz();
+ displayPrimaries.white.X = 0.950456f;
+ displayPrimaries.white.Y = 1.000000f;
+ displayPrimaries.white.Z = 1.089058f;
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
+
+ setUpTintController();
+ assertWithMessage("Setup with valid SurfaceControl failed")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isTrue();
+
+ final int cct = 6500;
+ mDisplayWhiteBalanceTintController.setTargetCct(cct);
+ final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
+ mDisplayWhiteBalanceTintController.setAppliedCct(cct);
+
+ assertWithMessage("Failed to set temperature")
+ .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+ .isEqualTo(cct);
+ final float[] expectedMatrixDwb = {
+ 0.971848f, -0.001421f, 0.000491f, 0.0f,
+ 0.028193f, 0.945798f, 0.003207f, 0.0f,
+ -0.000042f, -0.000989f, 0.988659f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+ assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+ 1e-6f /* tolerance */);
+ }
+
+ private void setUpTintController() {
+ mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
+ mDisplayManagerInternal);
+ mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
+ mDisplayWhiteBalanceTintController.setActivated(true);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
similarity index 100%
rename from services/tests/mockingservicestests/src/com/android/server/display/state/DisplayStateControllerTest.java
rename to services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
diff --git a/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/OWNERS b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/OWNERS
index 999ea0e..2fe78c5 100644
--- a/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/OWNERS
+++ b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/OWNERS
@@ -1 +1,2 @@
+#Bug component: 137825
include /core/java/android/permission/OWNERS
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 73eb237..410ae35 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -125,6 +125,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.UnaryOperator;
@@ -327,7 +328,7 @@
eq(ActivityManager.PROCESS_STATE_LAST_ACTIVITY), any());
mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
- mConstants.TIMEOUT = 100;
+ mConstants.TIMEOUT = 200;
mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
@@ -707,6 +708,9 @@
private void waitForIdle() throws Exception {
mLooper.release();
mQueue.waitForIdle(LOG_WRITER_INFO);
+ final CountDownLatch latch = new CountDownLatch(1);
+ mHandlerThread.getThreadHandler().post(latch::countDown);
+ latch.await();
mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
.acquireLooperManager(mHandlerThread.getLooper()));
}
@@ -2342,6 +2346,7 @@
mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+ waitForIdle();
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
@@ -2375,6 +2380,7 @@
mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
+ waitForIdle();
final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/OWNERS b/services/tests/mockingservicestests/src/com/android/server/appop/OWNERS
index 999ea0e..2fe78c5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/OWNERS
@@ -1 +1,2 @@
+#Bug component: 137825
include /core/java/android/permission/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
deleted file mode 100644
index 50996d7..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import static org.junit.Assert.assertEquals;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.display.brightness.BrightnessReason;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class DisplayBrightnessStateTest {
- private static final float FLOAT_DELTA = 0.001f;
-
- private DisplayBrightnessState.Builder mDisplayBrightnessStateBuilder;
-
- @Before
- public void before() {
- mDisplayBrightnessStateBuilder = new DisplayBrightnessState.Builder();
- }
-
- @Test
- public void validateAllDisplayBrightnessStateFieldsAreSetAsExpected() {
- float brightness = 0.3f;
- float sdrBrightness = 0.2f;
- BrightnessReason brightnessReason = new BrightnessReason();
- brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
- brightnessReason.setModifier(BrightnessReason.MODIFIER_DIMMED);
- DisplayBrightnessState displayBrightnessState =
- mDisplayBrightnessStateBuilder.setBrightness(brightness).setSdrBrightness(
- sdrBrightness).setBrightnessReason(brightnessReason).build();
-
- assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
- assertEquals(displayBrightnessState.getSdrBrightness(), sdrBrightness, FLOAT_DELTA);
- assertEquals(displayBrightnessState.getBrightnessReason(), brightnessReason);
- assertEquals(displayBrightnessState.toString(), getString(displayBrightnessState));
- }
-
- private String getString(DisplayBrightnessState displayBrightnessState) {
- StringBuilder sb = new StringBuilder();
- sb.append("DisplayBrightnessState:");
- sb.append("\n brightness:" + displayBrightnessState.getBrightness());
- sb.append("\n sdrBrightness:" + displayBrightnessState.getSdrBrightness());
- sb.append("\n brightnessReason:" + displayBrightnessState.getBrightnessReason());
- return sb.toString();
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/OWNERS b/services/tests/mockingservicestests/src/com/android/server/display/OWNERS
deleted file mode 100644
index 6ce1ee4..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/display/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
deleted file mode 100644
index 3faf394..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display.color;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.Binder;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.CieXyz;
-import android.view.SurfaceControl.DisplayPrimaries;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
-import com.android.internal.R;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-@RunWith(AndroidJUnit4.class)
-public class DisplayWhiteBalanceTintControllerTest {
- @Mock
- private Context mMockedContext;
- @Mock
- private Resources mMockedResources;
- @Mock
- private DisplayManagerInternal mDisplayManagerInternal;
-
- private MockitoSession mSession;
- private Resources mResources;
- IBinder mDisplayToken;
- DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
-
- @Before
- public void setUp() {
- mSession = ExtendedMockito.mockitoSession()
- .initMocks(this)
- .mockStatic(SurfaceControl.class)
- .strictness(Strictness.LENIENT)
- .startMocking();
-
- mResources = InstrumentationRegistry.getContext().getResources();
- // These Resources are common to all tests.
- doReturn(4000)
- .when(mMockedResources)
- .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
- doReturn(8000)
- .when(mMockedResources)
- .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
- doReturn(6500)
- .when(mMockedResources)
- .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
- doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
- .when(mMockedResources)
- .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
- doReturn(6500)
- .when(mMockedResources)
- .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
- doReturn(new int[] {0})
- .when(mMockedResources)
- .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
- doReturn(new int[] {20})
- .when(mMockedResources)
- .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
-
- doReturn(mMockedResources).when(mMockedContext).getResources();
-
- mDisplayToken = new Binder();
- }
-
- @After
- public void tearDown() throws Exception {
- if (mSession != null) {
- mSession.finishMocking();
- }
- }
-
- /**
- * Setup should succeed when SurfaceControl setup results in a valid color transform.
- */
- @Test
- public void displayWhiteBalance_setupWithSurfaceControl() {
- // Make SurfaceControl return sRGB primaries
- DisplayPrimaries displayPrimaries = new DisplayPrimaries();
- displayPrimaries.red = new CieXyz();
- displayPrimaries.red.X = 0.412315f;
- displayPrimaries.red.Y = 0.212600f;
- displayPrimaries.red.Z = 0.019327f;
- displayPrimaries.green = new CieXyz();
- displayPrimaries.green.X = 0.357600f;
- displayPrimaries.green.Y = 0.715200f;
- displayPrimaries.green.Z = 0.119200f;
- displayPrimaries.blue = new CieXyz();
- displayPrimaries.blue.X = 0.180500f;
- displayPrimaries.blue.Y = 0.072200f;
- displayPrimaries.blue.Z = 0.950633f;
- displayPrimaries.white = new CieXyz();
- displayPrimaries.white.X = 0.950456f;
- displayPrimaries.white.Y = 1.000000f;
- displayPrimaries.white.Z = 1.089058f;
- when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
- .thenReturn(displayPrimaries);
-
- setUpTintController();
- assertWithMessage("Setup with valid SurfaceControl failed")
- .that(mDisplayWhiteBalanceTintController.mSetUp)
- .isTrue();
- }
-
- /**
- * Setup should fail when SurfaceControl setup results in an invalid color transform.
- */
- @Test
- public void displayWhiteBalance_setupWithInvalidSurfaceControlData() {
- // Make SurfaceControl return invalid display primaries
- DisplayPrimaries displayPrimaries = new DisplayPrimaries();
- displayPrimaries.red = new CieXyz();
- displayPrimaries.green = new CieXyz();
- displayPrimaries.blue = new CieXyz();
- displayPrimaries.white = new CieXyz();
- when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
- .thenReturn(displayPrimaries);
-
- setUpTintController();
- assertWithMessage("Setup with invalid SurfaceControl succeeded")
- .that(mDisplayWhiteBalanceTintController.mSetUp)
- .isFalse();
- }
-
- /**
- * Setup should succeed when SurfaceControl setup fails and Resources result in a valid color
- * transform.
- */
- @Test
- public void displayWhiteBalance_setupWithResources() {
- // Use default (valid) Resources
- doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries))
- .when(mMockedResources)
- .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
- // Make SurfaceControl setup fail
- when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
-
- setUpTintController();
- assertWithMessage("Setup with valid Resources failed")
- .that(mDisplayWhiteBalanceTintController.mSetUp)
- .isTrue();
- }
-
- /**
- * Setup should fail when SurfaceControl setup fails and Resources result in an invalid color
- * transform.
- */
- @Test
- public void displayWhiteBalance_setupWithInvalidResources() {
- // Use Resources with invalid color data
- doReturn(new String[] {
- "0", "0", "0", // Red X, Y, Z
- "0", "0", "0", // Green X, Y, Z
- "0", "0", "0", // Blue X, Y, Z
- "0", "0", "0", // White X, Y, Z
- })
- .when(mMockedResources)
- .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
- // Make SurfaceControl setup fail
- when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
-
- setUpTintController();
- assertWithMessage("Setup with invalid Resources succeeded")
- .that(mDisplayWhiteBalanceTintController.mSetUp)
- .isFalse();
- }
-
- /**
- * Matrix should match the precalculated one for given cct and display primaries.
- */
- @Test
- public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
- DisplayPrimaries displayPrimaries = new DisplayPrimaries();
- displayPrimaries.red = new CieXyz();
- displayPrimaries.red.X = 0.412315f;
- displayPrimaries.red.Y = 0.212600f;
- displayPrimaries.red.Z = 0.019327f;
- displayPrimaries.green = new CieXyz();
- displayPrimaries.green.X = 0.357600f;
- displayPrimaries.green.Y = 0.715200f;
- displayPrimaries.green.Z = 0.119200f;
- displayPrimaries.blue = new CieXyz();
- displayPrimaries.blue.X = 0.180500f;
- displayPrimaries.blue.Y = 0.072200f;
- displayPrimaries.blue.Z = 0.950633f;
- displayPrimaries.white = new CieXyz();
- displayPrimaries.white.X = 0.950456f;
- displayPrimaries.white.Y = 1.000000f;
- displayPrimaries.white.Z = 1.089058f;
- when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
- .thenReturn(displayPrimaries);
-
- setUpTintController();
- assertWithMessage("Setup with valid SurfaceControl failed")
- .that(mDisplayWhiteBalanceTintController.mSetUp)
- .isTrue();
-
- final int cct = 6500;
- mDisplayWhiteBalanceTintController.setMatrix(cct);
- mDisplayWhiteBalanceTintController.setAppliedCct(
- mDisplayWhiteBalanceTintController.getTargetCct());
-
- assertWithMessage("Failed to set temperature")
- .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
- .isEqualTo(cct);
- float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
- final float[] expectedMatrixDwb = {
- 0.971848f, -0.001421f, 0.000491f, 0.0f,
- 0.028193f, 0.945798f, 0.003207f, 0.0f,
- -0.000042f, -0.000989f, 0.988659f, 0.0f,
- 0.0f, 0.0f, 0.0f, 1.0f
- };
- assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
- 1e-6f /* tolerance */);
- }
-
- /**
- * Matrix should match the precalculated one for given cct and display primaries.
- */
- @Test
- public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
- DisplayPrimaries displayPrimaries = new DisplayPrimaries();
- displayPrimaries.red = new CieXyz();
- displayPrimaries.red.X = 0.412315f;
- displayPrimaries.red.Y = 0.212600f;
- displayPrimaries.red.Z = 0.019327f;
- displayPrimaries.green = new CieXyz();
- displayPrimaries.green.X = 0.357600f;
- displayPrimaries.green.Y = 0.715200f;
- displayPrimaries.green.Z = 0.119200f;
- displayPrimaries.blue = new CieXyz();
- displayPrimaries.blue.X = 0.180500f;
- displayPrimaries.blue.Y = 0.072200f;
- displayPrimaries.blue.Z = 0.950633f;
- displayPrimaries.white = new CieXyz();
- displayPrimaries.white.X = 0.950456f;
- displayPrimaries.white.Y = 1.000000f;
- displayPrimaries.white.Z = 1.089058f;
- when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
- .thenReturn(displayPrimaries);
-
- setUpTintController();
- assertWithMessage("Setup with valid SurfaceControl failed")
- .that(mDisplayWhiteBalanceTintController.mSetUp)
- .isTrue();
-
- final int cct = 6500;
- mDisplayWhiteBalanceTintController.setTargetCct(cct);
- final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
- mDisplayWhiteBalanceTintController.setAppliedCct(cct);
-
- assertWithMessage("Failed to set temperature")
- .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
- .isEqualTo(cct);
- final float[] expectedMatrixDwb = {
- 0.971848f, -0.001421f, 0.000491f, 0.0f,
- 0.028193f, 0.945798f, 0.003207f, 0.0f,
- -0.000042f, -0.000989f, 0.988659f, 0.0f,
- 0.0f, 0.0f, 0.0f, 1.0f
- };
- assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
- 1e-6f /* tolerance */);
- }
-
- private void setUpTintController() {
- mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
- mDisplayManagerInternal);
- mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
- mDisplayWhiteBalanceTintController.setActivated(true);
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java
new file mode 100644
index 0000000..7b16545
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.pm.pkg.ArchiveState;
+import com.android.server.pm.pkg.PackageStateInternal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ArchiveManagerTest {
+
+ private static final String PACKAGE = "com.example";
+ private static final String CALLER_PACKAGE = "com.vending";
+
+ @Rule
+ public final MockSystemRule mMockSystem = new MockSystemRule();
+
+ @Mock
+ private IntentSender mIntentSender;
+ @Mock
+ private Computer mComputer;
+ @Mock
+ private Context mContext;
+ @Mock
+ private LauncherApps mLauncherApps;
+ @Mock
+ private PackageInstallerService mInstallerService;
+ @Mock
+ private PackageStateInternal mPackageState;
+
+ private final InstallSource mInstallSource =
+ InstallSource.create(
+ CALLER_PACKAGE,
+ CALLER_PACKAGE,
+ CALLER_PACKAGE,
+ Binder.getCallingUid(),
+ CALLER_PACKAGE,
+ /* installerAttributionTag= */ null,
+ /* packageSource= */ 0);
+
+ private final List<LauncherActivityInfo> mLauncherActivityInfos = createLauncherActivities();
+
+ private PackageSetting mPackageSetting;
+
+ private PackageManagerService mPm;
+ private ArchiveManager mArchiveManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mMockSystem.system().stageNominalSystemState();
+ when(mMockSystem.mocks().getInjector().getPackageInstallerService()).thenReturn(
+ mInstallerService);
+ mPm = spy(new PackageManagerService(mMockSystem.mocks().getInjector(),
+ /* factoryTest= */false,
+ MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint,
+ /* isEngBuild= */ false,
+ /* isUserDebugBuild= */false,
+ Build.VERSION_CODES.CUR_DEVELOPMENT,
+ Build.VERSION.INCREMENTAL));
+
+ when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
+ mPackageState);
+ when(mPackageState.getPackageName()).thenReturn(PACKAGE);
+ when(mPackageState.getInstallSource()).thenReturn(mInstallSource);
+ mPackageSetting = createBasicPackageSetting();
+ when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
+ mPackageSetting);
+ when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
+ when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
+ mLauncherActivityInfos);
+ doReturn(mComputer).when(mPm).snapshotComputer();
+ when(mComputer.getNameForUid(eq(Binder.getCallingUid()))).thenReturn(CALLER_PACKAGE);
+ mArchiveManager = new ArchiveManager(mContext, mPm);
+ }
+
+ @Test
+ public void archiveApp_callerPackageNameIncorrect() {
+ Exception e = assertThrows(
+ SecurityException.class,
+ () -> mArchiveManager.archiveApp(PACKAGE, "different", UserHandle.CURRENT,
+ mIntentSender)
+ );
+ assertThat(e).hasMessageThat().isEqualTo(
+ String.format(
+ "The callerPackageName %s set by the caller doesn't match the "
+ + "caller's own package name %s.",
+ "different",
+ CALLER_PACKAGE));
+ }
+
+ @Test
+ public void archiveApp_packageNotInstalled() {
+ when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
+ null);
+
+ Exception e = assertThrows(
+ PackageManager.NameNotFoundException.class,
+ () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
+ mIntentSender)
+ );
+ assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE));
+ }
+
+ @Test
+ public void archiveApp_packageNotInstalledForUser() {
+ mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);
+
+ Exception e = assertThrows(
+ PackageManager.NameNotFoundException.class,
+ () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
+ mIntentSender)
+ );
+ assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE));
+ }
+
+ @Test
+ public void archiveApp_callerNotInstallerOfRecord() {
+ InstallSource otherInstallSource =
+ InstallSource.create(
+ CALLER_PACKAGE,
+ CALLER_PACKAGE,
+ /* installerPackageName= */ "different",
+ Binder.getCallingUid(),
+ CALLER_PACKAGE,
+ /* installerAttributionTag= */ null,
+ /* packageSource= */ 0);
+ when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
+
+ Exception e = assertThrows(
+ SecurityException.class,
+ () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, Process.myUserHandle(),
+ mIntentSender)
+ );
+ assertThat(e).hasMessageThat().isEqualTo(
+ String.format("Caller is not the installer of record for %s.", PACKAGE));
+ }
+
+ @Test
+ public void archiveApp_callerNotUpdateOwner() {
+ InstallSource otherInstallSource =
+ InstallSource.create(
+ CALLER_PACKAGE,
+ CALLER_PACKAGE,
+ CALLER_PACKAGE,
+ Binder.getCallingUid(),
+ /* updateOwnerPackageName= */ "different",
+ /* installerAttributionTag= */ null,
+ /* packageSource= */ 0);
+ when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
+
+ Exception e = assertThrows(
+ SecurityException.class,
+ () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
+ mIntentSender)
+ );
+ assertThat(e).hasMessageThat().isEqualTo(
+ String.format("Caller is not the update owner for %s.", PACKAGE));
+ }
+
+ @Test
+ public void archiveApp_success() throws PackageManager.NameNotFoundException {
+ List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
+ for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
+ // TODO(b/278553670) Extract and store launcher icons
+ ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
+ mainActivity.getLabel().toString(),
+ Path.of("/TODO"), null);
+ activityInfos.add(activityInfo);
+ }
+
+ mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT, mIntentSender);
+ verify(mInstallerService).uninstall(
+ eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
+ eq(CALLER_PACKAGE), eq(DELETE_KEEP_DATA), eq(mIntentSender),
+ eq(UserHandle.CURRENT.getIdentifier()));
+ assertThat(mPackageSetting.readUserState(
+ UserHandle.CURRENT.getIdentifier()).getArchiveState()).isEqualTo(
+ new ArchiveState(activityInfos, CALLER_PACKAGE));
+ }
+
+ private static List<LauncherActivityInfo> createLauncherActivities() {
+ LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class);
+ when(activity1.getLabel()).thenReturn("activity1");
+ LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class);
+ when(activity2.getLabel()).thenReturn("activity2");
+ return List.of(activity1, activity2);
+ }
+
+ private PackageSetting createBasicPackageSetting() {
+ return new PackageSettingBuilder()
+ .setName(PACKAGE).setCodePath("/data/app/" + PACKAGE + "-randompath")
+ .setInstallState(UserHandle.CURRENT.getIdentifier(), /* installed= */ true)
+ .build();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index 3ac59e9..f206333 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -33,6 +33,8 @@
import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.Looper;
+import android.os.Process;
+import android.util.SparseArray;
import org.junit.After;
import org.junit.Before;
@@ -244,6 +246,54 @@
verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
}
+ @Test
+ public void testRegisterPackageMonitorCallbackInAllowList_callbackShouldCalled()
+ throws Exception {
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ SparseArray<int[]> broadcastAllowList = new SparseArray<>();
+ broadcastAllowList.put(0, new int[] {Binder.getCallingUid()});
+
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Binder.getCallingUid());
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+ null /* instantUserIds */, broadcastAllowList);
+
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
+ }
+
+ @Test
+ public void testRegisterPackageMonitorCallbackNotInAllowList_callbackShouldNotCalled()
+ throws Exception {
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ SparseArray<int[]> broadcastAllowList = new SparseArray<>();
+ broadcastAllowList.put(0, new int[] {12345});
+
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Binder.getCallingUid());
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+ null /* instantUserIds */, broadcastAllowList);
+
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
+ }
+
+ @Test
+ public void testRegisterPackageMonitorCallbackNotInAllowListSystemUid_callbackShouldCalled()
+ throws Exception {
+ IRemoteCallback callback = createMockPackageMonitorCallback();
+ SparseArray<int[]> broadcastAllowList = new SparseArray<>();
+ broadcastAllowList.put(0, new int[] {12345});
+
+ mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+ Process.SYSTEM_UID);
+ mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED,
+ FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */,
+ null /* instantUserIds */, broadcastAllowList);
+
+ verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any());
+ }
+
private IRemoteCallback createMockPackageMonitorCallback() {
return spy(new IRemoteCallback.Stub() {
@Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
deleted file mode 100644
index 0f2c27c..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/power/PowerManagerServiceMockingTest.java
+++ /dev/null
@@ -1,316 +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.server.power;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManagerInternal;
-import android.attention.AttentionManagerInternal;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.res.Resources;
-import android.hardware.SensorManager;
-import android.hardware.devicestate.DeviceStateManager;
-import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
-import android.hardware.display.AmbientDisplayConfiguration;
-import android.hardware.display.DisplayManagerInternal;
-import android.os.BatteryManagerInternal;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.PowerSaveState;
-import android.os.test.TestLooper;
-import android.provider.Settings;
-import android.service.dreams.DreamManagerInternal;
-import android.test.mock.MockContentResolver;
-import android.view.Display;
-import android.view.DisplayInfo;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.LocalServices;
-import com.android.server.SystemService;
-import com.android.server.lights.LightsManager;
-import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.power.PowerManagerService.BatteryReceiver;
-import com.android.server.power.PowerManagerService.Injector;
-import com.android.server.power.PowerManagerService.NativeWrapper;
-import com.android.server.power.PowerManagerService.UserSwitchedReceiver;
-import com.android.server.power.batterysaver.BatterySaverController;
-import com.android.server.power.batterysaver.BatterySaverPolicy;
-import com.android.server.power.batterysaver.BatterySaverStateMachine;
-import com.android.server.power.batterysaver.BatterySavingStats;
-import com.android.server.testutils.OffsettableClock;
-
-import java.util.concurrent.Executor;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link com.android.server.power.PowerManagerService}.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:PowerManagerServiceMockingTest
- */
-public class PowerManagerServiceMockingTest {
- private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
- private static final String SYSTEM_PROPERTY_REBOOT_REASON = "sys.boot.reason";
-
- private static final float BRIGHTNESS_FACTOR = 0.7f;
- private static final boolean BATTERY_SAVER_ENABLED = true;
-
- @Mock private BatterySaverController mBatterySaverControllerMock;
- @Mock private BatterySaverPolicy mBatterySaverPolicyMock;
- @Mock private BatterySaverStateMachine mBatterySaverStateMachineMock;
- @Mock private LightsManager mLightsManagerMock;
- @Mock private DisplayManagerInternal mDisplayManagerInternalMock;
- @Mock private BatteryManagerInternal mBatteryManagerInternalMock;
- @Mock private ActivityManagerInternal mActivityManagerInternalMock;
- @Mock private AttentionManagerInternal mAttentionManagerInternalMock;
- @Mock private DreamManagerInternal mDreamManagerInternalMock;
- @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
- @Mock private Notifier mNotifierMock;
- @Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
- @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
- @Mock private SystemPropertiesWrapper mSystemPropertiesMock;
- @Mock private DeviceStateManager mDeviceStateManagerMock;
-
- @Mock
- private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
-
- private PowerManagerService mService;
- private PowerSaveState mPowerSaveState;
- private ContextWrapper mContextSpy;
- private BatteryReceiver mBatteryReceiver;
- private UserSwitchedReceiver mUserSwitchedReceiver;
- private Resources mResourcesSpy;
- private OffsettableClock mClock;
- private TestLooper mTestLooper;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- FakeSettingsProvider.clearSettingsProvider();
-
- mPowerSaveState = new PowerSaveState.Builder()
- .setBatterySaverEnabled(BATTERY_SAVER_ENABLED)
- .setBrightnessFactor(BRIGHTNESS_FACTOR)
- .build();
- when(mBatterySaverPolicyMock.getBatterySaverPolicy(
- eq(PowerManager.ServiceType.SCREEN_BRIGHTNESS)))
- .thenReturn(mPowerSaveState);
- when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false);
- when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false);
- when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(), anyBoolean()))
- .thenReturn(true);
- when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
- when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
-
- addLocalServiceMock(LightsManager.class, mLightsManagerMock);
- addLocalServiceMock(DisplayManagerInternal.class, mDisplayManagerInternalMock);
- addLocalServiceMock(BatteryManagerInternal.class, mBatteryManagerInternalMock);
- addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
- addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock);
- addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock);
-
- mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
- mResourcesSpy = spy(mContextSpy.getResources());
- when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
-
- MockContentResolver cr = new MockContentResolver(mContextSpy);
- cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- when(mContextSpy.getContentResolver()).thenReturn(cr);
-
- when(mContextSpy.getSystemService(DeviceStateManager.class))
- .thenReturn(mDeviceStateManagerMock);
-
- Settings.Global.putInt(mContextSpy.getContentResolver(),
- Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
-
- mClock = new OffsettableClock.Stopped();
- mTestLooper = new TestLooper(mClock::now);
- }
-
- private PowerManagerService createService() {
- mService = new PowerManagerService(mContextSpy, new Injector() {
- @Override
- Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
- SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
- FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
- Executor executor) {
- return mNotifierMock;
- }
-
- @Override
- SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
- return super.createSuspendBlocker(service, name);
- }
-
- @Override
- BatterySaverPolicy createBatterySaverPolicy(
- Object lock, Context context, BatterySavingStats batterySavingStats) {
- return mBatterySaverPolicyMock;
- }
-
- @Override
- BatterySaverController createBatterySaverController(
- Object lock, Context context, BatterySaverPolicy batterySaverPolicy,
- BatterySavingStats batterySavingStats) {
- return mBatterySaverControllerMock;
- }
-
- @Override
- BatterySaverStateMachine createBatterySaverStateMachine(Object lock, Context context,
- BatterySaverController batterySaverController) {
- return mBatterySaverStateMachineMock;
- }
-
- @Override
- NativeWrapper createNativeWrapper() {
- return mNativeWrapperMock;
- }
-
- @Override
- WirelessChargerDetector createWirelessChargerDetector(
- SensorManager sensorManager, SuspendBlocker suspendBlocker, Handler handler) {
- return mWirelessChargerDetectorMock;
- }
-
- @Override
- AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) {
- return mAmbientDisplayConfigurationMock;
- }
-
- @Override
- InattentiveSleepWarningController createInattentiveSleepWarningController() {
- return mInattentiveSleepWarningControllerMock;
- }
-
- @Override
- public SystemPropertiesWrapper createSystemPropertiesWrapper() {
- return mSystemPropertiesMock;
- }
-
- @Override
- PowerManagerService.Clock createClock() {
- return new PowerManagerService.Clock() {
- @Override
- public long uptimeMillis() {
- return mClock.now();
- }
-
- @Override
- public long elapsedRealtime() {
- return mClock.now();
- }
- };
- }
-
- @Override
- Handler createHandler(Looper looper, Handler.Callback callback) {
- return new Handler(mTestLooper.getLooper(), callback);
- }
-
- @Override
- void invalidateIsInteractiveCaches() {
- // Avoids an SELinux failure.
- }
- });
- return mService;
- }
-
- @After
- public void tearDown() throws Exception {
- LocalServices.removeServiceForTest(LightsManager.class);
- LocalServices.removeServiceForTest(DisplayManagerInternal.class);
- LocalServices.removeServiceForTest(BatteryManagerInternal.class);
- LocalServices.removeServiceForTest(ActivityManagerInternal.class);
- LocalServices.removeServiceForTest(AttentionManagerInternal.class);
- LocalServices.removeServiceForTest(DreamManagerInternal.class);
- FakeSettingsProvider.clearSettingsProvider();
- }
-
- /**
- * Creates a mock and registers it to {@link LocalServices}.
- */
- private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
- LocalServices.removeServiceForTest(clazz);
- LocalServices.addService(clazz, mock);
- }
-
- private void advanceTime(long timeMs) {
- mClock.fastForward(timeMs);
- mTestLooper.dispatchAll();
- }
-
- @Test
- public void testUserActivityOnDeviceStateChange() {
- createService();
- mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
- mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-
- final DisplayInfo info = new DisplayInfo();
- info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
- when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
-
- final ArgumentCaptor<DeviceStateCallback> deviceStateCallbackCaptor =
- ArgumentCaptor.forClass(DeviceStateCallback.class);
- verify(mDeviceStateManagerMock).registerCallback(any(),
- deviceStateCallbackCaptor.capture());
-
- // Advance the time 10001 and verify that the device thinks it has been idle
- // for just less than that.
- mService.onUserActivity();
- advanceTime(10001);
- assertThat(mService.wasDeviceIdleForInternal(10000)).isTrue();
-
- // Send a display state change event and advance the clock 10.
- final DeviceStateCallback deviceStateCallback = deviceStateCallbackCaptor.getValue();
- deviceStateCallback.onStateChanged(1);
- final long timeToAdvance = 10;
- advanceTime(timeToAdvance);
-
- // Ensure that the device has been idle for only 10 (doesn't include the idle time
- // before the display state event).
- assertThat(mService.wasDeviceIdleForInternal(timeToAdvance - 1)).isTrue();
- assertThat(mService.wasDeviceIdleForInternal(timeToAdvance)).isFalse();
-
- // Send the same state and ensure that does not trigger an update.
- deviceStateCallback.onStateChanged(1);
- advanceTime(timeToAdvance);
- final long newTime = timeToAdvance * 2;
-
- assertThat(mService.wasDeviceIdleForInternal(newTime - 1)).isTrue();
- assertThat(mService.wasDeviceIdleForInternal(newTime)).isFalse();
- }
-}
diff --git a/services/tests/powerservicetests/Android.bp b/services/tests/powerservicetests/Android.bp
new file mode 100644
index 0000000..9384015
--- /dev/null
+++ b/services/tests/powerservicetests/Android.bp
@@ -0,0 +1,43 @@
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "PowerServiceTests",
+
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ static_libs: [
+ "frameworks-base-testutils",
+ "platform-compat-test-rules",
+ "platform-test-annotations",
+ "services.core",
+ "servicestests-utils",
+ "testables",
+ ],
+
+ libs: [
+ "android.test.mock",
+ ],
+
+ defaults: [
+ "modules-utils-testable-device-config-defaults",
+ ],
+
+ platform_apis: true,
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+
+ certificate: "platform",
+
+ dxflags: ["--multi-dex"],
+
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/powerservicetests/AndroidManifest.xml b/services/tests/powerservicetests/AndroidManifest.xml
new file mode 100644
index 0000000..26d9eec
--- /dev/null
+++ b/services/tests/powerservicetests/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.powerservicetests">
+
+ <!-- Permissions -->
+ <uses-permission android:name="android.permission.DEVICE_POWER"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.READ_DREAM_STATE"/>
+ <uses-permission android:name="android.permission.READ_DREAM_SUPPRESSION"/>
+ <uses-permission android:name="android.permission.STATUS_BAR_SERVICE"/>
+ <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS"/>
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
+ <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/>
+
+ <application android:debuggable="true"
+ android:testOnly="true">
+ <uses-library android:name="android.test.mock" android:required="true" />
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Power Service Tests"
+ android:targetPackage="com.android.frameworks.powerservicetests" />
+</manifest>
diff --git a/services/tests/powerservicetests/AndroidTest.xml b/services/tests/powerservicetests/AndroidTest.xml
new file mode 100644
index 0000000..fa6645b
--- /dev/null
+++ b/services/tests/powerservicetests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<configuration description="Runs Power Service Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="PowerServiceTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="PowerServiceTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.frameworks.powerservicetests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false" />
+ </test>
+</configuration>
diff --git a/services/tests/powerservicetests/OWNERS b/services/tests/powerservicetests/OWNERS
new file mode 100644
index 0000000..aff0930
--- /dev/null
+++ b/services/tests/powerservicetests/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 46788
+
+include /services/core/java/com/android/server/power/OWNERS
+
diff --git a/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/NotifierTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index c80ff45..9463b2d 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -60,6 +60,8 @@
import android.content.PermissionChecker;
import android.content.res.Resources;
import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
@@ -160,6 +162,7 @@
@Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
@Mock private PowerManagerService.PermissionCheckerWrapper mPermissionCheckerWrapperMock;
@Mock private PowerManagerService.PowerPropertiesWrapper mPowerPropertiesWrapper;
+ @Mock private DeviceStateManager mDeviceStateManagerMock;
@Rule public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -2710,4 +2713,48 @@
verify(mNotifierMock, never()).onUserActivity(anyInt(), anyInt(), anyInt());
}
+ @Test
+ public void testUserActivityOnDeviceStateChange() {
+ when(mContextSpy.getSystemService(DeviceStateManager.class))
+ .thenReturn(mDeviceStateManagerMock);
+
+ createService();
+ mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = Display.DEFAULT_DISPLAY_GROUP;
+ when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(info);
+
+ final ArgumentCaptor<DeviceStateCallback> deviceStateCallbackCaptor =
+ ArgumentCaptor.forClass(DeviceStateCallback.class);
+ verify(mDeviceStateManagerMock).registerCallback(any(),
+ deviceStateCallbackCaptor.capture());
+
+ // Advance the time 10001 and verify that the device thinks it has been idle
+ // for just less than that.
+ mService.onUserActivity();
+ advanceTime(10001);
+ assertThat(mService.wasDeviceIdleForInternal(10000)).isTrue();
+
+ // Send a display state change event and advance the clock 10.
+ final DeviceStateCallback deviceStateCallback = deviceStateCallbackCaptor.getValue();
+ deviceStateCallback.onStateChanged(1);
+ final long timeToAdvance = 10;
+ advanceTime(timeToAdvance);
+
+ // Ensure that the device has been idle for only 10 (doesn't include the idle time
+ // before the display state event).
+ assertThat(mService.wasDeviceIdleForInternal(timeToAdvance - 1)).isTrue();
+ assertThat(mService.wasDeviceIdleForInternal(timeToAdvance)).isFalse();
+
+ // Send the same state and ensure that does not trigger an update.
+ deviceStateCallback.onStateChanged(1);
+ advanceTime(timeToAdvance);
+ final long newTime = timeToAdvance * 2;
+
+ assertThat(mService.wasDeviceIdleForInternal(newTime - 1)).isTrue();
+ assertThat(mService.wasDeviceIdleForInternal(newTime)).isFalse();
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java b/services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/ShutdownCheckPointsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/ShutdownThreadTest.java b/services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/ShutdownThreadTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/ShutdownThreadTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java
rename to services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 4edb167..173c5f5 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -34,6 +34,7 @@
"services.core",
"services.credentials",
"services.devicepolicy",
+ "services.flags",
"services.net",
"services.people",
"services.usage",
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 2d4bf14..32d0c98 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
@@ -44,8 +44,10 @@
import android.graphics.PointF;
import android.os.Handler;
import android.os.Message;
+import android.os.UserHandle;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.provider.Settings;
import android.testing.TestableContext;
import android.util.DebugUtils;
import android.view.InputDevice;
@@ -140,8 +142,6 @@
@Mock
WindowMagnificationPromptController mWindowMagnificationPromptController;
@Mock
- AccessibilityManagerService mMockAccessibilityManagerService;
- @Mock
AccessibilityTraceManager mMockTraceManager;
@Rule
@@ -153,6 +153,8 @@
private long mLastDownTime = Integer.MIN_VALUE;
+ private float mOriginalMagnificationPersistedScale;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -166,6 +168,13 @@
when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
when(mockController.getAnimationDuration()).thenReturn(1000L);
when(mockWindowManager.setMagnificationCallbacks(eq(DISPLAY_0), any())).thenReturn(true);
+ mOriginalMagnificationPersistedScale = Settings.Secure.getFloatForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
+ UserHandle.USER_SYSTEM);
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
+ UserHandle.USER_SYSTEM);
mFullScreenMagnificationController = new FullScreenMagnificationController(
mockController,
new Object(),
@@ -192,6 +201,10 @@
mMgh.onDestroy();
mFullScreenMagnificationController.unregister(DISPLAY_0);
verify(mWindowMagnificationPromptController).onDestroy();
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ mOriginalMagnificationPersistedScale,
+ UserHandle.USER_SYSTEM);
}
@NonNull
@@ -525,10 +538,9 @@
final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
.CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
final float persistedScale = (1.0f + threshold) * scale + 1.0f;
- mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
- DEFAULT_Y, /* animate= */ false,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
- mFullScreenMagnificationController.persistScale(DISPLAY_0);
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
+ UserHandle.USER_SYSTEM);
mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
DEFAULT_Y, /* animate= */ false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
@@ -547,10 +559,9 @@
final float threshold = FullScreenMagnificationGestureHandler.PanningScalingState
.CHECK_DETECTING_PASS_PERSISTED_SCALE_THRESHOLD;
final float persistedScale = (1.0f + threshold) * scale - 0.1f;
- mFullScreenMagnificationController.setScale(DISPLAY_0, persistedScale, DEFAULT_X,
- DEFAULT_Y, /* animate= */ false,
- AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
- mFullScreenMagnificationController.persistScale(DISPLAY_0);
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, persistedScale,
+ UserHandle.USER_SYSTEM);
mFullScreenMagnificationController.setScale(DISPLAY_0, scale, DEFAULT_X,
DEFAULT_Y, /* animate= */ false,
AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index 27e6ef1..bbbab21 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -66,7 +66,6 @@
import com.android.server.statusbar.StatusBarManagerInternal;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -308,7 +307,6 @@
MagnificationScaleProvider.MAX_SCALE);
}
- @Ignore("b/278816260: We could refer to b/182561174#comment4 for solution.")
@Test
public void logTrackingTypingFocus_processScroll_logDuration() {
WindowMagnificationManager spyWindowMagnificationManager = spy(mWindowMagnificationManager);
diff --git a/services/tests/servicestests/src/com/android/server/appop/OWNERS b/services/tests/servicestests/src/com/android/server/appop/OWNERS
index 999ea0e..2fe78c5 100644
--- a/services/tests/servicestests/src/com/android/server/appop/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/appop/OWNERS
@@ -1 +1,2 @@
+#Bug component: 137825
include /core/java/android/permission/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 8346050..0cfddd3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -106,7 +106,7 @@
@Mock private KeyStore mKeyStore;
@Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
@Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
- @Mock BiometricSensorPrivacy mBiometricSensorPrivacy;
+ @Mock private BiometricCameraManager mBiometricCameraManager;
private Random mRandom;
private IBinder mToken;
@@ -609,7 +609,7 @@
TEST_PACKAGE,
checkDevicePolicyManager,
mContext,
- mBiometricSensorPrivacy);
+ mBiometricCameraManager);
}
private AuthSession createAuthSession(List<BiometricSensor> sensors,
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 41f7dbc..fc62e75 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
@@ -113,6 +114,10 @@
private static final String ERROR_UNABLE_TO_PROCESS = "error_unable_to_process";
private static final String ERROR_USER_CANCELED = "error_user_canceled";
private static final String ERROR_LOCKOUT = "error_lockout";
+ private static final String FACE_SUBTITLE = "face_subtitle";
+ private static final String FINGERPRINT_SUBTITLE = "fingerprint_subtitle";
+ private static final String DEFAULT_SUBTITLE = "default_subtitle";
+
private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty";
@@ -151,6 +156,8 @@
private AuthSessionCoordinator mAuthSessionCoordinator;
@Mock
private UserManager mUserManager;
+ @Mock
+ private BiometricCameraManager mBiometricCameraManager;
BiometricContextProvider mBiometricContextProvider;
@@ -177,6 +184,7 @@
when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager);
when(mInjector.getRequestGenerator()).thenReturn(() -> TEST_REQUEST_ID);
when(mInjector.getUserManager(any())).thenReturn(mUserManager);
+ when(mInjector.getBiometricCameraManager(any())).thenReturn(mBiometricCameraManager);
when(mResources.getString(R.string.biometric_error_hw_unavailable))
.thenReturn(ERROR_HW_UNAVAILABLE);
@@ -188,6 +196,12 @@
.thenReturn(ERROR_NOT_RECOGNIZED);
when(mResources.getString(R.string.biometric_error_user_canceled))
.thenReturn(ERROR_USER_CANCELED);
+ when(mContext.getString(R.string.biometric_dialog_face_subtitle))
+ .thenReturn(FACE_SUBTITLE);
+ when(mContext.getString(R.string.biometric_dialog_fingerprint_subtitle))
+ .thenReturn(FINGERPRINT_SUBTITLE);
+ when(mContext.getString(R.string.biometric_dialog_default_subtitle))
+ .thenReturn(DEFAULT_SUBTITLE);
when(mWindowManager.getDefaultDisplay()).thenReturn(
new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
@@ -208,7 +222,7 @@
@Test
public void testClientBinderDied_whenPaused() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
@@ -235,7 +249,7 @@
@Test
public void testClientBinderDied_whenAuthenticating() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
@@ -371,7 +385,7 @@
final int[] modalities = new int[] {
TYPE_FINGERPRINT,
- BiometricAuthenticator.TYPE_FACE,
+ TYPE_FACE,
};
final int[] strengths = new int[] {
@@ -424,9 +438,56 @@
}
@Test
+ public void testAuthenticateFace_shouldShowSubtitleForFace() throws Exception {
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ null);
+ waitForIdle();
+
+ assertEquals(FACE_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+ }
+
+ @Test
+ public void testAuthenticateFingerprint_shouldShowSubtitleForFingerprint() throws Exception {
+ setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ null);
+ waitForIdle();
+
+ assertEquals(FINGERPRINT_SUBTITLE,
+ mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+ }
+
+ @Test
+ public void testAuthenticateBothFpAndFace_shouldShowDefaultSubtitle() throws Exception {
+ final int[] modalities = new int[] {
+ TYPE_FINGERPRINT,
+ TYPE_FACE,
+ };
+
+ final int[] strengths = new int[] {
+ Authenticators.BIOMETRIC_WEAK,
+ Authenticators.BIOMETRIC_STRONG,
+ };
+
+ setupAuthForMultiple(modalities, strengths);
+
+ invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */,
+ null);
+ waitForIdle();
+
+ assertEquals(DEFAULT_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+ }
+
+ @Test
public void testAuthenticateFace_respectsUserSetting()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
// Disabled in user settings receives onError
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
@@ -565,7 +626,7 @@
@Test
public void testAuthenticate_noBiometrics_credentialAllowed() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(true);
@@ -592,13 +653,13 @@
@Test
public void testAuthenticate_happyPathWithConfirmation_strongBiometric() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
testAuthenticate_happyPathWithConfirmation(true /* isStrongBiometric */);
}
@Test
public void testAuthenticate_happyPathWithConfirmation_weakBiometric() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_WEAK);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_WEAK);
testAuthenticate_happyPathWithConfirmation(false /* isStrongBiometric */);
}
@@ -634,7 +695,7 @@
@Test
public void testAuthenticate_no_Biometrics_noCredential() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(false);
@@ -652,7 +713,7 @@
@Test
public void testRejectFace_whenAuthenticating_notifiesSystemUIAndClient_thenPaused()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -660,7 +721,7 @@
waitForIdle();
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED),
eq(0 /* vendorCode */));
verify(mReceiver1).onAuthenticationFailed();
@@ -688,7 +749,7 @@
@Test
public void testRequestAuthentication_whenAlreadyAuthenticating() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -697,7 +758,7 @@
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricPrompt.BIOMETRIC_ERROR_CANCELED),
eq(0) /* vendorCode */);
verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
@@ -707,7 +768,7 @@
@Test
public void testErrorHalTimeout_whenAuthenticating_entersPausedState() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -720,7 +781,7 @@
assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState());
verify(mBiometricService.mStatusBarService).onBiometricError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_TIMEOUT),
eq(0 /* vendorCode */));
// Timeout does not count as fail as per BiometricPrompt documentation.
@@ -756,7 +817,7 @@
@Test
public void testErrorFromHal_whenPaused_notifiesSystemUIAndClient() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -774,7 +835,7 @@
// Client receives error immediately
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
eq(0 /* vendorCode */));
// Dialog is hidden immediately
@@ -923,7 +984,7 @@
int biometricPromptError) throws Exception {
final int[] modalities = new int[] {
TYPE_FINGERPRINT,
- BiometricAuthenticator.TYPE_FACE,
+ TYPE_FACE,
};
final int[] strengths = new int[] {
@@ -1120,7 +1181,7 @@
@Test
public void testDismissedReasonNegative_whilePaused_invokeHalCancel() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -1139,7 +1200,7 @@
@Test
public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, null /* authenticators */);
@@ -1158,7 +1219,7 @@
@Test
public void testDismissedReasonUserCancel_whenPendingConfirmation() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
true /* requireConfirmation */, null /* authenticators */);
@@ -1172,7 +1233,7 @@
verify(mBiometricService.mSensors.get(0).impl)
.cancelAuthenticationFromService(any(), any(), anyLong());
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_FACE),
+ eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(0 /* vendorCode */));
verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
@@ -1293,7 +1354,7 @@
@Test
public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
.thenReturn(true);
@@ -1587,7 +1648,7 @@
@Test
public void testWorkAuthentication_faceWorksIfNotDisabledByDevicePolicyManager()
throws Exception {
- setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
+ setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
when(mDevicePolicyManager
.getKeyguardDisabledFeatures(any() /* admin*/, anyInt() /* userHandle */))
.thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FACE);
@@ -1680,7 +1741,7 @@
mFingerprintAuthenticator);
}
- if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+ if ((modality & TYPE_FACE) != 0) {
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(enrolled);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
@@ -1712,7 +1773,7 @@
strength, mFingerprintAuthenticator);
}
- if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
+ if ((modality & TYPE_FACE) != 0) {
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality,
@@ -1795,6 +1856,7 @@
boolean checkDevicePolicy) {
final PromptInfo promptInfo = new PromptInfo();
promptInfo.setConfirmationRequested(requireConfirmation);
+ promptInfo.setUseDefaultSubtitle(true);
if (authenticators != null) {
promptInfo.setAuthenticators(authenticators);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index 0c98c8d..c2bdf50 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -67,7 +67,7 @@
@Mock
BiometricService.SettingObserver mSettingObserver;
@Mock
- BiometricSensorPrivacy mBiometricSensorPrivacyUtil;
+ BiometricCameraManager mBiometricCameraManager;
@Before
public void setup() throws RemoteException {
@@ -79,11 +79,13 @@
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(LOCKOUT_NONE);
+ when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(false);
+ when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(false);
}
@Test
public void testFaceAuthentication_whenCameraPrivacyIsEnabled() throws Exception {
- when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(true);
+ when(mBiometricCameraManager.isCameraPrivacyEnabled()).thenReturn(true);
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
@@ -104,15 +106,14 @@
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
mSettingObserver, List.of(sensor),
0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil);
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
assertThat(preAuthInfo.eligibleSensors).isEmpty();
}
@Test
- public void testFaceAuthentication_whenCameraPrivacyIsDisabled() throws Exception {
- when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(false);
-
+ public void testFaceAuthentication_whenCameraPrivacyIsDisabledAndCameraIsAvailable()
+ throws Exception {
BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
@Override
@@ -132,8 +133,35 @@
PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
mSettingObserver, List.of(sensor),
0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
- false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil);
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
assertThat(preAuthInfo.eligibleSensors).hasSize(1);
}
+
+ @Test
+ public void testFaceAuthentication_whenCameraIsUnavailable() throws RemoteException {
+ when(mBiometricCameraManager.isAnyCameraUnavailable()).thenReturn(true);
+ BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+ @Override
+ boolean confirmationAlwaysRequired(int userId) {
+ return false;
+ }
+
+ @Override
+ boolean confirmationSupported() {
+ return false;
+ }
+ };
+ PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+ PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor),
+ 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+ assertThat(preAuthInfo.eligibleSensors).hasSize(0);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java b/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java
new file mode 100644
index 0000000..df4731f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FeatureFlagsServiceTest.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.flags.IFeatureFlagsCallback;
+import android.flags.SyncableFlag;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class FeatureFlagsServiceTest {
+ private static final String NS = "ns";
+ private static final String NAME = "name";
+ private static final String PROP_NAME = FlagOverrideStore.getPropName(NS, NAME);
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private FlagOverrideStore mFlagStore;
+ @Mock
+ private FlagsShellCommand mFlagCommand;
+ @Mock
+ private IFeatureFlagsCallback mIFeatureFlagsCallback;
+ @Mock
+ private IBinder mIFeatureFlagsCallbackAsBinder;
+ @Mock
+ private FeatureFlagsService.PermissionsChecker mPermissionsChecker;
+
+ private FeatureFlagsBinder mFeatureFlagsService;
+
+ @Before
+ public void setup() {
+ when(mIFeatureFlagsCallback.asBinder()).thenReturn(mIFeatureFlagsCallbackAsBinder);
+ mFeatureFlagsService = new FeatureFlagsBinder(
+ mFlagStore, mFlagCommand, mPermissionsChecker);
+ }
+
+ @Test
+ public void testRegisterCallback() {
+ mFeatureFlagsService.registerCallback(mIFeatureFlagsCallback);
+ try {
+ verify(mIFeatureFlagsCallbackAsBinder).linkToDeath(any(), eq(0));
+ } catch (RemoteException e) {
+ fail("Our mock threw a Remote Exception?");
+ }
+ }
+
+ @Test
+ public void testOverrideFlag_requiresWritePermission() {
+ SecurityException exc = new SecurityException("not allowed");
+ doThrow(exc).when(mPermissionsChecker).assertWritePermission();
+
+ SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+ try {
+ mFeatureFlagsService.overrideFlag(f);
+ fail("Should have thrown exception");
+ } catch (SecurityException e) {
+ assertThat(exc).isEqualTo(e);
+ } catch (Exception e) {
+ fail("should have thrown a security exception");
+ }
+ }
+
+ @Test
+ public void testResetFlag_requiresWritePermission() {
+ SecurityException exc = new SecurityException("not allowed");
+ doThrow(exc).when(mPermissionsChecker).assertWritePermission();
+
+ SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+ try {
+ mFeatureFlagsService.resetFlag(f);
+ fail("Should have thrown exception");
+ } catch (SecurityException e) {
+ assertThat(exc).isEqualTo(e);
+ } catch (Exception e) {
+ fail("should have thrown a security exception");
+ }
+ }
+
+ @Test
+ public void testSyncFlags_noOverrides() {
+ List<SyncableFlag> inputFlags = List.of(
+ new SyncableFlag(NS, "a", "false", false),
+ new SyncableFlag(NS, "b", "true", false),
+ new SyncableFlag(NS, "c", "false", false)
+ );
+
+ List<SyncableFlag> outputFlags = mFeatureFlagsService.syncFlags(inputFlags);
+
+ assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+ for (SyncableFlag inpF: inputFlags) {
+ boolean found = false;
+ for (SyncableFlag outF : outputFlags) {
+ if (compareSyncableFlagsNames(inpF, outF)) {
+ found = true;
+ break;
+ }
+ }
+ assertWithMessage("Failed to find input flag " + inpF + " in the output")
+ .that(found).isTrue();
+ }
+ }
+
+ @Test
+ public void testSyncFlags_withSomeOverrides() {
+ List<SyncableFlag> inputFlags = List.of(
+ new SyncableFlag(NS, "a", "false", false),
+ new SyncableFlag(NS, "b", "true", false),
+ new SyncableFlag(NS, "c", "false", false)
+ );
+
+ assertThat(mFlagStore).isNotNull();
+ when(mFlagStore.get(NS, "c")).thenReturn("true");
+ List<SyncableFlag> outputFlags = mFeatureFlagsService.syncFlags(inputFlags);
+
+ assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+ for (SyncableFlag inpF: inputFlags) {
+ boolean found = false;
+ for (SyncableFlag outF : outputFlags) {
+ if (compareSyncableFlagsNames(inpF, outF)) {
+ found = true;
+
+ // Once we've found "c", do an extra check
+ if (outF.getName().equals("c")) {
+ assertWithMessage("Flag " + outF + "was not returned with an override")
+ .that(outF.getValue()).isEqualTo("true");
+ }
+ break;
+ }
+ }
+ assertWithMessage("Failed to find input flag " + inpF + " in the output")
+ .that(found).isTrue();
+ }
+ }
+
+ @Test
+ public void testSyncFlags_twoCallsWithDifferentDefaults() {
+ List<SyncableFlag> inputFlagsFirst = List.of(
+ new SyncableFlag(NS, "a", "false", false)
+ );
+ List<SyncableFlag> inputFlagsSecond = List.of(
+ new SyncableFlag(NS, "a", "true", false),
+ new SyncableFlag(NS, "b", "false", false)
+ );
+
+ List<SyncableFlag> outputFlagsFirst = mFeatureFlagsService.syncFlags(inputFlagsFirst);
+ List<SyncableFlag> outputFlagsSecond = mFeatureFlagsService.syncFlags(inputFlagsSecond);
+
+ assertThat(inputFlagsFirst.size()).isEqualTo(outputFlagsFirst.size());
+ assertThat(inputFlagsSecond.size()).isEqualTo(outputFlagsSecond.size());
+
+ // This test only cares that the "a" flag passed in the second time came out with the
+ // same value that was passed in the first time.
+
+ boolean found = false;
+ for (SyncableFlag second : outputFlagsSecond) {
+ if (compareSyncableFlagsNames(second, inputFlagsFirst.get(0))) {
+ found = true;
+ assertThat(second.getValue()).isEqualTo(inputFlagsFirst.get(0).getValue());
+ break;
+ }
+ }
+
+ assertWithMessage(
+ "Failed to find flag " + inputFlagsFirst.get(0) + " in the second calls output")
+ .that(found).isTrue();
+ }
+
+ @Test
+ public void testQueryFlags_onlyOnce() {
+ List<SyncableFlag> inputFlags = List.of(
+ new SyncableFlag(NS, "a", "false", false),
+ new SyncableFlag(NS, "b", "true", false),
+ new SyncableFlag(NS, "c", "false", false)
+ );
+
+ List<SyncableFlag> outputFlags = mFeatureFlagsService.queryFlags(inputFlags);
+
+ assertThat(inputFlags.size()).isEqualTo(outputFlags.size());
+
+ for (SyncableFlag inpF: inputFlags) {
+ boolean found = false;
+ for (SyncableFlag outF : outputFlags) {
+ if (compareSyncableFlagsNames(inpF, outF)) {
+ found = true;
+ break;
+ }
+ }
+ assertWithMessage("Failed to find input flag " + inpF + " in the output")
+ .that(found).isTrue();
+ }
+ }
+
+ @Test
+ public void testQueryFlags_twoCallsWithDifferentDefaults() {
+ List<SyncableFlag> inputFlagsFirst = List.of(
+ new SyncableFlag(NS, "a", "false", false)
+ );
+ List<SyncableFlag> inputFlagsSecond = List.of(
+ new SyncableFlag(NS, "a", "true", false),
+ new SyncableFlag(NS, "b", "false", false)
+ );
+
+ List<SyncableFlag> outputFlagsFirst = mFeatureFlagsService.queryFlags(inputFlagsFirst);
+ List<SyncableFlag> outputFlagsSecond = mFeatureFlagsService.queryFlags(inputFlagsSecond);
+
+ assertThat(inputFlagsFirst.size()).isEqualTo(outputFlagsFirst.size());
+ assertThat(inputFlagsSecond.size()).isEqualTo(outputFlagsSecond.size());
+
+ // This test only cares that the "a" flag passed in the second time came out with the
+ // same value that was passed in (i.e. it wasn't cached).
+
+ boolean found = false;
+ for (SyncableFlag second : outputFlagsSecond) {
+ if (compareSyncableFlagsNames(second, inputFlagsSecond.get(0))) {
+ found = true;
+ assertThat(second.getValue()).isEqualTo(inputFlagsSecond.get(0).getValue());
+ break;
+ }
+ }
+
+ assertWithMessage(
+ "Failed to find flag " + inputFlagsSecond.get(0) + " in the second calls output")
+ .that(found).isTrue();
+ }
+
+ @Test
+ public void testOverrideFlag() {
+ SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+ mFeatureFlagsService.overrideFlag(f);
+
+ verify(mFlagStore).set(f.getNamespace(), f.getName(), f.getValue());
+ }
+
+ @Test
+ public void testResetFlag() {
+ SyncableFlag f = new SyncableFlag(NS, "a", "false", false);
+
+ mFeatureFlagsService.resetFlag(f);
+
+ verify(mFlagStore).erase(f.getNamespace(), f.getName());
+ }
+
+
+ private static boolean compareSyncableFlagsNames(SyncableFlag a, SyncableFlag b) {
+ return a.getNamespace().equals(b.getNamespace())
+ && a.getName().equals(b.getName())
+ && a.isDynamic() == b.isDynamic();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java b/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java
new file mode 100644
index 0000000..c2cf540
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FlagCacheTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class FlagCacheTest {
+ private static final String NS = "ns";
+ private static final String NAME = "name";
+
+ FlagCache mFlagCache = new FlagCache();
+
+ @Test
+ public void testGetOrNull_unset() {
+ assertThat(mFlagCache.getOrNull(NS, NAME)).isNull();
+ }
+
+ @Test
+ public void testGetOrSet_unset() {
+ assertThat(mFlagCache.getOrSet(NS, NAME, "value")).isEqualTo("value");
+ }
+
+ @Test
+ public void testGetOrSet_alreadySet() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.getOrSet(NS, NAME, "newvalue")).isEqualTo("value");
+ }
+
+ @Test
+ public void testSetIfChanged_unset() {
+ assertThat(mFlagCache.setIfChanged(NS, NAME, "value")).isTrue();
+ }
+
+ @Test
+ public void testSetIfChanged_noChange() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.setIfChanged(NS, NAME, "value")).isFalse();
+ }
+
+ @Test
+ public void testSetIfChanged_changing() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.setIfChanged(NS, NAME, "newvalue")).isTrue();
+ }
+
+ @Test
+ public void testContainsNamespace_unset() {
+ assertThat(mFlagCache.containsNamespace(NS)).isFalse();
+ }
+
+ @Test
+ public void testContainsNamespace_set() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.containsNamespace(NS)).isTrue();
+ }
+
+ @Test
+ public void testContains_unset() {
+ assertThat(mFlagCache.contains(NS, NAME)).isFalse();
+ }
+
+ @Test
+ public void testContains_set() {
+ mFlagCache.setIfChanged(NS, NAME, "value");
+ assertThat(mFlagCache.contains(NS, NAME)).isTrue();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java b/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java
new file mode 100644
index 0000000..6cc3acf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/FlagOverrideStoreTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.flags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class FlagOverrideStoreTest {
+ private static final String NS = "ns";
+ private static final String NAME = "name";
+ private static final String PROP_NAME = FlagOverrideStore.getPropName(NS, NAME);
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private SettingsProxy mSettingsProxy;
+ @Mock
+ private FlagOverrideStore.FlagChangeCallback mCallback;
+
+ private FlagOverrideStore mFlagStore;
+
+ @Before
+ public void setup() {
+ mFlagStore = new FlagOverrideStore(mSettingsProxy);
+ mFlagStore.setChangeCallback(mCallback);
+ }
+
+ @Test
+ public void testSet_unset() {
+ mFlagStore.set(NS, NAME, "value");
+ verify(mSettingsProxy).putString(PROP_NAME, "value");
+ }
+
+ @Test
+ public void testSet_setTwice() {
+ mFlagStore.set(NS, NAME, "value");
+ mFlagStore.set(NS, NAME, "newvalue");
+ verify(mSettingsProxy).putString(PROP_NAME, "value");
+ verify(mSettingsProxy).putString(PROP_NAME, "newvalue");
+ }
+
+ @Test
+ public void testGet_unset() {
+ assertThat(mFlagStore.get(NS, NAME)).isNull();
+ }
+
+ @Test
+ public void testGet_set() {
+ when(mSettingsProxy.getString(PROP_NAME)).thenReturn("value");
+ assertThat(mFlagStore.get(NS, NAME)).isEqualTo("value");
+ }
+
+ @Test
+ public void testErase() {
+ mFlagStore.erase(NS, NAME);
+ verify(mSettingsProxy).putString(PROP_NAME, null);
+ }
+
+ @Test
+ public void testContains_unset() {
+ assertThat(mFlagStore.contains(NS, NAME)).isFalse();
+ }
+
+ @Test
+ public void testContains_set() {
+ when(mSettingsProxy.getString(PROP_NAME)).thenReturn("value");
+ assertThat(mFlagStore.contains(NS, NAME)).isTrue();
+ }
+
+ @Test
+ public void testCallback_onSet() {
+ mFlagStore.set(NS, NAME, "value");
+ verify(mCallback).onFlagChanged(NS, NAME, "value");
+ }
+
+ @Test
+ public void testCallback_onErase() {
+ mFlagStore.erase(NS, NAME);
+ verify(mCallback).onFlagChanged(NS, NAME, null);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/flags/OWNERS b/services/tests/servicestests/src/com/android/server/flags/OWNERS
new file mode 100644
index 0000000..7ed369e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/flags/OWNERS
@@ -0,0 +1 @@
+include /services/flags/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
index 2405757..30ce961 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
@@ -19,7 +19,9 @@
import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_UNKNOWN;
import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -206,4 +208,45 @@
.earcStatusChanged(false, false, HDMI_EARC_STATUS_UNKNOWN,
HDMI_EARC_STATUS_UNKNOWN, HdmiStatsEnums.LOG_REASON_WAKE);
}
+
+ @Test
+ public void testEarcStatusChanged_handleEarcStateChange_unSupportedPort_writesAtom() {
+ // Initialize HDMI port with eARC not supported.
+ HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+ hdmiPortInfos[0] =
+ new HdmiPortInfo.Builder(EARC_PORT_ID, HdmiPortInfo.PORT_OUTPUT, 0x0000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .setEarcSupported(false)
+ .build();
+ mNativeWrapper.setPortInfo(hdmiPortInfos);
+ mNativeWrapper.setPortConnectionStatus(EARC_PORT_ID, true);
+ mHdmiControlServiceSpy.initService();
+ mTestLooper.dispatchAll();
+
+ mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+ mTestLooper.dispatchAll();
+ Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+
+ mHdmiControlServiceSpy.handleEarcStateChange(HDMI_EARC_STATUS_EARC_PENDING, EARC_PORT_ID);
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .earcStatusChanged(eq(false), eq(true), anyInt(),
+ eq(HDMI_EARC_STATUS_EARC_PENDING),
+ eq(HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED_UNSUPPORTED_PORT));
+ }
+
+ @Test
+ public void testEarcStatusChanged_handleEarcStateChange_wrongState_writesAtom() {
+ mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
+ mTestLooper.dispatchAll();
+ Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+
+ // mEarcLocalDevice should be empty since eARC is disabled.
+ mHdmiControlServiceSpy.handleEarcStateChange(HDMI_EARC_STATUS_EARC_PENDING, EARC_PORT_ID);
+ verify(mHdmiCecAtomWriterSpy, times(1))
+ .earcStatusChanged(eq(true), eq(false), anyInt(),
+ eq(HDMI_EARC_STATUS_EARC_PENDING),
+ eq(HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED_WRONG_STATE));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ArchiveManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ArchiveManagerTest.java
deleted file mode 100644
index 73a6bb7..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/ArchiveManagerTest.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.when;
-
-import android.content.IntentSender;
-import android.content.pm.PackageManager;
-import android.os.Binder;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.pm.pkg.PackageStateInternal;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class ArchiveManagerTest {
-
- private static final String PACKAGE = "com.example";
- private static final String CALLER_PACKAGE = "com.vending";
- private static final int USER_ID = 1;
-
- @Mock private IntentSender mIntentSender;
- @Mock private PackageManagerService mPm;
- @Mock private Computer mComputer;
- @Mock private PackageStateInternal mPackageState;
- private InstallSource mInstallSource =
- InstallSource.create(
- CALLER_PACKAGE,
- CALLER_PACKAGE,
- CALLER_PACKAGE,
- Binder.getCallingUid(),
- CALLER_PACKAGE,
- /* installerAttributionTag= */ null,
- /* packageSource= */ 0);
-
- private ArchiveManager mArchiveManager;
- private int mCallingUid;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mArchiveManager = new ArchiveManager(mPm);
- mCallingUid = Binder.getCallingUid();
- when(mPm.snapshotComputer()).thenReturn(mComputer);
- when(mComputer.getNameForUid(eq(mCallingUid))).thenReturn(CALLER_PACKAGE);
- when(mComputer.getPackageStateInternal(eq(PACKAGE))).thenReturn(mPackageState);
- when(mPackageState.getInstallSource()).thenReturn(mInstallSource);
- }
-
- @Test
- public void archiveApp_callerPackageNameIncorrect() {
- Exception e = assertThrows(
- SecurityException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, "different", USER_ID, mIntentSender)
- );
- assertThat(e).hasMessageThat().isEqualTo(
- String.format(
- "The callerPackageName %s set by the caller doesn't match the "
- + "caller's own package name %s.",
- "different",
- CALLER_PACKAGE));
- }
-
- @Test
- public void archiveApp_packageNotInstalled() {
- when(mComputer.getPackageStateInternal(eq(PACKAGE))).thenReturn(null);
-
- Exception e = assertThrows(
- PackageManager.NameNotFoundException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, USER_ID, mIntentSender)
- );
- assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE));
- }
-
- @Test
- public void archiveApp_callerNotInstallerOfRecord() {
- InstallSource otherInstallSource =
- InstallSource.create(
- CALLER_PACKAGE,
- CALLER_PACKAGE,
- /* installerPackageName= */ "different",
- Binder.getCallingUid(),
- CALLER_PACKAGE,
- /* installerAttributionTag= */ null,
- /* packageSource= */ 0);
- when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
-
- Exception e = assertThrows(
- SecurityException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, USER_ID, mIntentSender)
- );
- assertThat(e).hasMessageThat().isEqualTo(
- String.format("Caller is not the installer of record for %s.", PACKAGE));
- }
-
- @Test
- public void archiveApp_callerNotUpdateOwner() {
- InstallSource otherInstallSource =
- InstallSource.create(
- CALLER_PACKAGE,
- CALLER_PACKAGE,
- CALLER_PACKAGE,
- Binder.getCallingUid(),
- /* updateOwnerPackageName= */ "different",
- /* installerAttributionTag= */ null,
- /* packageSource= */ 0);
- when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
-
- Exception e = assertThrows(
- SecurityException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, USER_ID, mIntentSender)
- );
- assertThat(e).hasMessageThat().isEqualTo(
- String.format("Caller is not the update owner for %s.", PACKAGE));
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
index d9b698e..43bf537 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
@@ -16,17 +16,19 @@
package com.android.server.pm;
import static android.os.UserHandle.USER_NULL;
-import static android.os.UserHandle.USER_SYSTEM;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assume.assumeFalse;
+
import android.app.ActivityManager;
import android.app.IStopUserCallback;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Postsubmit;
import android.provider.Settings;
@@ -62,7 +64,7 @@
private static final String TAG = "UserLifecycleStressTest";
// TODO: Make this smaller once we have improved it.
private static final int TIMEOUT_IN_SECOND = 40;
- private static final int CHECK_USER_REMOVED_INTERVAL_MS = 200;
+ private static final int CHECK_USER_REMOVED_INTERVAL_MS = 500;
private static final int NUM_ITERATIONS = 8;
private static final int WAIT_BEFORE_STOP_USER_IN_SECOND = 3;
@@ -75,6 +77,7 @@
private ActivityManager mActivityManager;
private UserSwitchWaiter mUserSwitchWaiter;
private String mRemoveGuestOnExitOriginalValue;
+ private int mOriginalCurrentUserId;
@Before
public void setup() throws RemoteException {
@@ -85,10 +88,12 @@
mRemoveGuestOnExitOriginalValue = Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.REMOVE_GUEST_ON_EXIT);
waitForBroadcastBarrier(); // isolate tests from each other
+ mOriginalCurrentUserId = ActivityManager.getCurrentUser();
}
@After
public void tearDown() throws IOException {
+ switchUser(mOriginalCurrentUserId);
mUserSwitchWaiter.close();
Settings.Global.putString(mContext.getContentResolver(),
Settings.Global.REMOVE_GUEST_ON_EXIT, mRemoveGuestOnExitOriginalValue);
@@ -101,6 +106,10 @@
*/
@Test
public void stopManagedProfileStressTest() throws RemoteException, InterruptedException {
+ UserHandle mainUser = mUserManager.getMainUser();
+ assumeFalse("There is no main user", mainUser == null);
+ switchUser(mainUser.getIdentifier());
+
for (int i = 0; i < NUM_ITERATIONS; i++) {
logIteration(i, "stopManagedProfileStressTest");
@@ -164,10 +173,6 @@
Settings.Global.putString(mContext.getContentResolver(),
Settings.Global.REMOVE_GUEST_ON_EXIT, "0");
- if (ActivityManager.getCurrentUser() != USER_SYSTEM) {
- switchUser(USER_SYSTEM);
- }
-
final List<UserInfo> guestUsers = mUserManager.getGuestUsers();
int nextGuestId = guestUsers.isEmpty() ? USER_NULL : guestUsers.get(0).id;
@@ -202,8 +207,8 @@
.isTrue();
}
- Log.d(TAG, "Switching back to the system user");
- switchUser(USER_SYSTEM);
+ Log.d(TAG, "Switching back to the initial user");
+ switchUser(mOriginalCurrentUserId);
nextGuestId = newGuest.id;
}
@@ -253,6 +258,10 @@
/** Starts the given user in the foreground and waits for the switch to finish. */
private void switchUser(int userId) {
+ if (ActivityManager.getCurrentUser() == userId) {
+ Log.d(TAG, "No need to switch, current user is already user " + userId);
+ return;
+ }
Log.d(TAG, "Switching to user " + userId);
mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(userId, () -> {
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index 5bd7116..835ccf0 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -30,6 +30,7 @@
import android.provider.Settings.Global;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
import android.util.ArrayMap;
import com.android.frameworks.servicestests.R;
@@ -111,6 +112,7 @@
testServiceDefaultValue_On(ServiceType.NULL);
}
+ @Suppress
@SmallTest
public void testGetBatterySaverPolicy_PolicyVibration_DefaultValueCorrect() {
testDefaultValue(
@@ -217,6 +219,7 @@
ServiceType.QUICK_DOZE);
}
+ @Suppress
@SmallTest
public void testUpdateConstants_getCorrectData() {
mBatterySaverPolicy.updateConstantsLocked(BATTERY_SAVER_CONSTANTS, "");
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 7e81ef2..a109d5c 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -73,6 +73,7 @@
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
@@ -405,6 +406,7 @@
UriGrantsManagerInternal mUgmInternal;
@Mock
AppOpsManager mAppOpsManager;
+ private AppOpsManager.OnOpChangedListener mOnPermissionChangeListener;
@Mock
private TestableNotificationManagerService.NotificationAssistantAccessGrantedCallback
mNotificationAssistantAccessGrantedCallback;
@@ -604,6 +606,12 @@
tr.addOverride(com.android.internal.R.string.config_defaultSearchSelectorPackageName,
SEARCH_SELECTOR_PKG);
+ doAnswer(invocation -> {
+ mOnPermissionChangeListener = invocation.getArgument(2);
+ return null;
+ }).when(mAppOpsManager).startWatchingMode(eq(AppOpsManager.OP_POST_NOTIFICATION), any(),
+ any());
+
mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
@@ -2002,10 +2010,10 @@
final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
sbn.getNotification(), sbn.getUserId());
- Thread.sleep(1); // make sure the system clock advances before the next step
+ mTestableLooper.moveTimeForward(1);
// THEN it is canceled
mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
- Thread.sleep(1); // here too
+ mTestableLooper.moveTimeForward(1);
// THEN it is posted again (before the cancel has a chance to finish)
mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", sbn.getId(),
sbn.getNotification(), sbn.getUserId());
@@ -2295,8 +2303,8 @@
mTestNotificationChannel, 1, "group", true);
notif.getNotification().flags |= Notification.FLAG_NO_CLEAR;
mService.addNotification(notif);
- mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true,
- notif.getUserId(), 0, null);
+ mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0,
+ notif.getUserId(), REASON_CANCEL);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3034,7 +3042,7 @@
notif.getNotification().flags |= Notification.FLAG_NO_CLEAR;
mService.addNotification(notif);
mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0,
- Notification.FLAG_ONGOING_EVENT, true, notif.getUserId(), 0, null);
+ Notification.FLAG_ONGOING_EVENT, notif.getUserId(), REASON_CANCEL);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3061,8 +3069,8 @@
mTestNotificationChannel, 1, "group", true);
notif.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
mService.addNotification(notif);
- mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true,
- notif.getUserId(), 0, null);
+ mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0,
+ notif.getUserId(), REASON_CANCEL);
waitForIdle();
StatusBarNotification[] notifs =
mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
@@ -3216,48 +3224,6 @@
}
@Test
- public void testUpdateAppNotifyCreatorBlock() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
-
- mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
- Thread.sleep(500);
- waitForIdle();
-
- ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
-
- assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
- captor.getValue().getAction());
- assertEquals(PKG, captor.getValue().getPackage());
- assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
- }
-
- @Test
- public void testUpdateAppNotifyCreatorBlock_notIfMatchesExistingSetting() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
-
- mBinderService.setNotificationsEnabledForPackage(PKG, 0, false);
- verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null));
- }
-
- @Test
- public void testUpdateAppNotifyCreatorUnblock() throws Exception {
- when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
-
- mBinderService.setNotificationsEnabledForPackage(PKG, mUid, true);
- Thread.sleep(500);
- waitForIdle();
-
- ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
-
- assertEquals(NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED,
- captor.getValue().getAction());
- assertEquals(PKG, captor.getValue().getPackage());
- assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true));
- }
-
- @Test
public void testUpdateChannelNotifyCreatorBlock() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -12173,6 +12139,134 @@
any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
}
+ @Test
+ public void onOpChanged_permissionRevoked_cancelsAllNotificationsFromPackage()
+ throws RemoteException {
+ // Have preexisting posted notifications from revoked package and other packages.
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("revoked", 1001, 1, 0), mTestNotificationChannel));
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("other", 1002, 2, 0), mTestNotificationChannel));
+ // Have preexisting enqueued notifications from revoked package and other packages.
+ mService.addEnqueuedNotification(new NotificationRecord(mContext,
+ generateSbn("revoked", 1001, 3, 0), mTestNotificationChannel));
+ mService.addEnqueuedNotification(new NotificationRecord(mContext,
+ generateSbn("other", 1002, 4, 0), mTestNotificationChannel));
+ assertThat(mService.mNotificationList).hasSize(2);
+ assertThat(mService.mEnqueuedNotifications).hasSize(2);
+
+ when(mPackageManagerInternal.getPackageUid("revoked", 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false);
+
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, "revoked", 0);
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).hasSize(1);
+ assertThat(mService.mNotificationList.get(0).getSbn().getPackageName()).isEqualTo("other");
+ assertThat(mService.mEnqueuedNotifications).hasSize(1);
+ assertThat(mService.mEnqueuedNotifications.get(0).getSbn().getPackageName()).isEqualTo(
+ "other");
+ }
+
+ @Test
+ public void onOpChanged_permissionStillGranted_notificationsAreNotAffected()
+ throws RemoteException {
+ // NOTE: This combination (receiving the onOpChanged broadcast for a package, the permission
+ // being now granted, AND having previously posted notifications from said package) should
+ // never happen (if we trust the broadcasts are correct). So this test is for a what-if
+ // scenario, to verify we still handle it reasonably.
+
+ // Have preexisting posted notifications from specific package and other packages.
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("granted", 1001, 1, 0), mTestNotificationChannel));
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("other", 1002, 2, 0), mTestNotificationChannel));
+ // Have preexisting enqueued notifications from specific package and other packages.
+ mService.addEnqueuedNotification(new NotificationRecord(mContext,
+ generateSbn("granted", 1001, 3, 0), mTestNotificationChannel));
+ mService.addEnqueuedNotification(new NotificationRecord(mContext,
+ generateSbn("other", 1002, 4, 0), mTestNotificationChannel));
+ assertThat(mService.mNotificationList).hasSize(2);
+ assertThat(mService.mEnqueuedNotifications).hasSize(2);
+
+ when(mPackageManagerInternal.getPackageUid("granted", 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true);
+
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, "granted", 0);
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).hasSize(2);
+ assertThat(mService.mEnqueuedNotifications).hasSize(2);
+ }
+
+ @Test
+ public void onOpChanged_permissionGranted_notifiesAppUnblocked() throws Exception {
+ when(mPackageManagerInternal.getPackageUid(PKG, 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(true);
+
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0);
+ waitForIdle();
+ mTestableLooper.moveTimeForward(500);
+ waitForIdle();
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+ assertThat(captor.getValue().getAction()).isEqualTo(
+ NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED);
+ assertThat(captor.getValue().getPackage()).isEqualTo(PKG);
+ assertThat(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, true)).isFalse();
+ }
+
+ @Test
+ public void onOpChanged_permissionRevoked_notifiesAppBlocked() throws Exception {
+ when(mPackageManagerInternal.getPackageUid(PKG, 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasPermission(eq(1001))).thenReturn(false);
+
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, PKG, 0);
+ waitForIdle();
+ mTestableLooper.moveTimeForward(500);
+ waitForIdle();
+
+ ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+ verify(mContext).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+ assertThat(captor.getValue().getAction()).isEqualTo(
+ NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED);
+ assertThat(captor.getValue().getPackage()).isEqualTo(PKG);
+ assertThat(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false)).isTrue();
+ }
+
+ @Test
+ public void setNotificationsEnabledForPackage_disabling_clearsNotifications() throws Exception {
+ mService.addNotification(new NotificationRecord(mContext,
+ generateSbn("package", 1001, 1, 0), mTestNotificationChannel));
+ assertThat(mService.mNotificationList).hasSize(1);
+ when(mPackageManagerInternal.getPackageUid("package", 0, 0)).thenReturn(1001);
+ when(mPermissionHelper.hasRequestedPermission(any(), eq("package"), anyInt())).thenReturn(
+ true);
+
+ // Start with granted permission and simulate effect of revoking it.
+ when(mPermissionHelper.hasPermission(1001)).thenReturn(true);
+ doAnswer(invocation -> {
+ when(mPermissionHelper.hasPermission(1001)).thenReturn(false);
+ mOnPermissionChangeListener.onOpChanged(
+ AppOpsManager.OPSTR_POST_NOTIFICATION, "package", 0);
+ return null;
+ }).when(mPermissionHelper).setNotificationPermission("package", 0, false, true);
+
+ mBinderService.setNotificationsEnabledForPackage("package", 1001, false);
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).hasSize(0);
+
+ mTestableLooper.moveTimeForward(500);
+ waitForIdle();
+ verify(mContext).sendBroadcastAsUser(any(), eq(UserHandle.of(0)), eq(null));
+ }
+
private static <T extends Parcelable> T parcelAndUnparcel(T source,
Parcelable.Creator<T> creator) {
Parcel parcel = Parcel.obtain();
diff --git a/services/tests/wmtests/OWNERS b/services/tests/wmtests/OWNERS
index 7a128fc..cece37f 100644
--- a/services/tests/wmtests/OWNERS
+++ b/services/tests/wmtests/OWNERS
@@ -1,3 +1,4 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=316125&template=1018199
include /services/core/java/com/android/server/wm/OWNERS
# Voice Interaction
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 5c3102d..65e77dc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -182,12 +182,12 @@
@Test
public void testLaunchState() {
- final ToIntFunction<Boolean> launchTemplate = doRelaunch -> {
+ final ToIntFunction<Runnable> launchTemplate = action -> {
clearInvocations(mLaunchObserver);
onActivityLaunched(mTopActivity);
notifyTransitionStarting(mTopActivity);
- if (doRelaunch) {
- mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity);
+ if (action != null) {
+ action.run();
}
final ActivityMetricsLogger.TransitionInfoSnapshot info =
notifyWindowsDrawn(mTopActivity);
@@ -199,21 +199,27 @@
// Assume that the process is started (ActivityBuilder has mocked the returned value of
// ATMS#getProcessController) but the activity has not attached process.
mTopActivity.app = null;
- assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
+ assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(null))
.isEqualTo(WaitResult.LAUNCH_STATE_WARM);
mTopActivity.app = app;
mNewActivityCreated = false;
- assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
+ assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(null))
.isEqualTo(WaitResult.LAUNCH_STATE_HOT);
- assertWithMessage("Relaunch").that(launchTemplate.applyAsInt(true /* doRelaunch */))
+ assertWithMessage("Relaunch").that(launchTemplate.applyAsInt(
+ () -> mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity)))
.isEqualTo(WaitResult.LAUNCH_STATE_RELAUNCH);
+ assertWithMessage("Cold launch by restart").that(launchTemplate.applyAsInt(
+ () -> mActivityMetricsLogger.notifyBindApplication(
+ mTopActivity.info.applicationInfo)))
+ .isEqualTo(WaitResult.LAUNCH_STATE_COLD);
+
mTopActivity.app = null;
mNewActivityCreated = true;
doReturn(null).when(mAtm).getProcessController(app.mName, app.mUid);
- assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
+ assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(null))
.isEqualTo(WaitResult.LAUNCH_STATE_COLD);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index fe1ea0d..f6f3f03 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -22,11 +22,17 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
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.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
import android.app.Activity;
import android.app.ActivityManager.RunningTaskInfo;
@@ -41,6 +47,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.util.Log;
import android.util.Rational;
import android.view.SurfaceControl;
import android.window.TaskOrganizer;
@@ -48,7 +55,10 @@
import androidx.test.filters.MediumTest;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -166,6 +176,122 @@
}
}
+ /**
+ * Tests if any unknown key is being used in the ActivityOptions bundle. If so, please review
+ * if the newly added bundle should be protected with permissions to avoid malicious attacks.
+ *
+ * @see SafeActivityOptionsTest#test_getOptions
+ */
+ @Test
+ public void testActivityOptionsFromBundle() {
+ // Spy on a bundle that is generated from a basic ActivityOptions.
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ Bundle bundle = options.toBundle();
+ spyOn(bundle);
+
+ // Create a new ActivityOptions from the bundle
+ new ActivityOptions(bundle);
+
+ // Verify the keys that are being used.
+ final ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class);
+ verify(bundle, atLeastOnce()).getString(stringCaptor.capture());
+ verify(bundle, atLeastOnce()).getBoolean(stringCaptor.capture());
+ verify(bundle, atLeastOnce()).getParcelable(stringCaptor.capture(), any());
+ verify(bundle, atLeastOnce()).getInt(stringCaptor.capture(), anyInt());
+ verify(bundle, atLeastOnce()).getBinder(stringCaptor.capture());
+ verify(bundle, atLeastOnce()).getBundle(stringCaptor.capture());
+ final List<String> keys = stringCaptor.getAllValues();
+ final List<String> unknownKeys = new ArrayList<>();
+ for (String key : keys) {
+ switch (key) {
+ case ActivityOptions.KEY_PACKAGE_NAME:
+ case ActivityOptions.KEY_LAUNCH_BOUNDS:
+ case ActivityOptions.KEY_ANIM_TYPE:
+ case ActivityOptions.KEY_ANIM_ENTER_RES_ID:
+ case ActivityOptions.KEY_ANIM_EXIT_RES_ID:
+ case ActivityOptions.KEY_ANIM_IN_PLACE_RES_ID:
+ case ActivityOptions.KEY_ANIM_BACKGROUND_COLOR:
+ case ActivityOptions.KEY_ANIM_THUMBNAIL:
+ case ActivityOptions.KEY_ANIM_START_X:
+ case ActivityOptions.KEY_ANIM_START_Y:
+ case ActivityOptions.KEY_ANIM_WIDTH:
+ case ActivityOptions.KEY_ANIM_HEIGHT:
+ case ActivityOptions.KEY_ANIM_START_LISTENER:
+ case ActivityOptions.KEY_SPLASH_SCREEN_THEME:
+ case ActivityOptions.KEY_LEGACY_PERMISSION_PROMPT_ELIGIBLE:
+ case ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN:
+ case ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN:
+ case ActivityOptions.KEY_TRANSIENT_LAUNCH:
+ case "android:activity.animationFinishedListener":
+ // KEY_ANIMATION_FINISHED_LISTENER
+ case "android:activity.animSpecs": // KEY_ANIM_SPECS
+ case "android:activity.lockTaskMode": // KEY_LOCK_TASK_MODE
+ case "android:activity.shareIdentity": // KEY_SHARE_IDENTITY
+ case "android.activity.launchDisplayId": // KEY_LAUNCH_DISPLAY_ID
+ case "android.activity.callerDisplayId": // KEY_CALLER_DISPLAY_ID
+ case "android.activity.launchTaskDisplayAreaToken":
+ // KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN
+ case "android.activity.launchTaskDisplayAreaFeatureId":
+ // KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID
+ case "android.activity.windowingMode": // KEY_LAUNCH_WINDOWING_MODE
+ case "android.activity.activityType": // KEY_LAUNCH_ACTIVITY_TYPE
+ case "android.activity.launchTaskId": // KEY_LAUNCH_TASK_ID
+ case "android.activity.disableStarting": // KEY_DISABLE_STARTING_WINDOW
+ case "android.activity.pendingIntentLaunchFlags":
+ // KEY_PENDING_INTENT_LAUNCH_FLAGS
+ case "android.activity.alwaysOnTop": // KEY_TASK_ALWAYS_ON_TOP
+ case "android.activity.taskOverlay": // KEY_TASK_OVERLAY
+ case "android.activity.taskOverlayCanResume": // KEY_TASK_OVERLAY_CAN_RESUME
+ case "android.activity.avoidMoveToFront": // KEY_AVOID_MOVE_TO_FRONT
+ case "android.activity.freezeRecentTasksReordering":
+ // KEY_FREEZE_RECENT_TASKS_REORDERING
+ case "android:activity.disallowEnterPictureInPictureWhileLaunching":
+ // KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING
+ case "android:activity.applyActivityFlagsForBubbles":
+ // KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES
+ case "android:activity.applyMultipleTaskFlagForShortcut":
+ // KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT
+ case "android:activity.applyNoUserActionFlagForShortcut":
+ // KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT
+ case "android:activity.transitionCompleteListener":
+ // KEY_TRANSITION_COMPLETE_LISTENER
+ case "android:activity.transitionIsReturning": // KEY_TRANSITION_IS_RETURNING
+ case "android:activity.sharedElementNames": // KEY_TRANSITION_SHARED_ELEMENTS
+ case "android:activity.resultData": // KEY_RESULT_DATA
+ case "android:activity.resultCode": // KEY_RESULT_CODE
+ case "android:activity.exitCoordinatorIndex": // KEY_EXIT_COORDINATOR_INDEX
+ case "android.activity.sourceInfo": // KEY_SOURCE_INFO
+ case "android:activity.usageTimeReport": // KEY_USAGE_TIME_REPORT
+ case "android:activity.rotationAnimationHint": // KEY_ROTATION_ANIMATION_HINT
+ case "android:instantapps.installerbundle": // KEY_INSTANT_APP_VERIFICATION_BUNDLE
+ case "android:activity.specsFuture": // KEY_SPECS_FUTURE
+ case "android:activity.remoteAnimationAdapter": // KEY_REMOTE_ANIMATION_ADAPTER
+ case "android:activity.remoteTransition": // KEY_REMOTE_TRANSITION
+ case "android:activity.overrideTaskTransition": // KEY_OVERRIDE_TASK_TRANSITION
+ case "android.activity.removeWithTaskOrganizer": // KEY_REMOVE_WITH_TASK_ORGANIZER
+ case "android.activity.launchTypeBubble": // KEY_LAUNCHED_FROM_BUBBLE
+ case "android.activity.splashScreenStyle": // KEY_SPLASH_SCREEN_STYLE
+ case "android.activity.launchIntoPipParams": // KEY_LAUNCH_INTO_PIP_PARAMS
+ case "android.activity.dismissKeyguard": // KEY_DISMISS_KEYGUARD
+ case "android.activity.pendingIntentCreatorBackgroundActivityStartMode":
+ // KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE
+ case "android.activity.launchCookie": // KEY_LAUNCH_COOKIE
+ // Existing keys
+ break;
+ default:
+ unknownKeys.add(key);
+ break;
+ }
+ }
+
+ // Report if any unknown key exists.
+ for (String key : unknownKeys) {
+ Log.e("ActivityOptionsTests", "Unknown key " + key + " is found. "
+ + "Please review if the given bundle should be protected with permissions.");
+ }
+ assertTrue(unknownKeys.isEmpty());
+ }
+
public static class TrampolineActivity extends Activity {
static int sTaskId;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 7284744..858a384 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1429,6 +1429,30 @@
}
@Test
+ public void testTransientLaunchWithKeyguard() {
+ final ActivityStarter starter = prepareStarter(0 /* flags */);
+ final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final KeyguardController keyguard = mSupervisor.getKeyguardController();
+ doReturn(true).when(keyguard).isKeyguardLocked(anyInt());
+ doReturn(true).when(keyguard).isDisplayOccluded(anyInt());
+ registerTestTransitionPlayer();
+ starter.setReason("testTransientLaunchWithKeyguard")
+ .setActivityOptions(ActivityOptions.makeBasic().setTransientLaunch().toBundle())
+ .setIntent(target.intent)
+ .execute();
+ final TransitionController controller = mRootWindowContainer.mTransitionController;
+ final Transition transition = controller.getCollectingTransition();
+ final Transition.ChangeInfo targetChangeInfo = transition.mChanges.get(target);
+
+ assertThat(targetChangeInfo).isNotNull();
+ assertThat(targetChangeInfo.hasChanged()).isTrue();
+ assertThat(controller.isCollecting(top.getTask())).isTrue();
+ assertThat(transition.isTransientLaunch(target)).isTrue();
+ assertThat(transition.isInTransientHide(top.getTask())).isTrue();
+ }
+
+ @Test
public void testActivityStart_expectAddedToRecentTask() {
RecentTasks recentTasks = mock(RecentTasks.class);
mAtm.mTaskSupervisor.setRecentTasks(recentTasks);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
index 51a7e74..06033c7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -20,7 +20,6 @@
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
-import static com.android.server.wm.LetterboxConfigurationPersister.LETTERBOX_CONFIGURATION_FILENAME;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,13 +41,26 @@
import java.io.File;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
+import java.util.function.Supplier;
+/**
+ * Tests for the {@link LetterboxConfigurationPersister} class.
+ *
+ * Build/Install/Run:
+ * atest WmTests:LetterboxConfigurationPersisterTest
+ */
@SmallTest
@Presubmit
public class LetterboxConfigurationPersisterTest {
private static final long TIMEOUT = 2000L; // 2 secs
+ private static final int DEFAULT_REACHABILITY_TEST = -1;
+ private static final Supplier<Integer> DEFAULT_REACHABILITY_SUPPLIER_TEST =
+ () -> DEFAULT_REACHABILITY_TEST;
+
+ private static final String LETTERBOX_CONFIGURATION_TEST_FILENAME = "letterbox_config_test";
+
private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
private Context mContext;
private PersisterQueue mPersisterQueue;
@@ -62,7 +74,7 @@
mConfigFolder = mContext.getFilesDir();
mPersisterQueue = new PersisterQueue();
mQueueState = new QueueState();
- mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(mContext,
+ mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(
() -> mContext.getResources().getInteger(
R.integer.config_letterboxDefaultPositionForHorizontalReachability),
() -> mContext.getResources().getInteger(
@@ -72,7 +84,8 @@
() -> mContext.getResources().getInteger(
R.integer.config_letterboxDefaultPositionForTabletopModeReachability
),
- mConfigFolder, mPersisterQueue, mQueueState);
+ mConfigFolder, mPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
mQueueListener = queueEmpty -> mQueueState.onItemAdded();
mPersisterQueue.addListener(mQueueListener);
mLetterboxConfigurationPersister.start();
@@ -127,8 +140,10 @@
public void test_whenUpdatedWithNewValues_valuesAreReadAfterRestart() {
final PersisterQueue firstPersisterQueue = new PersisterQueue();
final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
- mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(),
- firstPersisterQueue, mQueueState);
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ mContext.getFilesDir(), firstPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
firstPersister.start();
firstPersister.setLetterboxPositionForHorizontalReachability(false,
LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
@@ -138,8 +153,10 @@
stopPersisterSafe(firstPersisterQueue);
final PersisterQueue secondPersisterQueue = new PersisterQueue();
final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
- mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(),
- secondPersisterQueue, mQueueState);
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ mContext.getFilesDir(), secondPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
secondPersister.start();
final int newPositionForHorizontalReachability =
secondPersister.getLetterboxPositionForHorizontalReachability(false);
@@ -156,37 +173,46 @@
@Test
public void test_whenUpdatedWithNewValuesAndDeleted_valuesAreDefaults() {
- mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(false,
+ final PersisterQueue firstPersisterQueue = new PersisterQueue();
+ final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ mContext.getFilesDir(), firstPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
+ firstPersister.start();
+ firstPersister.setLetterboxPositionForHorizontalReachability(false,
LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
- mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(false,
+ firstPersister.setLetterboxPositionForVerticalReachability(false,
LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
waitForCompletion(mPersisterQueue);
final int newPositionForHorizontalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
- false);
+ firstPersister.getLetterboxPositionForHorizontalReachability(false);
final int newPositionForVerticalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
+ firstPersister.getLetterboxPositionForVerticalReachability(false);
Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
newPositionForHorizontalReachability);
Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
newPositionForVerticalReachability);
- deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
- waitForCompletion(mPersisterQueue);
+ deleteConfiguration(firstPersister, firstPersisterQueue);
+ waitForCompletion(firstPersisterQueue);
+ stopPersisterSafe(firstPersisterQueue);
+
+ final PersisterQueue secondPersisterQueue = new PersisterQueue();
+ final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ DEFAULT_REACHABILITY_SUPPLIER_TEST, DEFAULT_REACHABILITY_SUPPLIER_TEST,
+ mContext.getFilesDir(), secondPersisterQueue, mQueueState,
+ LETTERBOX_CONFIGURATION_TEST_FILENAME);
+ secondPersister.start();
final int positionForHorizontalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
- false);
- final int defaultPositionForHorizontalReachability =
- mContext.getResources().getInteger(
- R.integer.config_letterboxDefaultPositionForHorizontalReachability);
- Assert.assertEquals(defaultPositionForHorizontalReachability,
- positionForHorizontalReachability);
+ secondPersister.getLetterboxPositionForHorizontalReachability(false);
final int positionForVerticalReachability =
- mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
- final int defaultPositionForVerticalReachability =
- mContext.getResources().getInteger(
- R.integer.config_letterboxDefaultPositionForVerticalReachability);
- Assert.assertEquals(defaultPositionForVerticalReachability,
- positionForVerticalReachability);
+ secondPersister.getLetterboxPositionForVerticalReachability(false);
+ Assert.assertEquals(DEFAULT_REACHABILITY_TEST, positionForHorizontalReachability);
+ Assert.assertEquals(DEFAULT_REACHABILITY_TEST, positionForVerticalReachability);
+ deleteConfiguration(secondPersister, secondPersisterQueue);
+ waitForCompletion(secondPersisterQueue);
+ stopPersisterSafe(secondPersisterQueue);
}
private void stopPersisterSafe(PersisterQueue persisterQueue) {
@@ -222,7 +248,7 @@
private void deleteConfiguration(LetterboxConfigurationPersister persister,
PersisterQueue persisterQueue) {
final AtomicFile fileToDelete = new AtomicFile(
- new File(mConfigFolder, LETTERBOX_CONFIGURATION_FILENAME));
+ new File(mConfigFolder, LETTERBOX_CONFIGURATION_TEST_FILENAME));
persisterQueue.addItem(
new DeleteFileCommand(fileToDelete, mQueueState.andThen(
s -> persister.useDefaultValue())), true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index f23e56d..7cb5802 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1303,6 +1303,26 @@
assertTrue(info.supportsMultiWindow);
}
+ @Test
+ public void testRemoveCompatibleRecentTask() {
+ final Task task1 = createTaskBuilder(".Task").setWindowingMode(
+ WINDOWING_MODE_FULLSCREEN).build();
+ mRecentTasks.add(task1);
+ final Task task2 = createTaskBuilder(".Task").setWindowingMode(
+ WINDOWING_MODE_MULTI_WINDOW).build();
+ mRecentTasks.add(task2);
+ assertEquals(2, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
+ true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
+
+ // Set windowing mode and ensure the same fullscreen task that created earlier is removed.
+ task2.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ mRecentTasks.removeCompatibleRecentTask(task2);
+ assertEquals(1, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
+ true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());
+ assertEquals(task2.mTaskId, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
+ true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().get(0).taskId);
+ }
+
private TaskSnapshot createSnapshot(Point taskSize, Point bufferSize) {
HardwareBuffer buffer = null;
if (bufferSize != null) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
index 24e932f..6c48a69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java
@@ -16,17 +16,36 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+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.spyOn;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.os.Looper;
import android.platform.test.annotations.Presubmit;
+import android.view.RemoteAnimationAdapter;
+import android.window.RemoteTransition;
import android.window.WindowContainerToken;
import androidx.test.filters.MediumTest;
import org.junit.Test;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
/**
* Build/Install/Run:
@@ -73,4 +92,119 @@
assertSame(clone.getOriginalOptions().getLaunchRootTask(), token);
}
+
+ @Test
+ public void test_getOptions() {
+ // Mock everything necessary
+ MockitoSession mockingSession = mockitoSession()
+ .mockStatic(ActivityTaskManagerService.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ doReturn(PERMISSION_DENIED).when(() -> ActivityTaskManagerService.checkPermission(
+ any(), anyInt(), anyInt()));
+
+ final LockTaskController lockTaskController = mock(LockTaskController.class);
+ doReturn(false).when(lockTaskController).isPackageAllowlisted(anyInt(), any());
+
+ final ActivityTaskManagerService atm = mock(ActivityTaskManagerService.class);
+ doReturn(lockTaskController).when(atm).getLockTaskController();
+
+ final ActivityTaskSupervisor taskSupervisor =
+ new ActivityTaskSupervisor(atm, mock(Looper.class));
+ spyOn(taskSupervisor);
+ doReturn(false).when(taskSupervisor).isCallerAllowedToLaunchOnDisplay(anyInt(),
+ anyInt(), anyInt(), any());
+ doReturn(false).when(taskSupervisor).isCallerAllowedToLaunchOnTaskDisplayArea(anyInt(),
+ anyInt(), any(), any());
+
+ taskSupervisor.mRecentTasks = mock(RecentTasks.class);
+ doReturn(false).when(taskSupervisor.mRecentTasks).isCallerRecents(anyInt());
+
+ // Ensure exceptions are thrown when lack of permissions.
+ ActivityOptions activityOptions = ActivityOptions.makeBasic();
+ try {
+ activityOptions.setLaunchTaskId(100);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setDisableStartingWindow(true);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setTransientLaunch();
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setDismissKeyguard();
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setLaunchActivityType(ACTIVITY_TYPE_STANDARD);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setLaunchedFromBubble(true);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setLockTaskEnabled(true);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeCustomTaskAnimation(
+ getInstrumentation().getContext(), 0, 0, null, null, null);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ RemoteAnimationAdapter remoteAnimationAdapter = mock(RemoteAnimationAdapter.class);
+ RemoteTransition remoteTransition = mock(RemoteTransition.class);
+ activityOptions = ActivityOptions.makeRemoteAnimation(remoteAnimationAdapter);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeRemoteAnimation(remoteAnimationAdapter,
+ remoteTransition);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setRemoteAnimationAdapter(remoteAnimationAdapter);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeRemoteTransition(remoteTransition);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ activityOptions = ActivityOptions.makeBasic();
+ activityOptions.setRemoteTransition(remoteTransition);
+ verifySecureExceptionThrown(activityOptions, taskSupervisor);
+
+ verifySecureExceptionThrown(activityOptions, taskSupervisor,
+ mock(TaskDisplayArea.class));
+ } finally {
+ mockingSession.finishMocking();
+ }
+ }
+
+ private void verifySecureExceptionThrown(ActivityOptions activityOptions,
+ ActivityTaskSupervisor taskSupervisor) {
+ verifySecureExceptionThrown(activityOptions, taskSupervisor, null /* mockTda */);
+ }
+
+ private void verifySecureExceptionThrown(ActivityOptions activityOptions,
+ ActivityTaskSupervisor taskSupervisor, TaskDisplayArea mockTda) {
+ SafeActivityOptions safeActivityOptions = new SafeActivityOptions(activityOptions);
+ if (mockTda != null) {
+ spyOn(safeActivityOptions);
+ doReturn(mockTda).when(safeActivityOptions).getLaunchTaskDisplayArea(any(), any());
+ }
+
+ boolean isExceptionThrow = false;
+ final ActivityInfo aInfo = mock(ActivityInfo.class);
+ try {
+ safeActivityOptions.getOptions(null, aInfo, null, taskSupervisor);
+ } catch (SecurityException ex) {
+ isExceptionThrow = true;
+ }
+ assertTrue(isExceptionThrow);
+ }
}
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 3908947..d5afe3b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -27,6 +27,11 @@
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_UNSPECIFIED;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
@@ -91,9 +96,12 @@
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
@@ -2255,6 +2263,169 @@
}
@Test
+ public void testUserOverrideSplitScreenAspectRatioForLandscapeDisplay() {
+ final int displayWidth = 1600;
+ final int displayHeight = 1400;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+
+ float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
+
+ testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN);
+ }
+
+ @Test
+ public void testUserOverrideSplitScreenAspectRatioForPortraitDisplay() {
+ final int displayWidth = 1400;
+ final int displayHeight = 1600;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+
+ float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
+
+ testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN);
+ }
+
+ @Test
+ public void testUserOverrideDisplaySizeAspectRatioForLandscapeDisplay() {
+ final int displayWidth = 1600;
+ final int displayHeight = 1400;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+
+ float expectedAspectRatio = 1f * displayWidth / displayHeight;
+
+ testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE);
+ }
+
+ @Test
+ public void testUserOverrideDisplaySizeAspectRatioForPortraitDisplay() {
+ final int displayWidth = 1400;
+ final int displayHeight = 1600;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+
+ float expectedAspectRatio = 1f * displayHeight / displayWidth;
+
+ testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE);
+ }
+
+ @Test
+ public void testUserOverride32AspectRatioForPortraitDisplay() {
+ setUpDisplaySizeWithApp(/* dw */ 1400, /* dh */ 1600);
+ testUserOverrideAspectRatio(3 / 2f, USER_MIN_ASPECT_RATIO_3_2);
+ }
+
+ @Test
+ public void testUserOverride32AspectRatioForLandscapeDisplay() {
+ setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
+ testUserOverrideAspectRatio(3 / 2f, USER_MIN_ASPECT_RATIO_3_2);
+ }
+
+ @Test
+ public void testUserOverride43AspectRatioForPortraitDisplay() {
+ setUpDisplaySizeWithApp(/* dw */ 1400, /* dh */ 1600);
+ testUserOverrideAspectRatio(4 / 3f, USER_MIN_ASPECT_RATIO_4_3);
+ }
+
+ @Test
+ public void testUserOverride43AspectRatioForLandscapeDisplay() {
+ setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
+ testUserOverrideAspectRatio(4 / 3f, USER_MIN_ASPECT_RATIO_4_3);
+ }
+
+ @Test
+ public void testUserOverride169AspectRatioForPortraitDisplay() {
+ setUpDisplaySizeWithApp(/* dw */ 1800, /* dh */ 1500);
+ testUserOverrideAspectRatio(16 / 9f, USER_MIN_ASPECT_RATIO_16_9);
+ }
+
+ @Test
+ public void testUserOverride169AspectRatioForLandscapeDisplay() {
+ setUpDisplaySizeWithApp(/* dw */ 1500, /* dh */ 1800);
+ testUserOverrideAspectRatio(16 / 9f, USER_MIN_ASPECT_RATIO_16_9);
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+ public void testUserOverrideAspectRatioOverSystemOverride() {
+ setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
+
+ testUserOverrideAspectRatio(false,
+ SCREEN_ORIENTATION_PORTRAIT,
+ 3 / 2f,
+ USER_MIN_ASPECT_RATIO_3_2,
+ true);
+ }
+
+ @Test
+ public void testUserOverrideAspectRatioNotEnabled() {
+ setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
+
+ // App aspect ratio doesn't change
+ testUserOverrideAspectRatio(false,
+ SCREEN_ORIENTATION_PORTRAIT,
+ 1f * 1600 / 1400,
+ USER_MIN_ASPECT_RATIO_3_2,
+ false);
+ }
+
+ private void testUserOverrideAspectRatio(float expectedAspectRatio,
+ @PackageManager.UserMinAspectRatio int aspectRatio) {
+ testUserOverrideAspectRatio(true,
+ SCREEN_ORIENTATION_PORTRAIT,
+ expectedAspectRatio,
+ aspectRatio,
+ true);
+
+ testUserOverrideAspectRatio(false,
+ SCREEN_ORIENTATION_PORTRAIT,
+ expectedAspectRatio,
+ aspectRatio,
+ true);
+
+ testUserOverrideAspectRatio(true,
+ SCREEN_ORIENTATION_LANDSCAPE,
+ expectedAspectRatio,
+ aspectRatio,
+ true);
+
+ testUserOverrideAspectRatio(false,
+ SCREEN_ORIENTATION_LANDSCAPE,
+ expectedAspectRatio,
+ aspectRatio,
+ true);
+ }
+
+ private void testUserOverrideAspectRatio(boolean isUnresizable, int screenOrientation,
+ float expectedAspectRatio, @PackageManager.UserMinAspectRatio int aspectRatio,
+ boolean enabled) {
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+ activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ activity.mWmService.mLetterboxConfiguration
+ .setUserAppAspectRatioSettingsOverrideEnabled(enabled);
+ // Set user aspect ratio override
+ final IPackageManager pm = mAtm.getPackageManager();
+ try {
+ doReturn(aspectRatio).when(pm)
+ .getUserMinAspectRatio(activity.packageName, activity.mUserId);
+ } catch (RemoteException ignored) {
+ }
+
+ prepareLimitedBounds(activity, screenOrientation, isUnresizable);
+
+ final Rect afterBounds = activity.getBounds();
+ final int width = afterBounds.width();
+ final int height = afterBounds.height();
+ final float afterAspectRatio =
+ (float) Math.max(width, height) / (float) Math.min(width, height);
+
+ assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ }
+
+ @Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
public void testOverrideSplitScreenAspectRatioForUnresizablePortraitApps() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 7351e81..ffecafb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -61,6 +61,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -1429,6 +1430,15 @@
// No need to wait for the activity in transient hide task.
assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState);
+ // An active transient launch overrides idle state to avoid clearing power mode before the
+ // transition is finished.
+ spyOn(mRootWindowContainer.mTransitionController);
+ doAnswer(invocation -> controller.isTransientLaunch(invocation.getArgument(0))).when(
+ mRootWindowContainer.mTransitionController).isTransientLaunch(any());
+ activity2.getTask().setResumedActivity(activity2, "test");
+ activity2.idle = true;
+ assertFalse(mRootWindowContainer.allResumedActivitiesIdle());
+
activity1.setVisibleRequested(false);
activity2.setVisibleRequested(true);
activity2.setVisible(true);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index b528fa6..ed1c41f 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9659,6 +9659,7 @@
*
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
+ * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
*/
public static final String
KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 64e4356..c0f4008 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -35,6 +35,9 @@
import android.annotation.SystemService;
import android.app.PendingIntent;
import android.app.PropertyInvalidatedCache;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -85,6 +88,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -1322,41 +1326,61 @@
* for #onSubscriptionsChanged to be invoked.
*/
public static class OnSubscriptionsChangedListener {
- private class OnSubscriptionsChangedListenerHandler extends Handler {
- OnSubscriptionsChangedListenerHandler() {
- super();
- }
-
- OnSubscriptionsChangedListenerHandler(Looper looper) {
- super(looper);
- }
- }
/**
- * Posted executor callback on the handler associated with a given looper.
- * The looper can be the calling thread's looper or the looper passed from the
- * constructor {@link #OnSubscriptionsChangedListener(Looper)}.
+ * After {@link Build.VERSION_CODES.Q}, it is no longer necessary to instantiate a
+ * Handler inside of the OnSubscriptionsChangedListener in all cases, so it will only
+ * be done for callers that do not supply an Executor.
*/
- private final HandlerExecutor mExecutor;
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long LAZY_INITIALIZE_SUBSCRIPTIONS_CHANGED_HANDLER = 278814050L;
+
+ /**
+ * For backwards compatibility reasons, stashes the Looper associated with the thread
+ * context in which this listener was created.
+ */
+ private final Looper mCreatorLooper;
/**
* @hide
*/
- public HandlerExecutor getHandlerExecutor() {
- return mExecutor;
+ public Looper getCreatorLooper() {
+ return mCreatorLooper;
}
+ /**
+ * Create an OnSubscriptionsChangedListener.
+ *
+ * For callers targeting {@link Build.VERSION_CODES.P} or earlier, this can only be called
+ * on a thread that already has a prepared Looper. Callers targeting Q or later should
+ * subsequently use {@link SubscriptionManager#addOnSubscriptionsChangedListener(
+ * Executor, OnSubscriptionsChangedListener)}.
+ *
+ * On OS versions prior to {@link Build.VERSION_CODES.V} callers should assume that this
+ * call will fail if invoked on a thread that does not already have a prepared looper.
+ */
public OnSubscriptionsChangedListener() {
- mExecutor = new HandlerExecutor(new OnSubscriptionsChangedListenerHandler());
+ mCreatorLooper = Looper.myLooper();
+ if (mCreatorLooper == null
+ && !Compatibility.isChangeEnabled(
+ LAZY_INITIALIZE_SUBSCRIPTIONS_CHANGED_HANDLER)) {
+ // matches the implementation of Handler
+ throw new RuntimeException(
+ "Can't create handler inside thread "
+ + Thread.currentThread()
+ + " that has not called Looper.prepare()");
+ }
}
/**
* Allow a listener to be created with a custom looper
- * @param looper the looper that the underlining handler should run on
+ * @param looper the non-null Looper that the underlining handler should run on
* @hide
*/
- public OnSubscriptionsChangedListener(Looper looper) {
- mExecutor = new HandlerExecutor(new OnSubscriptionsChangedListenerHandler(looper));
+ public OnSubscriptionsChangedListener(@NonNull Looper looper) {
+ Objects.requireNonNull(looper);
+ mCreatorLooper = looper;
}
/**
@@ -1423,7 +1447,9 @@
@Deprecated
public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
if (listener == null) return;
- addOnSubscriptionsChangedListener(listener.mExecutor, listener);
+
+ addOnSubscriptionsChangedListener(
+ new HandlerExecutor(new Handler(listener.getCreatorLooper())), listener);
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a3099db..f1ee76e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5268,7 +5268,11 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.getMergedImsisFromGroup(getSubId(), getOpPackageName());
+ String[] mergedImsisFromGroup =
+ telephony.getMergedImsisFromGroup(getSubId(), getOpPackageName());
+ if (mergedImsisFromGroup != null) {
+ return mergedImsisFromGroup;
+ }
}
} catch (RemoteException ex) {
}
@@ -17602,6 +17606,15 @@
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15;
/**
+ * Purchase premium capability failed because the user disabled the feature.
+ * Subsequent attempts will be throttled for the amount of time specified by
+ * {@link CarrierConfigManager
+ * #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
+ * and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
+ */
+ public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16;
+
+ /**
* Results of the purchase premium capability request.
* @hide
*/
@@ -17620,7 +17633,8 @@
PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
PURCHASE_PREMIUM_CAPABILITY_RESULT_ENTITLEMENT_CHECK_FAILED,
PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA_SUBSCRIPTION,
- PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP,
+ PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED})
public @interface PurchasePremiumCapabilityResult {}
/**
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
index 144a7312..6722cc8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -62,14 +62,6 @@
}
}
- /** Assert the background animation layer is never visible during bounds change transition. */
- @Presubmit
- @Test
- fun backgroundLayerNeverVisible() {
- val backgroundColorLayer = ComponentNameMatcher("", "Animation Background")
- flicker.assertLayers { isInvisible(backgroundColorLayer) }
- }
-
/** Trampoline activity should finish itself before the end of this test. */
@Presubmit
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
new file mode 100644
index 0000000..0417f9d
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.datatypes.Rect
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.common.traces.component.ComponentNameMatcher.Companion.TRANSITION_SNAPSHOT
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launching a secondary Activity into Picture-In-Picture mode.
+ *
+ * Setup: Start from a split A|B.
+ * Transition: B enters PIP, observe the window shrink to the bottom right corner on screen.
+ *
+ * To run this test: `atest FlickerTests:SecondaryActivityEnterPipTest`
+ *
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SecondaryActivityEnterPipTest (flicker: LegacyFlickerTest) :
+ ActivityEmbeddingTestBase(flicker) {
+ override val transition: FlickerBuilder.() -> Unit = {
+ setup {
+ tapl.setExpectedRotationCheckEnabled(false)
+ testApp.launchViaIntent(wmHelper)
+ testApp.launchSecondaryActivity(wmHelper)
+ startDisplayBounds =
+ wmHelper.currentState.layerState.physicalDisplayBounds
+ ?: error("Can't get display bounds")
+ }
+ transitions {
+ testApp.secondaryActivityEnterPip(wmHelper)
+ }
+ teardown {
+ tapl.goHome()
+ testApp.exit(wmHelper)
+ }
+ }
+
+ /**
+ * Main and secondary activity start from a split each taking half of the screen.
+ */
+ @Presubmit
+ @Test
+ fun layersStartFromEqualSplit() {
+ flicker.assertLayersStart {
+ val leftLayerRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ val rightLayerRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ // Compare dimensions of two splits, given we're using default split attributes,
+ // both activities take up the same visible size on the display.
+ check { "height" }
+ .that(leftLayerRegion.region.height).isEqual(rightLayerRegion.region.height)
+ check { "width" }
+ .that(leftLayerRegion.region.width).isEqual(rightLayerRegion.region.width)
+ leftLayerRegion.notOverlaps(rightLayerRegion.region)
+ leftLayerRegion.plus(rightLayerRegion.region).coversExactly(startDisplayBounds)
+ }
+ flicker.assertLayersEnd {
+ visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Main Activity is visible throughout the transition and becomes fullscreen.
+ */
+ @Presubmit
+ @Test
+ fun mainActivityWindowBecomesFullScreen() {
+ flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) }
+ flicker.assertWmEnd {
+ visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Main Activity is visible throughout the transition and becomes fullscreen.
+ */
+ @Presubmit
+ @Test
+ fun mainActivityLayerBecomesFullScreen() {
+ flicker.assertLayers {
+ isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .then()
+ .isVisible(TRANSITION_SNAPSHOT)
+ .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .then()
+ .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ flicker.assertLayersEnd {
+ visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .coversExactly(startDisplayBounds)
+ }
+ }
+
+ /**
+ * Secondary Activity is visible throughout the transition and shrinks to the bottom right
+ * corner.
+ */
+ @Presubmit
+ @Test
+ fun secondaryWindowShrinks() {
+ flicker.assertWm {
+ isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ flicker.assertWmEnd {
+ val pipWindowRegion =
+ visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ check{"height"}
+ .that(pipWindowRegion.region.height)
+ .isLower(startDisplayBounds.height / 2)
+ check{"width"}
+ .that(pipWindowRegion.region.width).isLower(startDisplayBounds.width)
+ }
+ }
+
+ /**
+ * During the transition Secondary Activity shrinks to the bottom right corner.
+ */
+ @Presubmit
+ @Test
+ fun secondaryLayerShrinks() {
+ flicker.assertLayers {
+ val pipLayerList = layers {
+ ComponentNameMatcher.PIP_CONTENT_OVERLAY.layerMatchesAnyOf(it) && it.isVisible
+ }
+ pipLayerList.zipWithNext { previous, current ->
+ // TODO(b/290987990): Add checks for visibleRegion.
+ current.screenBounds.isToTheRightBottom(previous.screenBounds.region, 3)
+ current.screenBounds.notBiggerThan(previous.screenBounds.region)
+ }
+ }
+ flicker.assertLayersEnd {
+ val pipRegion = visibleRegion(
+ ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ check { "height" }
+ .that(pipRegion.region.height)
+ .isLower(startDisplayBounds.height / 2)
+ check { "width" }
+ .that(pipRegion.region.width).isLower(startDisplayBounds.width)
+ }
+ }
+
+ companion object {
+ /** {@inheritDoc} */
+ private var startDisplayBounds = Rect.EMPTY
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+ }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index a72ec1e..2d3bc2d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -113,6 +113,21 @@
.waitForAndVerify()
}
+ fun secondaryActivityEnterPip(wmHelper: WindowManagerStateHelper) {
+ val pipButton =
+ uiDevice.wait(
+ Until.findObject(By.res(getPackage(), "secondary_enter_pip_button")),
+ FIND_TIMEOUT
+ )
+ require(pipButton != null) { "Can't find enter pip button on screen." }
+ pipButton.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withPipShown()
+ .waitForAndVerify()
+ }
+
/**
* Clicks the button to launch a secondary activity with alwaysExpand enabled, which will launch
* a fullscreen window on top of the visible region.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index c1db423..ea7c7c4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -25,6 +25,7 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import com.android.server.wm.flicker.helpers.setRotation
@@ -78,6 +79,7 @@
flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
}
+ @FlakyTest(bugId = 290767483)
@Postsubmit
@Test
fun imeLayerAlphaOneAfterSnapshotStartingWindowRemoval() {
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 68ae806..ff9799a 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -102,6 +102,24 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".LaunchTransparentActivity"
+ android:resizeableActivity="false"
+ android:screenOrientation="portrait"
+ android:theme="@android:style/Theme"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity"
+ android:label="LaunchTransparentActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity android:name=".TransparentActivity"
+ android:theme="@style/TransparentTheme"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.TransparentActivity"
+ android:label="TransparentActivity"
+ android:exported="false">
+ </activity>
<activity android:name=".LaunchNewActivity"
android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity"
android:theme="@style/CutoutShortEdges"
@@ -206,6 +224,7 @@
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
android:theme="@style/CutoutShortEdges"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:supportsPictureInPicture="true"
android:exported="false"/>
<activity
android:name=".ActivityEmbeddingThirdActivity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
index 6731446..135140a 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml
@@ -35,4 +35,10 @@
android:onClick="launchThirdActivity"
android:text="Launch a third activity" />
+ <Button
+ android:id="@+id/secondary_enter_pip_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="Enter pip" />
+
</LinearLayout>
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml
new file mode 100644
index 0000000..0730ded
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+</FrameLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml
new file mode 100644
index 0000000..ff4ead9
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_transparent_launch.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:color/black">
+
+ <Button
+ android:id="@+id/button_launch_transparent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:text="Launch Transparent" />
+ <Button
+ android:id="@+id/button_request_permission"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:text="Request Permission" />
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 1d21fd5..e51ed29 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -43,6 +43,13 @@
<item name="android:windowSoftInputMode">stateUnchanged</item>
</style>
+ <style name="TransparentTheme" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ </style>
+
<style name="no_starting_window" parent="@android:style/Theme.DeviceDefault">
<item name="android:windowDisablePreview">true</item>
</style>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
index dc21027..ee087ef 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
@@ -18,6 +18,7 @@
import android.app.Activity;
import android.content.Intent;
+import android.app.PictureInPictureParams;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
@@ -40,6 +41,16 @@
finish();
}
});
+ findViewById(R.id.secondary_enter_pip_button).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ PictureInPictureParams.Builder picInPicParamsBuilder =
+ new PictureInPictureParams.Builder();
+ enterPictureInPictureMode(picInPicParamsBuilder.build());
+ }
+ }
+ );
}
public void launchThirdActivity(View view) {
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 95c86ac..2795a6c 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -73,6 +73,18 @@
FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity");
}
+ public static class TransparentActivity {
+ public static final String LABEL = "TransparentActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".TransparentActivity");
+ }
+
+ public static class LaunchTransparentActivity {
+ public static final String LABEL = "LaunchTransparentActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".LaunchTransparentActivity");
+ }
+
public static class DialogThemedActivity {
public static final String LABEL = "DialogThemedActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java
new file mode 100644
index 0000000..7c161fd
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchTransparentActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class LaunchTransparentActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.activity_transparent_launch);
+ findViewById(R.id.button_launch_transparent)
+ .setOnClickListener(v -> launchTransparentActivity());
+ }
+
+ private void launchTransparentActivity() {
+ startActivity(new Intent(this, TransparentActivity.class));
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java
similarity index 66%
copy from services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
copy to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java
index 6727fbc..1bac8bd 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransparentActivity.java
@@ -14,12 +14,16 @@
* limitations under the License.
*/
-package com.android.server.biometrics;
+package com.android.server.wm.flicker.testapp;
-/**
- * Interface for biometric operations to get camera privacy state.
- */
-public interface BiometricSensorPrivacy {
- /* Returns true if privacy is enabled and camera access is disabled. */
- boolean isCameraPrivacyEnabled();
+import android.app.Activity;
+import android.os.Bundle;
+
+public class TransparentActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.activity_transparent);
+ }
}
diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
index e2dc131..56b0b9a 100644
--- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -48,11 +48,11 @@
private fun createImeSubtype(
imeSubtypeId: Int,
- languageTag: String,
+ languageTag: ULocale?,
layoutType: String
): InputMethodSubtype =
InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(imeSubtypeId)
- .setPhysicalKeyboardHint(ULocale.forLanguageTag(languageTag), layoutType).build()
+ .setPhysicalKeyboardHint(languageTag, layoutType).build()
/**
* Tests for {@link KeyboardMetricsCollector}.
@@ -95,7 +95,8 @@
null,
null
)
- ).addLayoutSelection(createImeSubtype(1, "en-US", "qwerty"), null, 123).build()
+ ).addLayoutSelection(createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
+ null, 123).build()
}
}
@@ -111,15 +112,19 @@
)
)
val event = builder.addLayoutSelection(
- createImeSubtype(1, "en-US", "qwerty"),
+ createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
KeyboardLayout(null, "English(US)(Qwerty)", null, 0, null, 0, 0, 0),
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
).addLayoutSelection(
- createImeSubtype(2, "en-US", "azerty"),
- null,
+ createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"),
+ null, // Default layout type
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
).addLayoutSelection(
- createImeSubtype(3, "en-US", "qwerty"),
+ createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"),
+ KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+ ).addLayoutSelection(
+ createImeSubtype(4, null, "qwerty"), // Default language tag
KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
).setIsFirstTimeConfiguration(true).build()
@@ -137,43 +142,62 @@
assertTrue(event.isFirstConfiguration)
assertEquals(
- "KeyboardConfigurationEvent should contain 3 configurations provided",
- 3,
+ "KeyboardConfigurationEvent should contain 4 configurations provided",
+ 4,
event.layoutConfigurations.size
)
assertExpectedLayoutConfiguration(
event.layoutConfigurations[0],
+ "de-CH",
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+ "English(US)(Qwerty)",
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
"en-US",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
- "English(US)(Qwerty)",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
)
assertExpectedLayoutConfiguration(
event.layoutConfigurations[1],
+ "de-CH",
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+ KeyboardMetricsCollector.DEFAULT_LAYOUT,
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER,
"en-US",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
- KeyboardMetricsCollector.DEFAULT_LAYOUT,
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
)
assertExpectedLayoutConfiguration(
event.layoutConfigurations[2],
"de-CH",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
"German",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ "en-US",
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
+ )
+ assertExpectedLayoutConfiguration(
+ event.layoutConfigurations[3],
+ "de-CH",
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+ "German",
+ KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
)
}
private fun assertExpectedLayoutConfiguration(
configuration: KeyboardMetricsCollector.LayoutConfiguration,
- expectedLanguageTag: String,
- expectedLayoutType: Int,
+ expectedKeyboardLanguageTag: String,
+ expectedKeyboardLayoutType: Int,
expectedSelectedLayout: String,
- expectedLayoutSelectionCriteria: Int
+ expectedLayoutSelectionCriteria: Int,
+ expectedImeLanguageTag: String,
+ expectedImeLayoutType: Int
) {
- assertEquals(expectedLanguageTag, configuration.keyboardLanguageTag)
- assertEquals(expectedLayoutType, configuration.keyboardLayoutType)
+ assertEquals(expectedKeyboardLanguageTag, configuration.keyboardLanguageTag)
+ assertEquals(expectedKeyboardLayoutType, configuration.keyboardLayoutType)
assertEquals(expectedSelectedLayout, configuration.keyboardLayoutName)
assertEquals(expectedLayoutSelectionCriteria, configuration.layoutSelectionCriteria)
+ assertEquals(expectedImeLanguageTag, configuration.imeLanguageTag)
+ assertEquals(expectedImeLayoutType, configuration.imeLayoutType)
}
-}
\ No newline at end of file
+}
diff --git a/tests/Internal/src/android/service/wallpaper/OWNERS b/tests/Internal/src/android/service/wallpaper/OWNERS
new file mode 100644
index 0000000..5a26d0e
--- /dev/null
+++ b/tests/Internal/src/android/service/wallpaper/OWNERS
@@ -0,0 +1,4 @@
+dupin@google.com
+santie@google.com
+pomini@google.com
+poultney@google.com
\ No newline at end of file
diff --git a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
index 153ca79..0c5e8d48 100644
--- a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
+++ b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java
@@ -85,4 +85,17 @@
assertEquals("onAmbientModeChanged should have been called", 2, zoomChangedCount[0]);
}
+ @Test
+ public void testNotifyColorsOfDestroyedEngine_doesntCrash() {
+ WallpaperService service = new WallpaperService() {
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+ };
+ WallpaperService.Engine engine = service.onCreateEngine();
+ engine.detach();
+
+ engine.notifyColorsChanged();
+ }
}
diff --git a/tests/permission/OWNERS b/tests/permission/OWNERS
index 999ea0e..2fe78c5 100644
--- a/tests/permission/OWNERS
+++ b/tests/permission/OWNERS
@@ -1 +1,2 @@
+#Bug component: 137825
include /core/java/android/permission/OWNERS
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index edd6dd3..82e40b1 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -32,6 +32,7 @@
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* This is a wrapper around {@link TestLooperManager} to make it easier to manage
@@ -55,7 +56,6 @@
private MessageHandler mMessageHandler;
private Handler mHandler;
- private Runnable mEmptyMessage;
private TestLooperManager mQueueWrapper;
static {
@@ -121,8 +121,12 @@
* @param num Number of messages to parse
*/
public int processMessages(int num) {
+ return processMessagesInternal(num, null);
+ }
+
+ private int processMessagesInternal(int num, Runnable barrierRunnable) {
for (int i = 0; i < num; i++) {
- if (!parseMessageInt()) {
+ if (!processSingleMessage(barrierRunnable)) {
return i + 1;
}
}
@@ -130,6 +134,27 @@
}
/**
+ * Process up to a certain number of messages, not blocking if the queue has less messages than
+ * that
+ * @param num the maximum number of messages to process
+ * @return the number of messages processed. This will be at most {@code num}.
+ */
+
+ public int processMessagesNonBlocking(int num) {
+ final AtomicBoolean reachedBarrier = new AtomicBoolean(false);
+ Runnable barrierRunnable = () -> {
+ reachedBarrier.set(true);
+ };
+ mHandler.post(barrierRunnable);
+ waitForMessage(mQueueWrapper, mHandler, barrierRunnable);
+ try {
+ return processMessagesInternal(num, barrierRunnable) + (reachedBarrier.get() ? -1 : 0);
+ } finally {
+ mHandler.removeCallbacks(barrierRunnable);
+ }
+ }
+
+ /**
* Process messages in the queue until no more are found.
*/
public void processAllMessages() {
@@ -165,19 +190,20 @@
private int processQueuedMessages() {
int count = 0;
- mEmptyMessage = () -> { };
- mHandler.post(mEmptyMessage);
- waitForMessage(mQueueWrapper, mHandler, mEmptyMessage);
- while (parseMessageInt()) count++;
+ Runnable barrierRunnable = () -> { };
+ mHandler.post(barrierRunnable);
+ waitForMessage(mQueueWrapper, mHandler, barrierRunnable);
+ while (processSingleMessage(barrierRunnable)) count++;
return count;
}
- private boolean parseMessageInt() {
+ private boolean processSingleMessage(Runnable barrierRunnable) {
try {
Message result = mQueueWrapper.next();
if (result != null) {
// This is a break message.
- if (result.getCallback() == mEmptyMessage) {
+ if (result.getCallback() == barrierRunnable) {
+ mQueueWrapper.execute(result);
mQueueWrapper.recycle(result);
return false;
}
diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java
index 0f491b8..a02eb6b 100644
--- a/tests/testables/tests/src/android/testing/TestableLooperTest.java
+++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java
@@ -27,12 +27,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -40,6 +34,11 @@
import android.testing.TestableLooper.MessageHandler;
import android.testing.TestableLooper.RunWithLooper;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -240,4 +239,33 @@
inOrder.verify(handler).dispatchMessage(messageC);
}
+ @Test
+ public void testProcessMessagesNonBlocking_onlyArgNumber() {
+ Handler h = new Handler(mTestableLooper.getLooper());
+ Runnable r = mock(Runnable.class);
+
+ h.post(r);
+ h.post(r);
+ h.post(r);
+
+ int processed = mTestableLooper.processMessagesNonBlocking(2);
+
+ verify(r, times(2)).run();
+ assertEquals(2, processed);
+ }
+
+ @Test
+ public void testProcessMessagesNonBlocking_lessMessagesThanArg() {
+ Handler h = new Handler(mTestableLooper.getLooper());
+ Runnable r = mock(Runnable.class);
+
+ h.post(r);
+ h.post(r);
+ h.post(r);
+
+ int processed = mTestableLooper.processMessagesNonBlocking(5);
+
+ verify(r, times(3)).run();
+ assertEquals(3, processed);
+ }
}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
index e03d92ab..f1727b7 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
@@ -30,6 +30,7 @@
const val IINTERFACE_INTERFACE = "android.os.IInterface"
const val AIDL_PERMISSION_HELPER_SUFFIX = "_enforcePermission"
+const val PERMISSION_PREFIX_LITERAL = "android.permission."
/**
* If a non java (e.g. c++) backend is enabled, the @EnforcePermission
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index 0a66cd5..3a95df9 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -95,7 +95,7 @@
val v1 = ConstantEvaluator.evaluate(context, value1)
val v2 = ConstantEvaluator.evaluate(context, value2)
if (v1 != null && v2 != null) {
- if (v1 != v2) {
+ if (v1 != v2 && !isOneShortPermissionOfOther(v1, v2)) {
return false
}
} else {
@@ -107,7 +107,7 @@
for (j in children1.indices) {
val c1 = ConstantEvaluator.evaluate(context, children1[j])
val c2 = ConstantEvaluator.evaluate(context, children2[j])
- if (c1 != c2) {
+ if (c1 != c2 && !isOneShortPermissionOfOther(c1, c2)) {
return false
}
}
@@ -116,6 +116,12 @@
return true
}
+ private fun isOneShortPermissionOfOther(
+ permission1: Any?,
+ permission2: Any?
+ ): Boolean = permission1 == (permission2 as? String)?.removePrefix(PERMISSION_PREFIX_LITERAL) ||
+ permission2 == (permission1 as? String)?.removePrefix(PERMISSION_PREFIX_LITERAL)
+
private fun compareMethods(
context: JavaContext,
element: UElement,
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index 75b0073..b3dacbd 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -316,20 +316,55 @@
overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same annotation must be used on Default.testMethod [MissingEnforcePermissionAnnotation]
public void testMethod() {}
~~~~~~~~~~
- 1 errors, 0 warnings
+ 1 errors, 0 warnings
""".addLineContinuation()
)
}
- fun testDoesDetectIssuesShortStringsNotAllowed() {
+ fun testDoesNotDetectIssuesShortStringsAllowedInChildAndParent() {
lint().files(java(
"""
package test.pkg;
import android.annotation.EnforcePermission;
public class TestClass121 extends IFooMethod.Stub {
@Override
+ @EnforcePermission("READ_PHONE_STATE")
+ public void testMethod() {}
+ @Override
+ @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+ public void testMethodParentShortPermission() {}
+ @Override
@EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
public void testMethodAnyLiteral() {}
+ @Override
+ @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+ public void testMethodAnyLiteralParentsShortPermission() {}
+ }
+ """).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ fun testDoesDetectIssuesWrongShortStringsInChildAndParent() {
+ lint().files(java(
+ """
+ package test.pkg;
+ import android.annotation.EnforcePermission;
+ public class TestClass121 extends IFooMethod.Stub {
+ @Override
+ @EnforcePermission("READ_WRONG_PHONE_STATE")
+ public void testMethod() {}
+ @Override
+ @EnforcePermission(android.Manifest.permission.READ_WRONG_PHONE_STATE)
+ public void testMethodParentShortPermission() {}
+ @Override
+ @EnforcePermission(anyOf={"WRONG_INTERNET", "READ_PHONE_STATE"})
+ public void testMethodAnyLiteral() {}
+ @Override
+ @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_WRONG_PHONE_STATE})
+ public void testMethodAnyLiteralParentsShortPermission() {}
}
""").indented(),
*stubs
@@ -337,14 +372,19 @@
.run()
.expect(
"""
- src/test/pkg/TestClass121.java:6: Error: The method \
- TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}) \
- which differs from the overridden method Stub.testMethodAnyLiteral: \
- @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
- The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
- public void testMethodAnyLiteral() {}
- ~~~~~~~~~~~~~~~~~~~~
- 1 errors, 0 warnings
+ src/test/pkg/TestClass121.java:6: Error: The method TestClass121.testMethod is annotated with @EnforcePermission("READ_WRONG_PHONE_STATE") which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethod() {}
+ ~~~~~~~~~~
+ src/test/pkg/TestClass121.java:9: Error: The method TestClass121.testMethodParentShortPermission is annotated with @EnforcePermission(android.Manifest.permission.READ_WRONG_PHONE_STATE) which differs from the overridden method Stub.testMethodParentShortPermission: @android.annotation.EnforcePermission("READ_PHONE_STATE"). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethodParentShortPermission() {}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass121.java:12: Error: The method TestClass121.testMethodAnyLiteral is annotated with @EnforcePermission(anyOf={"WRONG_INTERNET", "READ_PHONE_STATE"}) which differs from the overridden method Stub.testMethodAnyLiteral: @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethodAnyLiteral() {}
+ ~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass121.java:15: Error: The method TestClass121.testMethodAnyLiteralParentsShortPermission is annotated with @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_WRONG_PHONE_STATE}) which differs from the overridden method Stub.testMethodAnyLiteralParentsShortPermission: @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"}). The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+ public void testMethodAnyLiteralParentsShortPermission() {}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 4 errors, 0 warnings
""".addLineContinuation()
)
}
@@ -360,12 +400,18 @@
@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
public void testMethod() {}
@Override
+ @android.annotation.EnforcePermission("READ_PHONE_STATE")
+ public void testMethodParentShortPermission() {}
+ @Override
@android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
public void testMethodAny() {}
@Override
@android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
public void testMethodAnyLiteral() {}
@Override
+ @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
+ public void testMethodAnyLiteralParentsShortPermission() {}
+ @Override
@android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
public void testMethodAll() {}
@Override
@@ -374,10 +420,14 @@
}
@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
public void testMethod();
+ @android.annotation.EnforcePermission("READ_PHONE_STATE")
+ public void testMethodParentShortPermission();
@android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
public void testMethodAny() {}
@android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
public void testMethodAnyLiteral() {}
+ @android.annotation.EnforcePermission(anyOf={"INTERNET", "READ_PHONE_STATE"})
+ public void testMethodAnyLiteralParentsShortPermission() {}
@android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
public void testMethodAll() {}
@android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
@@ -404,6 +454,7 @@
package android.Manifest;
class permission {
public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+ public static final String READ_WRONG_PHONE_STATE = "android.permission.READ_WRONG_PHONE_STATE";
public static final String NFC = "android.permission.NFC";
public static final String INTERNET = "android.permission.INTERNET";
}