Merge "Fix theme for package uninstall dialog" into main
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index 0104ee1..ace56d4 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -20,6 +20,7 @@
],
libs: [
+ "androidx.annotation_annotation",
"app-compat-annotations",
"error_prone_annotations",
"framework",
diff --git a/core/api/current.txt b/core/api/current.txt
index e0b919a..323b425 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4482,7 +4482,7 @@
method @CallSuper public void onActionModeStarted(android.view.ActionMode);
method public void onActivityReenter(int, android.content.Intent);
method protected void onActivityResult(int, int, android.content.Intent);
- method @FlaggedApi("android.security.content_uri_permission_apis") public void onActivityResult(int, int, @NonNull android.content.Intent, @NonNull android.app.ComponentCaller);
+ method @FlaggedApi("android.security.content_uri_permission_apis") public void onActivityResult(int, int, @Nullable android.content.Intent, @NonNull android.app.ComponentCaller);
method @Deprecated public void onAttachFragment(android.app.Fragment);
method public void onAttachedToWindow();
method @Deprecated public void onBackPressed();
@@ -57915,7 +57915,7 @@
method public abstract boolean getBuiltInZoomControls();
method public abstract int getCacheMode();
method public abstract String getCursiveFontFamily();
- method public abstract boolean getDatabaseEnabled();
+ method @Deprecated public abstract boolean getDatabaseEnabled();
method @Deprecated public abstract String getDatabasePath();
method public abstract int getDefaultFixedFontSize();
method public abstract int getDefaultFontSize();
@@ -57961,7 +57961,7 @@
method public abstract void setBuiltInZoomControls(boolean);
method public abstract void setCacheMode(int);
method public abstract void setCursiveFontFamily(String);
- method public abstract void setDatabaseEnabled(boolean);
+ method @Deprecated public abstract void setDatabaseEnabled(boolean);
method @Deprecated public abstract void setDatabasePath(String);
method public abstract void setDefaultFixedFontSize(int);
method public abstract void setDefaultFontSize(int);
@@ -60157,7 +60157,7 @@
method public void setRadioGroupChecked(@IdRes int, @IdRes int);
method public void setRelativeScrollPosition(@IdRes int, int);
method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent);
- method public void setRemoteAdapter(@IdRes int, android.content.Intent);
+ method @Deprecated public void setRemoteAdapter(@IdRes int, android.content.Intent);
method public void setRemoteAdapter(@IdRes int, @NonNull android.widget.RemoteViews.RemoteCollectionItems);
method public void setScrollPosition(@IdRes int, int);
method public void setShort(@IdRes int, String, short);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fb2a4ac..4b04d10 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4912,7 +4912,6 @@
method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat();
method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.util.Size getSize();
method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.view.Surface getSurface();
- method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int);
method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setDynamicRangeProfile(long);
}
@@ -4923,6 +4922,7 @@
@FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionConfiguration {
ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionConfiguration(int, int, @NonNull java.util.List<android.hardware.camera2.extension.ExtensionOutputConfiguration>, @Nullable android.hardware.camera2.CaptureRequest);
+ method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int);
}
@FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionOutputConfiguration {
@@ -6261,7 +6261,7 @@
method public void addOnCompleteListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.OnCompleteListener);
method public void addOnCompleteListener(@NonNull android.hardware.radio.ProgramList.OnCompleteListener);
method public void close();
- method @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
+ method @Deprecated @Nullable public android.hardware.radio.RadioManager.ProgramInfo get(@NonNull android.hardware.radio.ProgramSelector.Identifier);
method @FlaggedApi("android.hardware.radio.hd_radio_improved") @NonNull public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramInfos(@NonNull android.hardware.radio.ProgramSelector.Identifier);
method public void registerListCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.radio.ProgramList.ListCallback);
method public void registerListCallback(@NonNull android.hardware.radio.ProgramList.ListCallback);
@@ -6313,7 +6313,7 @@
field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa
- field public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
+ field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3
field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int IDENTIFIER_TYPE_HD_STATION_LOCATION = 15; // 0xf
@@ -6321,8 +6321,8 @@
field @Deprecated public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4
field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0
field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
- field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
- field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
+ field @Deprecated public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
+ field @Deprecated public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
field public static final int IDENTIFIER_TYPE_VENDOR_END = 1999; // 0x7cf
field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = 1999; // 0x7cf
field @Deprecated public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = 1000; // 0x3e8
@@ -6375,7 +6375,7 @@
field public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; // 0x8
field public static final int CONFIG_DAB_FM_LINKING = 7; // 0x7
field public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; // 0x9
- field public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
+ field @Deprecated public static final int CONFIG_FORCE_ANALOG = 2; // 0x2
field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_AM = 11; // 0xb
field @FlaggedApi("android.hardware.radio.hd_radio_improved") public static final int CONFIG_FORCE_ANALOG_FM = 10; // 0xa
field public static final int CONFIG_FORCE_DIGITAL = 3; // 0x3
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 892567c6..bc45a76 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -42,6 +42,7 @@
field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
field public static final String READ_WRITE_SYNC_DISABLED_MODE_CONFIG = "android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG";
field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
+ field @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") public static final String RECORD_SENSITIVE_CONTENT = "android.permission.RECORD_SENSITIVE_CONTENT";
field public static final String REMAP_MODIFIER_KEYS = "android.permission.REMAP_MODIFIER_KEYS";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String REQUEST_UNIQUE_ID_ATTESTATION = "android.permission.REQUEST_UNIQUE_ID_ATTESTATION";
diff --git a/core/java/Android.bp b/core/java/Android.bp
index ab1c9a4..4f96206 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -167,6 +167,9 @@
"com/android/internal/logging/UiEventLoggerImpl.java",
":statslog-framework-java-gen",
],
+ libs: [
+ "androidx.annotation_annotation",
+ ],
static_libs: ["modules-utils-uieventlogger-interface"],
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index afbefca..1cc2d25 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7473,7 +7473,7 @@
* intent.
*/
@FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
- public void onActivityResult(int requestCode, int resultCode, @NonNull Intent data,
+ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data,
@NonNull ComponentCaller caller) {
onActivityResult(requestCode, resultCode, data);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index cf3b4659..ae5cacd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4076,6 +4076,13 @@
ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq);
mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
} catch (RemoteException ignored) {}
+ if (Flags.clearDnsCacheOnNetworkRulesUpdate()) {
+ // InetAddress will cache UnknownHostException failures. If the rules got
+ // updated and the app has network access now, we need to clear the negative
+ // cache to ensure valid dns queries can work immediately.
+ // TODO: b/329133769 - Clear only the negative cache once it is available.
+ InetAddress.clearDnsCache();
+ }
}
}
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index af56cb4..ed0c933 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -272,10 +272,17 @@
@UnsupportedAppUsage
private Context mOuterContext;
+
+ private final Object mThemeLock = new Object();
+
@UnsupportedAppUsage
+ @GuardedBy("mThemeLock")
private int mThemeResource = 0;
+
@UnsupportedAppUsage
+ @GuardedBy("mThemeLock")
private Resources.Theme mTheme = null;
+
@UnsupportedAppUsage
private PackageManager mPackageManager;
private Context mReceiverRestrictedContext = null;
@@ -288,7 +295,6 @@
private ContentCaptureOptions mContentCaptureOptions = null;
- private final Object mSync = new Object();
/**
* Indicates this {@link Context} can not handle UI components properly and is not associated
* with a {@link Display} instance.
@@ -340,21 +346,18 @@
*/
private boolean mOwnsToken = false;
- @GuardedBy("mSync")
- private File mDatabasesDir;
- @GuardedBy("mSync")
- @UnsupportedAppUsage
- private File mPreferencesDir;
- @GuardedBy("mSync")
- private File mFilesDir;
- @GuardedBy("mSync")
- private File mCratesDir;
- @GuardedBy("mSync")
- private File mNoBackupFilesDir;
- @GuardedBy("mSync")
- private File mCacheDir;
- @GuardedBy("mSync")
- private File mCodeCacheDir;
+ private final Object mDirsLock = new Object();
+ private volatile File mDatabasesDir;
+ @UnsupportedAppUsage private volatile File mPreferencesDir;
+ private volatile File mFilesDir;
+ private volatile File mCratesDir;
+ private volatile File mNoBackupFilesDir;
+ private volatile File[] mExternalFilesDirs;
+ private volatile File[] mObbDirs;
+ private volatile File mCacheDir;
+ private volatile File mCodeCacheDir;
+ private volatile File[] mExternalCacheDirs;
+ private volatile File[] mExternalMediaDirs;
// The system service cache for the system services that are cached per-ContextImpl.
@UnsupportedAppUsage
@@ -458,7 +461,7 @@
@Override
public void setTheme(int resId) {
- synchronized (mSync) {
+ synchronized (mThemeLock) {
if (mThemeResource != resId) {
mThemeResource = resId;
initializeTheme();
@@ -468,14 +471,14 @@
@Override
public int getThemeResId() {
- synchronized (mSync) {
+ synchronized (mThemeLock) {
return mThemeResource;
}
}
@Override
public Resources.Theme getTheme() {
- synchronized (mSync) {
+ synchronized (mThemeLock) {
if (mTheme != null) {
return mTheme;
}
@@ -488,6 +491,7 @@
}
}
+ @GuardedBy("mThemeLock")
private void initializeTheme() {
if (mTheme == null) {
mTheme = mResources.newTheme();
@@ -597,12 +601,18 @@
if (sp == null) {
checkMode(mode);
if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
- if (isCredentialProtectedStorage()
- && !getSystemService(UserManager.class)
- .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
- throw new IllegalStateException("SharedPreferences in credential encrypted "
- + "storage are not available until after user (id "
- + UserHandle.myUserId() + ") is unlocked");
+ if (isCredentialProtectedStorage()) {
+ final UserManager um = getSystemService(UserManager.class);
+ if (um == null) {
+ throw new IllegalStateException("SharedPreferences cannot be accessed "
+ + "if UserManager is not available. "
+ + "(e.g. from inside an isolated process)");
+ }
+ if (!um.isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
+ throw new IllegalStateException("SharedPreferences in "
+ + "credential encrypted storage are not available until after "
+ + "user (id " + UserHandle.myUserId() + ") is unlocked");
+ }
}
}
sp = new SharedPreferencesImpl(file, mode);
@@ -731,12 +741,18 @@
@UnsupportedAppUsage
private File getPreferencesDir() {
- synchronized (mSync) {
- if (mPreferencesDir == null) {
- mPreferencesDir = new File(getDataDir(), "shared_prefs");
+ File localPreferencesDir = mPreferencesDir;
+ if (localPreferencesDir == null) {
+ synchronized (mDirsLock) {
+ localPreferencesDir = mPreferencesDir;
+ if (localPreferencesDir == null) {
+ localPreferencesDir = new File(getDataDir(), "shared_prefs");
+ ensurePrivateDirExists(localPreferencesDir);
+ mPreferencesDir = localPreferencesDir;
+ }
}
- return ensurePrivateDirExists(mPreferencesDir);
}
+ return localPreferencesDir;
}
@Override
@@ -778,16 +794,16 @@
/**
* Common-path handling of app data dir creation
*/
- private static File ensurePrivateDirExists(File file) {
- return ensurePrivateDirExists(file, 0771, -1, null);
+ private static void ensurePrivateDirExists(File file) {
+ ensurePrivateDirExists(file, 0771, -1, null);
}
- private static File ensurePrivateCacheDirExists(File file, String xattr) {
+ private static void ensurePrivateCacheDirExists(File file, String xattr) {
final int gid = UserHandle.getCacheAppGid(Process.myUid());
- return ensurePrivateDirExists(file, 02771, gid, xattr);
+ ensurePrivateDirExists(file, 02771, gid, xattr);
}
- private static File ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
+ private static void ensurePrivateDirExists(File file, int mode, int gid, String xattr) {
if (!file.exists()) {
final String path = file.getAbsolutePath();
try {
@@ -815,17 +831,22 @@
}
}
}
- return file;
}
@Override
public File getFilesDir() {
- synchronized (mSync) {
- if (mFilesDir == null) {
- mFilesDir = new File(getDataDir(), "files");
+ File localFilesDir = mFilesDir;
+ if (localFilesDir == null) {
+ localFilesDir = mFilesDir;
+ synchronized (mDirsLock) {
+ if (localFilesDir == null) {
+ localFilesDir = new File(getDataDir(), "files");
+ ensurePrivateDirExists(localFilesDir);
+ mFilesDir = localFilesDir;
+ }
}
- return ensurePrivateDirExists(mFilesDir);
}
+ return localFilesDir;
}
@Override
@@ -835,25 +856,37 @@
final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId)
.toAbsolutePath().normalize();
- synchronized (mSync) {
- if (mCratesDir == null) {
- mCratesDir = cratesRootPath.toFile();
+ File localCratesDir = mCratesDir;
+ if (localCratesDir == null) {
+ synchronized (mDirsLock) {
+ localCratesDir = mCratesDir;
+ if (localCratesDir == null) {
+ localCratesDir = cratesRootPath.toFile();
+ ensurePrivateDirExists(localCratesDir);
+ mCratesDir = localCratesDir;
+ }
}
- ensurePrivateDirExists(mCratesDir);
}
- File cratedDir = absoluteNormalizedCratePath.toFile();
- return ensurePrivateDirExists(cratedDir);
+ File crateDir = absoluteNormalizedCratePath.toFile();
+ ensurePrivateDirExists(crateDir);
+ return crateDir;
}
@Override
public File getNoBackupFilesDir() {
- synchronized (mSync) {
- if (mNoBackupFilesDir == null) {
- mNoBackupFilesDir = new File(getDataDir(), "no_backup");
+ File localNoBackupFilesDir = mNoBackupFilesDir;
+ if (localNoBackupFilesDir == null) {
+ synchronized (mDirsLock) {
+ localNoBackupFilesDir = mNoBackupFilesDir;
+ if (localNoBackupFilesDir == null) {
+ localNoBackupFilesDir = new File(getDataDir(), "no_backup");
+ ensurePrivateDirExists(localNoBackupFilesDir);
+ mNoBackupFilesDir = localNoBackupFilesDir;
+ }
}
- return ensurePrivateDirExists(mNoBackupFilesDir);
}
+ return localNoBackupFilesDir;
}
@Override
@@ -865,13 +898,24 @@
@Override
public File[] getExternalFilesDirs(String type) {
- synchronized (mSync) {
- File[] dirs = Environment.buildExternalStorageAppFilesDirs(getPackageName());
- if (type != null) {
- dirs = Environment.buildPaths(dirs, type);
+ File[] localExternalFilesDirs = mExternalFilesDirs;
+ if (localExternalFilesDirs == null) {
+ synchronized (mDirsLock) {
+ localExternalFilesDirs = mExternalFilesDirs;
+ if (localExternalFilesDirs == null) {
+ localExternalFilesDirs =
+ Environment.buildExternalStorageAppFilesDirs(getPackageName());
+ if (type != null) {
+ localExternalFilesDirs =
+ Environment.buildPaths(localExternalFilesDirs, type);
+ }
+ localExternalFilesDirs = ensureExternalDirsExistOrFilter(
+ localExternalFilesDirs, true /* tryCreateInProcess */);
+ mExternalFilesDirs = localExternalFilesDirs;
+ }
}
- return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
}
+ return localExternalFilesDirs;
}
@Override
@@ -883,30 +927,51 @@
@Override
public File[] getObbDirs() {
- synchronized (mSync) {
- File[] dirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
- return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
+ File[] localObbDirs = mObbDirs;
+ if (mObbDirs == null) {
+ synchronized (mDirsLock) {
+ localObbDirs = mObbDirs;
+ if (localObbDirs == null) {
+ localObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName());
+ localObbDirs = ensureExternalDirsExistOrFilter(
+ localObbDirs, true /* tryCreateInProcess */);
+ mObbDirs = localObbDirs;
+ }
+ }
}
+ return localObbDirs;
}
@Override
public File getCacheDir() {
- synchronized (mSync) {
- if (mCacheDir == null) {
- mCacheDir = new File(getDataDir(), "cache");
+ File localCacheDir = mCacheDir;
+ if (localCacheDir == null) {
+ synchronized (mDirsLock) {
+ localCacheDir = mCacheDir;
+ if (localCacheDir == null) {
+ localCacheDir = new File(getDataDir(), "cache");
+ ensurePrivateCacheDirExists(localCacheDir, XATTR_INODE_CACHE);
+ mCacheDir = localCacheDir;
+ }
}
- return ensurePrivateCacheDirExists(mCacheDir, XATTR_INODE_CACHE);
}
+ return localCacheDir;
}
@Override
public File getCodeCacheDir() {
- synchronized (mSync) {
- if (mCodeCacheDir == null) {
- mCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
+ File localCodeCacheDir = mCodeCacheDir;
+ if (localCodeCacheDir == null) {
+ synchronized (mDirsLock) {
+ localCodeCacheDir = mCodeCacheDir;
+ if (localCodeCacheDir == null) {
+ localCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
+ ensurePrivateCacheDirExists(localCodeCacheDir, XATTR_INODE_CODE_CACHE);
+ mCodeCacheDir = localCodeCacheDir;
+ }
}
- return ensurePrivateCacheDirExists(mCodeCacheDir, XATTR_INODE_CODE_CACHE);
}
+ return localCodeCacheDir;
}
/**
@@ -927,21 +992,37 @@
@Override
public File[] getExternalCacheDirs() {
- synchronized (mSync) {
- File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName());
- // We don't try to create cache directories in-process, because they need special
- // setup for accurate quota tracking. This ensures the cache dirs are always
- // created through StorageManagerService.
- return ensureExternalDirsExistOrFilter(dirs, false /* tryCreateInProcess */);
+ File[] localExternalCacheDirs = mExternalCacheDirs;
+ if (localExternalCacheDirs == null) {
+ synchronized (mDirsLock) {
+ localExternalCacheDirs = mExternalCacheDirs;
+ if (localExternalCacheDirs == null) {
+ localExternalCacheDirs =
+ Environment.buildExternalStorageAppCacheDirs(getPackageName());
+ localExternalCacheDirs = ensureExternalDirsExistOrFilter(
+ localExternalCacheDirs, false /* tryCreateInProcess */);
+ mExternalCacheDirs = localExternalCacheDirs;
+ }
+ }
}
+ return localExternalCacheDirs;
}
@Override
public File[] getExternalMediaDirs() {
- synchronized (mSync) {
- File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
- return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */);
+ File[] localExternalMediaDirs = mExternalMediaDirs;
+ if (localExternalMediaDirs == null) {
+ synchronized (mDirsLock) {
+ localExternalMediaDirs = mExternalMediaDirs;
+ if (localExternalMediaDirs == null) {
+ localExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
+ localExternalMediaDirs = ensureExternalDirsExistOrFilter(
+ localExternalMediaDirs, true /* tryCreateInProcess */);
+ mExternalMediaDirs = localExternalMediaDirs;
+ }
+ }
}
+ return localExternalMediaDirs;
}
/**
@@ -1040,16 +1121,22 @@
}
private File getDatabasesDir() {
- synchronized (mSync) {
- if (mDatabasesDir == null) {
- if ("android".equals(getPackageName())) {
- mDatabasesDir = new File("/data/system");
- } else {
- mDatabasesDir = new File(getDataDir(), "databases");
+ File localDatabasesDir = mDatabasesDir;
+ if (localDatabasesDir == null) {
+ synchronized (mDirsLock) {
+ localDatabasesDir = mDatabasesDir;
+ if (localDatabasesDir == null) {
+ if ("android".equals(getPackageName())) {
+ localDatabasesDir = new File("/data/system");
+ } else {
+ localDatabasesDir = new File(getDataDir(), "databases");
+ }
+ ensurePrivateDirExists(localDatabasesDir);
+ mDatabasesDir = localDatabasesDir;
}
}
- return ensurePrivateDirExists(mDatabasesDir);
}
+ return localDatabasesDir;
}
@Override
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index 4ce983f..3e7d665 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -125,7 +125,10 @@
/**
* Get the current grammatical gender of privileged application from the encrypted file.
*
- * @return the value of grammatical gender
+ * @return the value of system grammatical gender only if the calling app has the permission,
+ * otherwise throwing an exception.
+ *
+ * @throws SecurityException if the caller does not have the required permission.
*
* @see Configuration#getGrammaticalGender
*/
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 79cb09d..cd4c0bc 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7531,6 +7531,9 @@
/**
* Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
* attached to.
+ * <p>
+ * Note: Calling build() multiple times returns the same Notification instance,
+ * so reusing a builder to create multiple Notifications is discouraged.
*
* @return the fully constructed Notification.
*/
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 986205a..9ef8b38 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -189,10 +189,13 @@
@FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
+ /**
+ * @hide
+ */
@IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED,
HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER})
@Retention(RetentionPolicy.SOURCE)
- private @interface HeadlessDeviceOwnerMode {}
+ public @interface HeadlessDeviceOwnerMode {}
/** @hide */
public static class PolicyInfo {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 620bbaf..cb4ed058 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -53,6 +53,7 @@
import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
import static android.Manifest.permission.SET_TIME;
import static android.Manifest.permission.SET_TIME_ZONE;
+import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
@@ -93,6 +94,7 @@
import android.app.IServiceConnection;
import android.app.KeyguardManager;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.admin.flags.Flags;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -17526,4 +17528,25 @@
}
return -1;
}
+
+ /**
+ * @return The headless device owner mode for the current set DO, returns
+ * {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED} if no DO is set.
+ *
+ * @hide
+ */
+ @DeviceAdminInfo.HeadlessDeviceOwnerMode
+ public int getHeadlessDeviceOwnerMode() {
+ if (!Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
+ return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+ }
+ if (mService != null) {
+ try {
+ return mService.getHeadlessDeviceOwnerMode(mContext.getPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
+ }
}
\ No newline at end of file
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3a7a891c..03d0b0f 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -625,4 +625,6 @@
void setMaxPolicyStorageLimit(String packageName, int storageLimit);
int getMaxPolicyStorageLimit(String packageName);
+
+ int getHeadlessDeviceOwnerMode(String callerPackageName);
}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 1927019..c29ea6d 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -163,3 +163,13 @@
description: "Enable UX changes for esim management"
bug: "295301164"
}
+
+flag {
+ name: "headless_device_owner_provisioning_fix_enabled"
+ namespace: "enterprise"
+ description: "Fix provisioning for single-user headless DO"
+ bug: "289515470"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/network-policy.aconfig b/core/java/android/app/network-policy.aconfig
new file mode 100644
index 0000000..88f386f
--- /dev/null
+++ b/core/java/android/app/network-policy.aconfig
@@ -0,0 +1,11 @@
+package: "android.app"
+
+flag {
+ namespace: "backstage_power"
+ name: "clear_dns_cache_on_network_rules_update"
+ description: "Clears the DNS cache when the network rules update"
+ bug: "237556596"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 8852705..bd04634 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -333,10 +333,9 @@
}
/**
- * Specifies permissions necessary to launch this activity via
- * {@link android.content.Context#startActivity} when passing content URIs. The default value is
- * {@code none}, meaning no specific permissions are required. Setting this attribute restricts
- * activity invocation based on the invoker's permissions.
+ * Specifies permissions necessary to launch this activity when passing content URIs. The
+ * default value is {@code none}, meaning no specific permissions are required. Setting this
+ * attribute restricts activity invocation based on the invoker's permissions.
* @hide
*/
@RequiredContentUriPermission
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 533fa51..55957bf 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -133,4 +133,7 @@
void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation);
List<UserHandle> getUserProfiles();
+
+ /** Saves view capture data to the wm trace directory. */
+ void saveViewCaptureData();
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 41c1f17..3a5383d 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1328,6 +1328,19 @@
}
/**
+ * Saves view capture data to the default location.
+ * @hide
+ */
+ @RequiresPermission(READ_FRAME_BUFFER)
+ public void saveViewCaptureData() {
+ try {
+ mService.saveViewCaptureData();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Unregister a callback, so that it won't be called when LauncherApps dumps.
* @hide
*/
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index f660770..7fd0b03 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -38,7 +38,7 @@
name: "nine_patch_frro"
namespace: "resource_manager"
description: "Feature flag for creating an frro from a 9-patch"
- bug: "309232726"
+ bug: "296324826"
}
flag {
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 749f218..083d49f 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -180,6 +180,16 @@
EXTENSION_HDR,
EXTENSION_NIGHT};
+ /**
+ * List of synthetic CameraCharacteristics keys that are supported in the extensions.
+ */
+ private static final List<CameraCharacteristics.Key>
+ SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS =
+ Arrays.asList(
+ CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES,
+ CameraCharacteristics.REQUEST_AVAILABLE_COLOR_SPACE_PROFILES
+ );
+
private final Context mContext;
private final String mCameraId;
private final Map<String, CameraCharacteristics> mCharacteristicsMap;
@@ -874,11 +884,17 @@
Class<CameraCharacteristics.Key<?>> keyTyped =
(Class<CameraCharacteristics.Key<?>>) key;
- // Do not include synthetic keys. Including synthetic keys leads to undefined
- // behavior. This causes inclusion of capabilities that may not be supported in
- // camera extensions.
ret.addAll(chars.getAvailableKeyList(CameraCharacteristics.class, keyTyped, keys,
/*includeSynthetic*/ false));
+
+ // Add synthetic keys to the available key list if they are part of the supported
+ // synthetic camera characteristic key list
+ for (CameraCharacteristics.Key charKey :
+ SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS) {
+ if (chars.get(charKey) != null) {
+ ret.add(charKey);
+ }
+ }
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to query the extension for all available keys! Extension "
@@ -990,6 +1006,7 @@
case ImageFormat.YUV_420_888:
case ImageFormat.JPEG:
case ImageFormat.JPEG_R:
+ case ImageFormat.YCBCR_P010:
break;
default:
throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1021,8 +1038,9 @@
return generateJpegSupportedSizes(
extenders.second.getSupportedPostviewResolutions(sz),
streamMap);
- } else if (format == ImageFormat.JPEG_R) {
- // Jpeg_R/UltraHDR is currently not supported in the basic extension case
+ } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+ // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic
+ // extension case
return new ArrayList<>();
} else {
throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1118,16 +1136,16 @@
*
* <p>Device-specific extensions currently support at most three
* multi-frame capture surface formats. ImageFormat.JPEG will be supported by all
- * extensions while ImageFormat.YUV_420_888 and ImageFormat.JPEG_R may or may not be
- * supported.</p>
+ * extensions while ImageFormat.YUV_420_888, ImageFormat.JPEG_R, or ImageFormat.YCBCR_P010
+ * may or may not be supported.</p>
*
* @param extension the extension type
* @param format device-specific extension output format
* @return non-modifiable list of available sizes or an empty list if the format is not
* supported.
* @throws IllegalArgumentException in case of format different from ImageFormat.JPEG,
- * ImageFormat.YUV_420_888, ImageFormat.JPEG_R; or
- * unsupported extension.
+ * ImageFormat.YUV_420_888, ImageFormat.JPEG_R,
+ * ImageFormat.YCBCR_P010; or unsupported extension.
*/
public @NonNull
List<Size> getExtensionSupportedSizes(@Extension int extension, int format) {
@@ -1151,6 +1169,7 @@
case ImageFormat.YUV_420_888:
case ImageFormat.JPEG:
case ImageFormat.JPEG_R:
+ case ImageFormat.YCBCR_P010:
break;
default:
throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1183,8 +1202,9 @@
} else {
return generateSupportedSizes(null, format, streamMap);
}
- } else if (format == ImageFormat.JPEG_R) {
- // Jpeg_R/UltraHDR is currently not supported in the basic extension case
+ } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+ // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the
+ // basic extension case
return new ArrayList<>();
} else {
throw new IllegalArgumentException("Unsupported format: " + format);
@@ -1213,7 +1233,8 @@
* @return the range of estimated minimal and maximal capture latency in milliseconds
* or null if no capture latency info can be provided
* @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG},
- * {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R};
+ * {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R}
+ * {@link ImageFormat#YCBCR_P010};
* or unsupported extension.
*/
public @Nullable Range<Long> getEstimatedCaptureLatencyRangeMillis(@Extension int extension,
@@ -1222,6 +1243,7 @@
case ImageFormat.YUV_420_888:
case ImageFormat.JPEG:
case ImageFormat.JPEG_R:
+ case ImageFormat.YCBCR_P010:
//No op
break;
default:
@@ -1269,8 +1291,8 @@
// specific and cannot be estimated accurately enough.
return null;
}
- if (format == ImageFormat.JPEG_R) {
- // JpegR/UltraHDR is not supported for basic extensions
+ if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) {
+ // JpegR/UltraHDR + YCBCR_P010 is not supported for basic extensions
return null;
}
diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
index 4895f38..8fa09a8 100644
--- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java
+++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
@@ -61,7 +61,6 @@
private CameraUsageTracker mCameraUsageTracker;
private static final String TAG = "AdvancedExtender";
-
/**
* Initialize a camera extension advanced extender instance.
*
@@ -263,6 +262,13 @@
*
* <p>For example, an extension may limit the zoom ratio range. In this case, an OEM can return
* a new zoom ratio range for the key {@link CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE}.
+ *
+ * <p> Currently, the only synthetic keys supported for override are
+ * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES} and
+ * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES}. To enable them, an OEM
+ * should override the respective native keys
+ * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP} and
+ * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP}.
*/
@FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET)
@NonNull
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
index 509bcb8..5567bed 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl
@@ -27,6 +27,7 @@
int imageFormat;
int capacity;
long usage;
+ long dynamicRangeProfile;
const int TYPE_SURFACE = 0;
const int TYPE_IMAGEREADER = 1;
diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
index 53f56bc..001b794 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
+++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
@@ -133,15 +133,4 @@
@DynamicRangeProfiles.Profile long dynamicRangeProfile) {
mOutputSurface.dynamicRangeProfile = dynamicRangeProfile;
}
-
- /**
- * Set the color space. The default colorSpace
- * will be
- * {@link android.hardware.camera2.params.ColorSpaceProfiles.UNSPECIFIED}
- * unless explicitly set using this method.
- */
- @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
- public void setColorSpace(int colorSpace) {
- mOutputSurface.colorSpace = colorSpace;
- }
}
diff --git a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
index 84ca2b6..c4f653c 100644
--- a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
@@ -25,4 +25,5 @@
CameraMetadataNative sessionParameter;
int sessionTemplateId;
int sessionType;
+ int colorSpace;
}
diff --git a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
index 96c88e6..84b7a7f 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
@@ -22,6 +22,7 @@
import android.annotation.SystemApi;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.ColorSpaceProfiles;
import android.os.IBinder;
import com.android.internal.camera.flags.Flags;
@@ -48,6 +49,7 @@
private final int mSessionTemplateId;
private final List<ExtensionOutputConfiguration> mOutputs;
private final CaptureRequest mSessionParameters;
+ private int mColorSpace;
/**
* Initialize an extension configuration instance
@@ -72,6 +74,18 @@
mSessionTemplateId = sessionTemplateId;
mOutputs = outputs;
mSessionParameters = sessionParams;
+ mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
+
+ /**
+ * Set the color space using the ordinal value of a
+ * {@link android.graphics.ColorSpace.Named}.
+ * The default will be -1, indicating an unspecified ColorSpace,
+ * unless explicitly set using this method.
+ */
+ @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT)
+ public void setColorSpace(int colorSpace) {
+ mColorSpace = colorSpace;
}
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
@@ -84,6 +98,11 @@
ret.sessionTemplateId = mSessionTemplateId;
ret.sessionType = mSessionType;
ret.outputConfigs = new ArrayList<>(mOutputs.size());
+ if (Flags.extension10Bit()) {
+ ret.colorSpace = mColorSpace;
+ } else {
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
for (ExtensionOutputConfiguration outputConfig : mOutputs) {
ret.outputConfigs.add(outputConfig.getOutputConfig());
}
diff --git a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
index 9dc6d7b..3a67d61 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.hardware.camera2.params.DynamicRangeProfiles;
import com.android.internal.camera.flags.Flags;
@@ -79,6 +80,11 @@
config.outputId = new OutputConfigId();
config.outputId.id = mOutputConfigId;
config.surfaceGroupId = mSurfaceGroupId;
+ if (Flags.extension10Bit()) {
+ config.dynamicRangeProfile = surface.getDynamicRangeProfile();
+ } else {
+ config.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+ }
}
@Nullable CameraOutputConfig getOutputConfig() {
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index a7d6caf..5b7f8bb 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.Context;
+import android.graphics.ColorSpace;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.SyncFence;
@@ -49,6 +50,7 @@
import android.hardware.camera2.extension.ParcelImage;
import android.hardware.camera2.extension.ParcelTotalCaptureResult;
import android.hardware.camera2.extension.Request;
+import android.hardware.camera2.params.ColorSpaceProfiles;
import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
@@ -62,6 +64,7 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.IntArray;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
@@ -97,6 +100,9 @@
private Surface mClientRepeatingRequestSurface;
private Surface mClientCaptureSurface;
private Surface mClientPostviewSurface;
+ private OutputConfiguration mClientRepeatingRequestOutputConfig;
+ private OutputConfiguration mClientCaptureOutputConfig;
+ private OutputConfiguration mClientPostviewOutputConfig;
private CameraCaptureSession mCaptureSession = null;
private ISessionProcessorImpl mSessionProcessor = null;
private final InitializeSessionHandler mInitializeHandler;
@@ -142,8 +148,19 @@
for (OutputConfiguration c : config.getOutputConfigurations()) {
if (c.getDynamicRangeProfile() != DynamicRangeProfiles.STANDARD) {
- throw new IllegalArgumentException("Unsupported dynamic range profile: " +
- c.getDynamicRangeProfile());
+ if (Flags.extension10Bit() && Flags.cameraExtensionsCharacteristicsGet()) {
+ DynamicRangeProfiles dynamicProfiles = extensionChars.get(
+ config.getExtension(),
+ CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES);
+ if (dynamicProfiles == null || !dynamicProfiles.getSupportedProfiles()
+ .contains(c.getDynamicRangeProfile())) {
+ throw new IllegalArgumentException("Unsupported dynamic range profile: "
+ + c.getDynamicRangeProfile());
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported dynamic range profile: "
+ + c.getDynamicRangeProfile());
+ }
}
if (c.getStreamUseCase() !=
CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT) {
@@ -157,12 +174,26 @@
config.getExtension(), SurfaceTexture.class);
Surface repeatingRequestSurface = CameraExtensionUtils.getRepeatingRequestSurface(
config.getOutputConfigurations(), supportedPreviewSizes);
+ OutputConfiguration repeatingRequestOutputConfig = null;
if (repeatingRequestSurface != null) {
+ for (OutputConfiguration outputConfig : config.getOutputConfigurations()) {
+ if (outputConfig.getSurface() == repeatingRequestSurface) {
+ repeatingRequestOutputConfig = outputConfig;
+ }
+ }
suitableSurfaceCount++;
}
HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>();
- for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+
+ IntArray supportedCaptureOutputFormats =
+ new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+ supportedCaptureOutputFormats.addAll(
+ CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+ if (Flags.extension10Bit()) {
+ supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+ }
+ for (int format : supportedCaptureOutputFormats.toArray()) {
List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
config.getExtension(), format);
if (supportedSizes != null) {
@@ -171,7 +202,13 @@
}
Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface(
config.getOutputConfigurations(), supportedCaptureSizes);
+ OutputConfiguration burstCaptureOutputConfig = null;
if (burstCaptureSurface != null) {
+ for (OutputConfiguration outputConfig : config.getOutputConfigurations()) {
+ if (outputConfig.getSurface() == burstCaptureSurface) {
+ burstCaptureOutputConfig = outputConfig;
+ }
+ }
suitableSurfaceCount++;
}
@@ -180,13 +217,14 @@
}
Surface postviewSurface = null;
+ OutputConfiguration postviewOutputConfig = config.getPostviewOutputConfiguration();
if (burstCaptureSurface != null && config.getPostviewOutputConfiguration() != null) {
CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo =
CameraExtensionUtils.querySurface(burstCaptureSurface);
Size burstCaptureSurfaceSize =
new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight);
HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>();
- for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ for (int format : supportedCaptureOutputFormats.toArray()) {
List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes(
config.getExtension(), burstCaptureSurfaceSize, format);
if (supportedSizesPostview != null) {
@@ -207,8 +245,8 @@
extender.init(cameraId, characteristicsMapNative);
CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(ctx,
- extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface,
- burstCaptureSurface, postviewSurface, config.getStateCallback(),
+ extender, cameraDevice, characteristicsMapNative, repeatingRequestOutputConfig,
+ burstCaptureOutputConfig, postviewOutputConfig, config.getStateCallback(),
config.getExecutor(), sessionId, token, config.getExtension());
ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
@@ -223,8 +261,9 @@
@NonNull IAdvancedExtenderImpl extender,
@NonNull CameraDeviceImpl cameraDevice,
Map<String, CameraMetadataNative> characteristicsMap,
- @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
- @Nullable Surface postviewSurface,
+ @Nullable OutputConfiguration repeatingRequestOutputConfig,
+ @Nullable OutputConfiguration burstCaptureOutputConfig,
+ @Nullable OutputConfiguration postviewOutputConfig,
@NonNull StateCallback callback, @NonNull Executor executor,
int sessionId,
@NonNull IBinder token,
@@ -235,9 +274,18 @@
mCharacteristicsMap = characteristicsMap;
mCallbacks = callback;
mExecutor = executor;
- mClientRepeatingRequestSurface = repeatingRequestSurface;
- mClientCaptureSurface = burstCaptureSurface;
- mClientPostviewSurface = postviewSurface;
+ mClientRepeatingRequestOutputConfig = repeatingRequestOutputConfig;
+ mClientCaptureOutputConfig = burstCaptureOutputConfig;
+ mClientPostviewOutputConfig = postviewOutputConfig;
+ if (repeatingRequestOutputConfig != null) {
+ mClientRepeatingRequestSurface = repeatingRequestOutputConfig.getSurface();
+ }
+ if (burstCaptureOutputConfig != null) {
+ mClientCaptureSurface = burstCaptureOutputConfig.getSurface();
+ }
+ if (postviewOutputConfig != null) {
+ mClientPostviewSurface = postviewOutputConfig.getSurface();
+ }
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
@@ -262,9 +310,9 @@
return;
}
- OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestSurface);
- OutputSurface captureSurface = initializeParcelable(mClientCaptureSurface);
- OutputSurface postviewSurface = initializeParcelable(mClientPostviewSurface);
+ OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestOutputConfig);
+ OutputSurface captureSurface = initializeParcelable(mClientCaptureOutputConfig);
+ OutputSurface postviewSurface = initializeParcelable(mClientPostviewOutputConfig);
mSessionProcessor = mAdvancedExtender.getSessionProcessor();
CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mToken,
@@ -300,6 +348,7 @@
cameraOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
cameraOutput.setReadoutTimestampEnabled(false);
cameraOutput.setPhysicalCameraId(output.physicalCameraId);
+ cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile);
outputList.add(cameraOutput);
mCameraConfigMap.put(cameraOutput.getSurface(), output);
}
@@ -314,7 +363,10 @@
SessionConfiguration sessionConfiguration = new SessionConfiguration(sessionType,
outputList, new CameraExtensionUtils.HandlerExecutor(mHandler),
new SessionStateHandler());
-
+ if (sessionConfig.colorSpace != ColorSpaceProfiles.UNSPECIFIED) {
+ sessionConfiguration.setColorSpace(
+ ColorSpace.Named.values()[sessionConfig.colorSpace]);
+ }
if ((sessionConfig.sessionParameter != null) &&
(!sessionConfig.sessionParameter.isEmpty())) {
CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(
@@ -362,21 +414,38 @@
return ret;
}
- private static OutputSurface initializeParcelable(Surface s) {
+ private static OutputSurface initializeParcelable(OutputConfiguration o) {
OutputSurface ret = new OutputSurface();
- if (s != null) {
+
+ if (o != null && o.getSurface() != null) {
+ Surface s = o.getSurface();
ret.surface = s;
ret.size = new android.hardware.camera2.extension.Size();
Size surfaceSize = SurfaceUtils.getSurfaceSize(s);
ret.size.width = surfaceSize.getWidth();
ret.size.height = surfaceSize.getHeight();
ret.imageFormat = SurfaceUtils.getSurfaceFormat(s);
+
+ if (Flags.extension10Bit()) {
+ ret.dynamicRangeProfile = o.getDynamicRangeProfile();
+ ColorSpace colorSpace = o.getColorSpace();
+ if (colorSpace != null) {
+ ret.colorSpace = colorSpace.getId();
+ } else {
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
+ } else {
+ ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
} else {
ret.surface = null;
ret.size = new android.hardware.camera2.extension.Size();
ret.size.width = -1;
ret.size.height = -1;
ret.imageFormat = ImageFormat.UNKNOWN;
+ ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
}
return ret;
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 725b413..5b32f33 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -58,12 +58,15 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.IntArray;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.Pair;
import android.util.Size;
import android.view.Surface;
+import com.android.internal.camera.flags.Flags;
+
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
@@ -183,7 +186,14 @@
}
HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>();
- for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ IntArray supportedCaptureOutputFormats =
+ new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+ supportedCaptureOutputFormats.addAll(
+ CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+ if (Flags.extension10Bit()) {
+ supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+ }
+ for (int format : supportedCaptureOutputFormats.toArray()) {
List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes(
config.getExtension(), format);
if (supportedSizes != null) {
@@ -207,7 +217,7 @@
Size burstCaptureSurfaceSize =
new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight);
HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>();
- for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ for (int format : supportedCaptureOutputFormats.toArray()) {
List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes(
config.getExtension(), burstCaptureSurfaceSize, format);
if (supportedSizesPostview != null) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index a8066aa..f0c6e2e 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -29,10 +29,13 @@
import android.media.Image;
import android.media.ImageWriter;
import android.os.Handler;
+import android.util.IntArray;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
+import com.android.internal.camera.flags.Flags;
+
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -130,9 +133,16 @@
public static Surface getBurstCaptureSurface(
@NonNull List<OutputConfiguration> outputConfigs,
@NonNull HashMap<Integer, List<Size>> supportedCaptureSizes) {
+ IntArray supportedCaptureOutputFormats =
+ new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length);
+ supportedCaptureOutputFormats.addAll(
+ CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS);
+ if (Flags.extension10Bit()) {
+ supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010);
+ }
for (OutputConfiguration config : outputConfigs) {
SurfaceInfo surfaceInfo = querySurface(config.getSurface());
- for (int supportedFormat : SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+ for (int supportedFormat : supportedCaptureOutputFormats.toArray()) {
if (surfaceInfo.mFormat == supportedFormat) {
Size captureSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
if (supportedCaptureSizes.containsKey(supportedFormat)) {
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index 905d911..76888f3 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -543,11 +543,13 @@
int identifier = source.readInt();
String name = source.readString8();
ArraySet<@DeviceStateProperties Integer> systemProperties = new ArraySet<>();
- for (int i = 0; i < source.readInt(); i++) {
+ int systemPropertySize = source.readInt();
+ for (int i = 0; i < systemPropertySize; i++) {
systemProperties.add(source.readInt());
}
ArraySet<@DeviceStateProperties Integer> physicalProperties = new ArraySet<>();
- for (int j = 0; j < source.readInt(); j++) {
+ int physicalPropertySize = source.readInt();
+ for (int j = 0; j < physicalPropertySize; j++) {
physicalProperties.add(source.readInt());
}
return new DeviceState.Configuration(identifier, name, systemProperties,
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index a3a2a2e..c5167db 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -304,7 +304,11 @@
*
* @param id primary identifier of a program to fetch
* @return the program info, or null if there is no such program on the list
+ *
+ * @deprecated Use {@link #getProgramInfos(ProgramSelector.Identifier)} to get all programs
+ * with the given primary identifier
*/
+ @Deprecated
public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
Map<UniqueProgramIdentifier, RadioManager.ProgramInfo> entries;
synchronized (mLock) {
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index a968c6f..0740374 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -312,15 +312,23 @@
public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
/**
* 1: AM, 2:FM
+ * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
/**
* 32bit primary identifier for SiriusXM Satellite Radio.
+ *
+ * @deprecated SiriusXM Satellite Radio is not supported
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
/**
* 0-999 range
+ *
+ * @deprecated SiriusXM Satellite Radio is not supported
*/
+ @Deprecated
public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
/**
* 44bit compound primary identifier for Digital Audio Broadcasting and
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 61cf8901..da6c686 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -166,7 +166,12 @@
* analog handover state managed from the HAL implementation side.
*
* <p>Some radio technologies may not support this, i.e. DAB.
+ *
+ * @deprecated Use {@link #CONFIG_FORCE_ANALOG_FM} instead. If {@link #CONFIG_FORCE_ANALOG_FM}
+ * is supported in HAL, {@link RadioTuner#setConfigFlag} and {@link RadioTuner#isConfigFlagSet}
+ * with CONFIG_FORCE_ANALOG will set/get the value of {@link #CONFIG_FORCE_ANALOG_FM}.
*/
+ @Deprecated
public static final int CONFIG_FORCE_ANALOG = 2;
/**
* Forces the digital playback for the supporting radio technology.
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 09e6b5d..c489c58 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -38,7 +38,6 @@
boolean isDreaming();
@UnsupportedAppUsage
boolean isDreamingOrInPreview();
- @UnsupportedAppUsage
boolean canStartDreaming(boolean isScreenOn);
void finishSelf(in IBinder token, boolean immediate);
void startDozing(in IBinder token, int screenState, int screenBrightness);
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 1acfc1b..1ca51ad 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -233,6 +233,17 @@
public static final String DEVICE_CONFIG_INCLUDE_INVISIBLE_VIEW_GROUP_IN_ASSIST_STRUCTURE =
"include_invisible_view_group_in_assist_structure";
+ /**
+ * Bugfix flag, Autofill should ignore views resetting to empty states.
+ *
+ * See frameworks/base/services/autofill/bugfixes.aconfig#ignore_view_state_reset_to_empty
+ * for more information.
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_IGNORE_VIEW_STATE_RESET_TO_EMPTY =
+ "ignore_view_state_reset_to_empty";
+
// END AUTOFILL FOR ALL APPS FLAGS //
@@ -494,6 +505,14 @@
false);
}
+ /** @hide */
+ public static boolean shouldIgnoreViewStateResetToEmpty() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_IGNORE_VIEW_STATE_RESET_TO_EMPTY,
+ false);
+ }
+
/**
* Whether should enable multi-line filter
*
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 14c5348..d12eda3 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1203,7 +1203,11 @@
* changes to this setting after that point.
*
* @param flag {@code true} if the WebView should use the database storage API
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract void setDatabaseEnabled(boolean flag);
/**
@@ -1236,7 +1240,11 @@
*
* @return {@code true} if the database storage API is enabled
* @see #setDatabaseEnabled
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract boolean getDatabaseEnabled();
/**
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index a2d8d80..e3caf70 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5127,7 +5127,10 @@
* @param viewId The id of the {@link AdapterView}
* @param intent The intent of the service which will be
* providing data to the RemoteViewsAdapter
+ * @deprecated use
+ * {@link #setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)} instead
*/
+ @Deprecated
public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
if (remoteAdapterConversion()) {
addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index ce74848..82e613e 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -77,4 +77,12 @@
description: "Properties to allow apps and activities to opt-in to cover display rendering"
bug: "312530526"
is_fixed_read_only: true
+}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "enable_wm_extensions_for_all_flag"
+ description: "Whether to enable WM Extensions for all devices"
+ bug: "306666082"
+ is_fixed_read_only: true
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index cbe0700..d4dcec9 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -93,6 +93,9 @@
throw ex;
}
+ if (peer.getUid() != Process.SYSTEM_UID) {
+ throw new ZygoteSecurityException("Only system UID is allowed to connect to Zygote.");
+ }
isEof = false;
}
diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
index 54c4cd5..e0cc055 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -354,6 +354,18 @@
return result;
}
+static uid_t getSocketPeerUid(int socket, const std::function<void(const std::string&)>& fail_fn) {
+ struct ucred credentials;
+ socklen_t cred_size = sizeof credentials;
+ if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
+ || cred_size != sizeof credentials) {
+ fail_fn(CREATE_ERROR("Failed to get socket credentials, %s",
+ strerror(errno)));
+ }
+
+ return credentials.uid;
+}
+
// Read all lines from the current command into the buffer, and then reset the buffer, so
// we will start reading again at the beginning of the command, starting with the argument
// count. And we don't need access to the fd to do so.
@@ -413,19 +425,12 @@
fail_fn_z("Failed to retrieve session socket timeout");
}
- struct ucred credentials;
- socklen_t cred_size = sizeof credentials;
- if (getsockopt(n_buffer->getFd(), SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
- || cred_size != sizeof credentials) {
- fail_fn_1(CREATE_ERROR("ForkRepeatedly failed to get initial credentials, %s",
- strerror(errno)));
+ uid_t peerUid = getSocketPeerUid(session_socket, fail_fn_1);
+ if (peerUid != static_cast<uid_t>(expected_uid)) {
+ return JNI_FALSE;
}
-
bool first_time = true;
do {
- if (credentials.uid != static_cast<uid_t>(expected_uid)) {
- return JNI_FALSE;
- }
n_buffer->readAllLines(first_time ? fail_fn_1 : fail_fn_n);
n_buffer->reset();
int pid = zygote::forkApp(env, /* no pipe FDs */ -1, -1, session_socket_fds,
@@ -453,6 +458,7 @@
}
}
for (;;) {
+ bool valid_session_socket = true;
// Clear buffer and get count from next command.
n_buffer->clear();
// Poll isn't strictly necessary for now. But without it, disconnect is hard to detect.
@@ -463,25 +469,50 @@
if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) {
if (n_buffer->getCount(fail_fn_z) != 0) {
break;
- } // else disconnected;
+ } else {
+ // Session socket was disconnected
+ valid_session_socket = false;
+ close(session_socket);
+ }
} else if (poll_res == 0 || (fd_structs[ZYGOTE_IDX].revents & POLLIN) == 0) {
fail_fn_z(
CREATE_ERROR("Poll returned with no descriptors ready! Poll returned %d", poll_res));
}
- // We've now seen either a disconnect or connect request.
- close(session_socket);
- int new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr));
+ int new_fd = -1;
+ do {
+ // We've now seen either a disconnect or connect request.
+ new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr));
+ if (new_fd == -1) {
+ fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)));
+ }
+ uid_t newPeerUid = getSocketPeerUid(new_fd, fail_fn_1);
+ if (newPeerUid != static_cast<uid_t>(expected_uid)) {
+ ALOGW("Dropping new connection with a mismatched uid %d\n", newPeerUid);
+ close(new_fd);
+ new_fd = -1;
+ } else {
+ // If we still have a valid session socket, close it now
+ if (valid_session_socket) {
+ close(session_socket);
+ }
+ valid_session_socket = true;
+ }
+ } while (!valid_session_socket);
+
+ // At this point we either have a valid new connection (new_fd > 0), or
+ // an existing session socket we can poll on
if (new_fd == -1) {
- fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)));
+ // The new connection wasn't valid, and we still have an old one; retry polling
+ continue;
}
if (new_fd != session_socket) {
- // Move new_fd back to the old value, so that we don't have to change Java-level data
- // structures to reflect a change. This implicitly closes the old one.
- if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) {
- fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
- new_fd, session_socket, strerror(errno)));
- }
- close(new_fd); // On Linux, fd is closed even if EINTR is returned.
+ // Move new_fd back to the old value, so that we don't have to change Java-level data
+ // structures to reflect a change. This implicitly closes the old one.
+ if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) {
+ fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
+ new_fd, session_socket, strerror(errno)));
+ }
+ close(new_fd); // On Linux, fd is closed even if EINTR is returned.
}
// If we ever return, we effectively reuse the old Java ZygoteConnection.
// None of its state needs to change.
@@ -493,13 +524,6 @@
fail_fn_z(CREATE_ERROR("Failed to set send timeout for socket %d: %s",
session_socket, strerror(errno)));
}
- if (getsockopt(session_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1) {
- fail_fn_z(CREATE_ERROR("ForkMany failed to get credentials: %s", strerror(errno)));
- }
- if (cred_size != sizeof credentials) {
- fail_fn_z(CREATE_ERROR("ForkMany credential size = %d, should be %d",
- cred_size, static_cast<int>(sizeof credentials)));
- }
}
first_time = false;
} while (n_buffer->isSimpleForkCommand(minUid, fail_fn_n));
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1acdc75..8ea742d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6916,6 +6916,12 @@
<permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"
android:protectionLevel="signature" />
+ <!-- @hide @TestApi Allows an application to record sensitive content during media
+ projection. This is intended for on device screen recording system app.
+ @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") -->
+ <permission android:name="android.permission.RECORD_SENSITIVE_CONTENT"
+ android:protectionLevel="signature"/>
+
<!-- @SystemApi Allows an application to read install sessions
@hide This is not a third-party API (intended for system apps). -->
<permission android:name="android.permission.READ_INSTALL_SESSIONS"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d89f236..5e900f7 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -3287,10 +3287,9 @@
-->
<attr name="enableOnBackInvokedCallback" format="boolean"/>
- <!-- Specifies permissions necessary to launch this activity via
- {@link android.content.Context#startActivity} when passing content URIs. The default
- value is {@code none}, meaning no specific permissions are required. Setting this
- attribute restricts activity invocation based on the invoker's permissions. If the
+ <!-- Specifies permissions necessary to launch this activity when passing content URIs. The
+ default value is {@code none}, meaning no specific permissions are required. Setting
+ this attribute restricts activity invocation based on the invoker's permissions. If the
invoker doesn't have the required permissions, the activity start will be denied via a
{@link java.lang.SecurityException}.
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
index 76772b7..0897726 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java
@@ -16,6 +16,12 @@
package android.hardware.devicestate;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -33,6 +39,7 @@
import org.junit.runners.JUnit4;
import java.util.List;
+import java.util.Set;
/**
* Unit tests for {@link DeviceStateInfo}.
@@ -44,11 +51,25 @@
public final class DeviceStateInfoTest {
private static final DeviceState DEVICE_STATE_0 = new DeviceState(
- new DeviceState.Configuration.Builder(0, "STATE_0").build());
+ new DeviceState.Configuration.Builder(0, "STATE_0")
+ .setSystemProperties(
+ Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY))
+ .setPhysicalProperties(
+ Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED))
+ .build());
private static final DeviceState DEVICE_STATE_1 = new DeviceState(
- new DeviceState.Configuration.Builder(1, "STATE_1").build());
+ new DeviceState.Configuration.Builder(1, "STATE_1")
+ .setSystemProperties(
+ Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY))
+ .setPhysicalProperties(
+ Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN))
+ .build());
private static final DeviceState DEVICE_STATE_2 = new DeviceState(
- new DeviceState.Configuration.Builder(2, "STATE_2").build());
+ new DeviceState.Configuration.Builder(2, "STATE_2")
+ .setSystemProperties(
+ Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY))
+ .build());
@Test
public void create() {
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
index 68de21f..78d4324 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
@@ -93,4 +93,22 @@
Assert.assertEquals(originalState, new DeviceState(stateConfiguration));
}
+
+ @Test
+ public void writeToParcel_noPhysicalProperties() {
+ final DeviceState originalState = new DeviceState(
+ new DeviceState.Configuration.Builder(0, "TEST_STATE")
+ .setSystemProperties(Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS,
+ PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST))
+ .build());
+
+ final Parcel parcel = Parcel.obtain();
+ originalState.getConfiguration().writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+
+ final DeviceState.Configuration stateConfiguration =
+ DeviceState.Configuration.CREATOR.createFromParcel(parcel);
+
+ Assert.assertEquals(originalState, new DeviceState(stateConfiguration));
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 539832e..d44033c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -523,8 +523,8 @@
/**
* Whether we should use jump cut for the change transition.
* This normally happens when opening a new secondary with the existing primary using a
- * different split layout. This can be complicated, like from horizontal to vertical split with
- * new split pairs.
+ * different split layout (ratio or direction). This can be complicated, like from horizontal to
+ * vertical split with new split pairs.
* Uses a jump cut animation to simplify.
*/
private boolean shouldUseJumpCutForChangeTransition(@NonNull TransitionInfo info) {
@@ -553,8 +553,8 @@
}
// Check if the transition contains both opening and closing windows.
- boolean hasOpeningWindow = false;
- boolean hasClosingWindow = false;
+ final List<TransitionInfo.Change> openChanges = new ArrayList<>();
+ final List<TransitionInfo.Change> closeChanges = new ArrayList<>();
for (TransitionInfo.Change change : info.getChanges()) {
if (changingChanges.contains(change)) {
continue;
@@ -564,10 +564,30 @@
// No-op if it will be covered by the changing parent window.
continue;
}
- hasOpeningWindow |= TransitionUtil.isOpeningType(change.getMode());
- hasClosingWindow |= TransitionUtil.isClosingType(change.getMode());
+ if (TransitionUtil.isOpeningType(change.getMode())) {
+ openChanges.add(change);
+ } else if (TransitionUtil.isClosingType(change.getMode())) {
+ closeChanges.add(change);
+ }
}
- return hasOpeningWindow && hasClosingWindow;
+ if (openChanges.isEmpty() || closeChanges.isEmpty()) {
+ // Only skip if the transition contains both open and close.
+ return false;
+ }
+ if (changingChanges.size() != 1 || openChanges.size() != 1 || closeChanges.size() != 1) {
+ // Skip when there are too many windows involved.
+ return true;
+ }
+ final TransitionInfo.Change changingChange = changingChanges.get(0);
+ final TransitionInfo.Change openChange = openChanges.get(0);
+ final TransitionInfo.Change closeChange = closeChanges.get(0);
+ if (changingChange.getStartAbsBounds().equals(openChange.getEndAbsBounds())
+ && changingChange.getEndAbsBounds().equals(closeChange.getStartAbsBounds())) {
+ // Don't skip if the transition is a simple shifting without split direction or ratio
+ // change. For example, A|B -> B|C.
+ return false;
+ }
+ return true;
}
/** Updates the changes to end states in {@code startTransaction} for jump cut animation. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 9585842..4455a3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1259,12 +1259,14 @@
* Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble
* exists for this entry, and it is able to bubble, a new bubble will be created.
*
- * This is the method to use when opening a bubble via a notification or in a state where
+ * <p>This is the method to use when opening a bubble via a notification or in a state where
* the device might not be unlocked.
*
* @param entry the entry to use for the bubble.
*/
public void expandStackAndSelectBubble(BubbleEntry entry) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "opening bubble from notification key=%s mIsStatusBarShade=%b",
+ entry.getKey(), mIsStatusBarShade);
if (mIsStatusBarShade) {
mNotifEntryToExpandOnShadeUnlock = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 8b2ec0a..8d489e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -846,8 +846,10 @@
static ShellController provideShellController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
+ DisplayInsetsController displayInsetsController,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellController(context, shellInit, shellCommandHandler, mainExecutor);
+ return new ShellController(context, shellInit, shellCommandHandler,
+ displayInsetsController, mainExecutor);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index fb3c35b..04f0f44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -57,6 +57,8 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
+import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -509,6 +511,7 @@
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver,
LaunchAdjacentController launchAdjacentController,
RecentsTransitionHandler recentsTransitionHandler,
MultiInstanceHelper multiInstanceHelper,
@@ -518,7 +521,8 @@
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
dragAndDropController, transitions, enterDesktopTransitionHandler,
exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
- dragToDesktopTransitionHandler, desktopModeTaskRepository, launchAdjacentController,
+ dragToDesktopTransitionHandler, desktopModeTaskRepository,
+ desktopModeLoggerTransitionObserver, launchAdjacentController,
recentsTransitionHandler, multiInstanceHelper, mainExecutor);
}
@@ -562,6 +566,22 @@
return new DesktopModeTaskRepository();
}
+ @WMSingleton
+ @Provides
+ static DesktopModeLoggerTransitionObserver provideDesktopModeLoggerTransitionObserver(
+ ShellInit shellInit,
+ Transitions transitions,
+ DesktopModeEventLogger desktopModeEventLogger) {
+ return new DesktopModeLoggerTransitionObserver(
+ shellInit, transitions, desktopModeEventLogger);
+ }
+
+ @WMSingleton
+ @Provides
+ static DesktopModeEventLogger provideDesktopModeEventLogger() {
+ return new DesktopModeEventLogger();
+ }
+
//
// Drag and drop
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
new file mode 100644
index 0000000..a10c7c0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.app.TaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.os.IBinder
+import android.util.SparseArray
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import androidx.annotation.VisibleForTesting
+import androidx.core.util.containsKey
+import androidx.core.util.forEach
+import androidx.core.util.isEmpty
+import androidx.core.util.isNotEmpty
+import androidx.core.util.plus
+import androidx.core.util.putAll
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log
+ * appropriate desktop mode session log events. This observes transitions related to desktop mode
+ * and other transitions that originate both within and outside shell.
+ */
+class DesktopModeLoggerTransitionObserver(
+ shellInit: ShellInit,
+ private val transitions: Transitions,
+ private val desktopModeEventLogger: DesktopModeEventLogger
+) : Transitions.TransitionObserver {
+
+ private val idSequence: InstanceIdSequence by lazy { InstanceIdSequence(Int.MAX_VALUE) }
+
+ init {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.isEnabled()) {
+ shellInit.addInitCallback(this::onInit, this)
+ }
+ }
+
+ // A sparse array of visible freeform tasks and taskInfos
+ private val visibleFreeformTaskInfos: SparseArray<TaskInfo> = SparseArray()
+
+ // Caching the taskInfos to handle canceled recents animations, if we identify that the recents
+ // animation was cancelled, we restore these tasks to calculate the post-Transition state
+ private val tasksSavedForRecents: SparseArray<TaskInfo> = SparseArray()
+
+ // The instanceId for the current logging session
+ private var loggerInstanceId: InstanceId? = null
+
+ private val isSessionActive: Boolean
+ get() = loggerInstanceId != null
+
+ private fun setSessionInactive() {
+ loggerInstanceId = null
+ }
+
+ fun onInit() {
+ transitions.registerObserver(this)
+ }
+
+ override fun onTransitionReady(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction
+ ) {
+ // this was a new recents animation
+ if (info.isRecentsTransition() && tasksSavedForRecents.isEmpty()) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Recents animation running, saving tasks for later"
+ )
+ // TODO (b/326391303) - avoid logging session exit if we can identify a cancelled
+ // recents animation
+
+ // when recents animation is running, all freeform tasks are sent TO_BACK temporarily
+ // if the user ends up at home, we need to update the visible freeform tasks
+ // if the user cancels the animation, the subsequent transition is NONE
+ // if the user opens a new task, the subsequent transition is OPEN with flag
+ tasksSavedForRecents.putAll(visibleFreeformTaskInfos)
+ }
+
+ // figure out what the new state of freeform tasks would be post transition
+ var postTransitionVisibleFreeformTasks = getPostTransitionVisibleFreeformTaskInfos(info)
+
+ // A canceled recents animation is followed by a TRANSIT_NONE transition with no flags, if
+ // that's the case, we might have accidentally logged a session exit and would need to
+ // revaluate again. Add all the tasks back.
+ // This will start a new desktop mode session.
+ if (
+ info.type == WindowManager.TRANSIT_NONE &&
+ info.flags == 0 &&
+ tasksSavedForRecents.isNotEmpty()
+ ) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Canceled recents animation, restoring tasks"
+ )
+ // restore saved tasks in the updated set and clear for next use
+ postTransitionVisibleFreeformTasks += tasksSavedForRecents
+ tasksSavedForRecents.clear()
+ }
+
+ // identify if we need to log any changes and update the state of visible freeform tasks
+ identifyLogEventAndUpdateState(
+ transitionInfo = info,
+ preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos,
+ postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks
+ )
+ }
+
+ override fun onTransitionStarting(transition: IBinder) {}
+
+ override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
+
+ override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {}
+
+ private fun getPostTransitionVisibleFreeformTaskInfos(
+ info: TransitionInfo
+ ): SparseArray<TaskInfo> {
+ // device is sleeping, so no task will be visible anymore
+ if (info.type == WindowManager.TRANSIT_SLEEP) {
+ return SparseArray()
+ }
+
+ // filter changes involving freeform tasks or tasks that were cached in previous state
+ val changesToFreeformWindows =
+ info.changes
+ .filter { it.taskInfo != null && it.requireTaskInfo().taskId != INVALID_TASK_ID }
+ .filter {
+ it.requireTaskInfo().isFreeformWindow() ||
+ visibleFreeformTaskInfos.containsKey(it.requireTaskInfo().taskId)
+ }
+
+ val postTransitionFreeformTasks: SparseArray<TaskInfo> = SparseArray()
+ // start off by adding all existing tasks
+ postTransitionFreeformTasks.putAll(visibleFreeformTaskInfos)
+
+ // the combined set of taskInfos we are interested in this transition change
+ for (change in changesToFreeformWindows) {
+ val taskInfo = change.requireTaskInfo()
+
+ // check if this task existed as freeform window in previous cached state and it's now
+ // changing window modes
+ if (
+ visibleFreeformTaskInfos.containsKey(taskInfo.taskId) &&
+ visibleFreeformTaskInfos.get(taskInfo.taskId).isFreeformWindow() &&
+ !taskInfo.isFreeformWindow()
+ ) {
+ postTransitionFreeformTasks.remove(taskInfo.taskId)
+ // no need to evaluate new visibility of this task, since it's no longer a freeform
+ // window
+ continue
+ }
+
+ // check if the task is visible after this change, otherwise remove it
+ if (isTaskVisibleAfterChange(change)) {
+ postTransitionFreeformTasks.put(taskInfo.taskId, taskInfo)
+ } else {
+ postTransitionFreeformTasks.remove(taskInfo.taskId)
+ }
+ }
+
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: taskInfo map after processing changes %s",
+ postTransitionFreeformTasks.size()
+ )
+
+ return postTransitionFreeformTasks
+ }
+
+ /**
+ * Look at the [TransitionInfo.Change] and figure out if this task will be visible after this
+ * change is processed
+ */
+ private fun isTaskVisibleAfterChange(change: TransitionInfo.Change): Boolean =
+ when {
+ TransitionUtil.isOpeningType(change.mode) -> true
+ TransitionUtil.isClosingType(change.mode) -> false
+ // change mode TRANSIT_CHANGE is only for visible to visible transitions
+ change.mode == WindowManager.TRANSIT_CHANGE -> true
+ else -> false
+ }
+
+ /**
+ * Log the appropriate log event based on the new state of TasksInfos and previously cached
+ * state and update it
+ */
+ private fun identifyLogEventAndUpdateState(
+ transitionInfo: TransitionInfo,
+ preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
+ postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+ ) {
+ if (
+ postTransitionVisibleFreeformTasks.isEmpty() &&
+ preTransitionVisibleFreeformTasks.isNotEmpty() &&
+ isSessionActive
+ ) {
+ // Sessions is finishing, log task updates followed by an exit event
+ identifyAndLogTaskUpdates(
+ loggerInstanceId!!.id,
+ preTransitionVisibleFreeformTasks,
+ postTransitionVisibleFreeformTasks
+ )
+
+ desktopModeEventLogger.logSessionExit(
+ loggerInstanceId!!.id,
+ getExitReason(transitionInfo)
+ )
+
+ setSessionInactive()
+ } else if (
+ postTransitionVisibleFreeformTasks.isNotEmpty() &&
+ preTransitionVisibleFreeformTasks.isEmpty() &&
+ !isSessionActive
+ ) {
+ // Session is starting, log enter event followed by task updates
+ loggerInstanceId = idSequence.newInstanceId()
+ desktopModeEventLogger.logSessionEnter(
+ loggerInstanceId!!.id,
+ getEnterReason(transitionInfo)
+ )
+
+ identifyAndLogTaskUpdates(
+ loggerInstanceId!!.id,
+ preTransitionVisibleFreeformTasks,
+ postTransitionVisibleFreeformTasks
+ )
+ } else if (isSessionActive) {
+ // Session is neither starting, nor finishing, log task updates if there are any
+ identifyAndLogTaskUpdates(
+ loggerInstanceId!!.id,
+ preTransitionVisibleFreeformTasks,
+ postTransitionVisibleFreeformTasks
+ )
+ }
+
+ // update the state to the new version
+ visibleFreeformTaskInfos.clear()
+ visibleFreeformTaskInfos.putAll(postTransitionVisibleFreeformTasks)
+ }
+
+ // TODO(b/326231724) - Add logging around taskInfoChanges Updates
+ /** Compare the old and new state of taskInfos and identify and log the changes */
+ private fun identifyAndLogTaskUpdates(
+ sessionId: Int,
+ preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>,
+ postTransitionVisibleFreeformTasks: SparseArray<TaskInfo>
+ ) {
+ // find new tasks that were added
+ postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
+ if (!preTransitionVisibleFreeformTasks.containsKey(taskId)) {
+ desktopModeEventLogger.logTaskAdded(sessionId, buildTaskUpdateForTask(taskInfo))
+ }
+ }
+
+ // find old tasks that were removed
+ preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo ->
+ if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) {
+ desktopModeEventLogger.logTaskRemoved(sessionId, buildTaskUpdateForTask(taskInfo))
+ }
+ }
+ }
+
+ // TODO(b/326231724: figure out how to get taskWidth and taskHeight from TaskInfo
+ private fun buildTaskUpdateForTask(taskInfo: TaskInfo): TaskUpdate {
+ val taskUpdate = TaskUpdate(taskInfo.taskId, taskInfo.userId)
+ // add task x, y if available
+ taskInfo.positionInParent?.let { taskUpdate.copy(taskX = it.x, taskY = it.y) }
+
+ return taskUpdate
+ }
+
+ /** Get [EnterReason] for this session enter */
+ private fun getEnterReason(transitionInfo: TransitionInfo): EnterReason {
+ // TODO(b/326231756) - Add support for missing enter reasons
+ return when (transitionInfo.type) {
+ WindowManager.TRANSIT_WAKE -> EnterReason.SCREEN_ON
+ Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> EnterReason.APP_HANDLE_DRAG
+ Transitions.TRANSIT_MOVE_TO_DESKTOP -> EnterReason.APP_HANDLE_MENU_BUTTON
+ WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT
+ else -> EnterReason.UNKNOWN_ENTER
+ }
+ }
+
+ /** Get [ExitReason] for this session exit */
+ private fun getExitReason(transitionInfo: TransitionInfo): ExitReason {
+ // TODO(b/326231756) - Add support for missing exit reasons
+ return when {
+ transitionInfo.type == WindowManager.TRANSIT_SLEEP -> ExitReason.SCREEN_OFF
+ transitionInfo.type == WindowManager.TRANSIT_CLOSE -> ExitReason.TASK_FINISHED
+ transitionInfo.type == Transitions.TRANSIT_EXIT_DESKTOP_MODE -> ExitReason.DRAG_TO_EXIT
+ transitionInfo.isRecentsTransition() -> ExitReason.RETURN_HOME_OR_OVERVIEW
+ else -> ExitReason.UNKNOWN_EXIT
+ }
+ }
+
+ /** Adds tasks to the saved copy of freeform taskId, taskInfo. Only used for testing. */
+ @VisibleForTesting
+ fun addTaskInfosToCachedMap(taskInfo: TaskInfo) {
+ visibleFreeformTaskInfos.set(taskInfo.taskId, taskInfo)
+ }
+
+ @VisibleForTesting fun getLoggerSessionId(): Int? = loggerInstanceId?.id
+
+ @VisibleForTesting
+ fun setLoggerSessionId(id: Int) {
+ loggerInstanceId = InstanceId.fakeInstanceId(id)
+ }
+
+ private fun TransitionInfo.Change.requireTaskInfo(): RunningTaskInfo {
+ return this.taskInfo ?: throw IllegalStateException("Expected TaskInfo in the Change")
+ }
+
+ private fun TaskInfo.isFreeformWindow(): Boolean {
+ return this.windowingMode == WINDOWING_MODE_FREEFORM
+ }
+
+ private fun TransitionInfo.isRecentsTransition(): Boolean {
+ return this.type == WindowManager.TRANSIT_TO_FRONT &&
+ this.flags == WindowManager.TRANSIT_FLAG_IS_RECENTS
+ }
+}
\ No newline at end of file
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 95237c3..dd8c1a0 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
@@ -102,6 +102,7 @@
ToggleResizeDesktopTaskTransitionHandler,
private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver,
private val launchAdjacentController: LaunchAdjacentController,
private val recentsTransitionHandler: RecentsTransitionHandler,
private val multiInstanceHelper: MultiInstanceHelper,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 952e2d4..86c8f04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -436,7 +436,11 @@
}
public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
- mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ mStageCoordinator.dismissSplitScreen(toTopTaskId, exitReason);
+ } else {
+ mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 7f16c5e..af11ebc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.splitscreen;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -45,6 +46,8 @@
return runSetSideStagePosition(args, pw);
case "switchSplitPosition":
return runSwitchSplitPosition();
+ case "exitSplitScreen":
+ return runExitSplitScreen(args, pw);
default:
pw.println("Invalid command: " + args[0]);
return false;
@@ -91,6 +94,17 @@
return true;
}
+ private boolean runExitSplitScreen(String[] args, PrintWriter pw) {
+ if (args.length < 2) {
+ // First argument is the action name.
+ pw.println("Error: task id should be provided as arguments");
+ return false;
+ }
+ final int taskId = Integer.parseInt(args[1]);
+ mController.exitSplitScreen(taskId, EXIT_REASON_UNKNOWN);
+ return true;
+ }
+
@Override
public void printShellCommandHelp(PrintWriter pw, String prefix) {
pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>");
@@ -101,5 +115,7 @@
pw.println(prefix + " Sets the position of the side-stage.");
pw.println(prefix + "switchSplitPosition");
pw.println(prefix + " Reverses the split.");
+ pw.println(prefix + "exitSplitScreen <taskId>");
+ pw.println(prefix + " Exits split screen and leaves the provided split task on top.");
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 9dd4c19..36368df9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -29,6 +29,7 @@
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -1450,6 +1451,7 @@
mExitSplitScreenOnHide = exitSplitScreenOnHide;
}
+ /** Exits split screen with legacy transition */
void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: topTaskId=%d reason=%s active=%b",
toTopTaskId, exitReasonToString(exitReason), mMainStage.isActive());
@@ -1469,6 +1471,7 @@
applyExitSplitScreen(childrenToTop, wct, exitReason);
}
+ /** Exits split screen with legacy transition */
private void exitSplitScreen(@Nullable StageTaskListener childrenToTop,
@ExitReason int exitReason) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: mainStageToTop=%b reason=%s active=%b",
@@ -1546,6 +1549,14 @@
}
}
+ void dismissSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
+ if (!mMainStage.isActive()) return;
+ final int stage = getStageOfTask(toTopTaskId);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(stage, wct);
+ mSplitTransitions.startDismissTransition(wct, this, stage, exitReason);
+ }
+
/**
* Overridden by child classes.
*/
@@ -1611,6 +1622,8 @@
// User has used a keyboard shortcut to go back to fullscreen from split
case EXIT_REASON_DESKTOP_MODE:
// One of the children enters desktop mode
+ case EXIT_REASON_UNKNOWN:
+ // Unknown reason
return true;
default:
return false;
@@ -2776,7 +2789,7 @@
+ " with " + taskInfo.taskId + " before startAnimation().");
record.addRecord(stage, true, taskInfo.taskId);
}
- } else if (isClosingType(change.getMode())) {
+ } else if (change.getMode() == TRANSIT_CLOSE) {
if (stage.containsTask(taskInfo.taskId)) {
record.addRecord(stage, false, taskInfo.taskId);
Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java
new file mode 100644
index 0000000..a94f802
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+import android.graphics.Rect;
+
+/**
+ * Callbacks for when the Display IME changes.
+ */
+public interface DisplayImeChangeListener {
+ /**
+ * Called when the ime bounds change.
+ */
+ default void onImeBoundsChanged(int displayId, Rect bounds) {}
+
+ /**
+ * Called when the IME visibility change.
+ */
+ default void onImeVisibilityChanged(int displayId, boolean isShowing) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index a7843e2..2f6edc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -30,21 +30,28 @@
import android.content.pm.ActivityInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.os.Bundle;
import android.util.ArrayMap;
+import android.view.InsetsSource;
+import android.view.InsetsState;
import android.view.SurfaceControlRegistry;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import java.io.PrintWriter;
import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
/**
@@ -57,6 +64,7 @@
private final ShellInit mShellInit;
private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mMainExecutor;
+ private final DisplayInsetsController mDisplayInsetsController;
private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
@@ -65,6 +73,8 @@
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners =
new CopyOnWriteArrayList<>();
+ private final ConcurrentHashMap<DisplayImeChangeListener, Executor> mDisplayImeChangeListeners =
+ new ConcurrentHashMap<>();
private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers =
new ArrayMap<>();
@@ -73,20 +83,53 @@
private Configuration mLastConfiguration;
+ private OnInsetsChangedListener mInsetsChangeListener = new OnInsetsChangedListener() {
+ private InsetsState mInsetsState = new InsetsState();
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ if (mInsetsState == insetsState) {
+ return;
+ }
+
+ InsetsSource oldSource = mInsetsState.peekSource(InsetsSource.ID_IME);
+ boolean wasVisible = (oldSource != null && oldSource.isVisible());
+ Rect oldFrame = wasVisible ? oldSource.getFrame() : null;
+
+ InsetsSource newSource = insetsState.peekSource(InsetsSource.ID_IME);
+ boolean isVisible = (newSource != null && newSource.isVisible());
+ Rect newFrame = isVisible ? newSource.getFrame() : null;
+
+ if (wasVisible != isVisible) {
+ onImeVisibilityChanged(isVisible);
+ }
+
+ if (newFrame != null && !newFrame.equals(oldFrame)) {
+ onImeBoundsChanged(newFrame);
+ }
+
+ mInsetsState = insetsState;
+ }
+ };
+
public ShellController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
+ DisplayInsetsController displayInsetsController,
ShellExecutor mainExecutor) {
mContext = context;
mShellInit = shellInit;
mShellCommandHandler = shellCommandHandler;
+ mDisplayInsetsController = displayInsetsController;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
mShellCommandHandler.addDumpCallback(this::dump, this);
+ mDisplayInsetsController.addInsetsChangedListener(
+ mContext.getDisplayId(), mInsetsChangeListener);
}
/**
@@ -259,6 +302,25 @@
}
}
+ @VisibleForTesting
+ void onImeBoundsChanged(Rect bounds) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime bounds changed");
+ mDisplayImeChangeListeners.forEach(
+ (DisplayImeChangeListener listener, Executor executor) ->
+ executor.execute(() -> listener.onImeBoundsChanged(
+ mContext.getDisplayId(), bounds)));
+ }
+
+ @VisibleForTesting
+ void onImeVisibilityChanged(boolean isShowing) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime visibility changed: isShowing=%b",
+ isShowing);
+ mDisplayImeChangeListeners.forEach(
+ (DisplayImeChangeListener listener, Executor executor) ->
+ executor.execute(() -> listener.onImeVisibilityChanged(
+ mContext.getDisplayId(), isShowing)));
+ }
+
private void handleInit() {
SurfaceControlRegistry.createProcessInstance(mContext);
mShellInit.init();
@@ -329,6 +391,19 @@
}
@Override
+ public void addDisplayImeChangeListener(DisplayImeChangeListener listener,
+ Executor executor) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Adding new DisplayImeChangeListener");
+ mDisplayImeChangeListeners.put(listener, executor);
+ }
+
+ @Override
+ public void removeDisplayImeChangeListener(DisplayImeChangeListener listener) {
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Removing DisplayImeChangeListener");
+ mDisplayImeChangeListeners.remove(listener);
+ }
+
+ @Override
public boolean handleCommand(String[] args, PrintWriter pw) {
try {
boolean[] result = new boolean[1];
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index bc5dd11..bd1c64a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -25,6 +25,7 @@
import java.io.PrintWriter;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* General interface for notifying the Shell of common SysUI events like configuration or keyguard
@@ -65,6 +66,18 @@
default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {}
/**
+ * Registers a DisplayImeChangeListener to monitor for changes on Ime
+ * position and visibility.
+ */
+ default void addDisplayImeChangeListener(DisplayImeChangeListener listener,
+ Executor executor) {}
+
+ /**
+ * Removes a registered DisplayImeChangeListener.
+ */
+ default void removeDisplayImeChangeListener(DisplayImeChangeListener listener) {}
+
+ /**
* Handles a shell command.
*/
default boolean handleCommand(final String[] args, PrintWriter pw) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index ae39fbc..4a4c5e8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -37,6 +37,7 @@
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView
import com.android.wm.shell.bubbles.properties.BubbleProperties
import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.FloatingContentCoordinator
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
@@ -94,7 +95,8 @@
val windowManager = context.getSystemService(WindowManager::class.java)
val shellInit = ShellInit(mainExecutor)
val shellCommandHandler = ShellCommandHandler()
- val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor)
+ val shellController = ShellController(context, shellInit, shellCommandHandler,
+ mock<DisplayInsetsController>(), mainExecutor)
bubblePositioner = BubblePositioner(context, windowManager)
val bubbleData =
BubbleData(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
new file mode 100644
index 0000000..65117f7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS
+import android.view.WindowManager.TRANSIT_NONE
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_SLEEP
+import android.view.WindowManager.TRANSIT_TO_BACK
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.view.WindowManager.TRANSIT_WAKE
+import android.window.IWindowContainerToken
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import android.window.WindowContainerToken
+import androidx.test.filters.SmallTest
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.TransitionInfoBuilder
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.same
+import org.mockito.kotlin.times
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeLoggerTransitionObserverTest {
+
+ @JvmField
+ @Rule
+ val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
+ .mockStatic(DesktopModeEventLogger::class.java)
+ .mockStatic(DesktopModeStatus::class.java).build()!!
+
+ @Mock
+ lateinit var testExecutor: ShellExecutor
+ @Mock
+ private lateinit var mockShellInit: ShellInit
+ @Mock
+ private lateinit var transitions: Transitions
+
+ private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
+ private lateinit var shellInit: ShellInit
+ private lateinit var desktopModeEventLogger: DesktopModeEventLogger
+
+ @Before
+ fun setup() {
+ Mockito.`when`(DesktopModeStatus.isEnabled()).thenReturn(true)
+ shellInit = Mockito.spy(ShellInit(testExecutor))
+ desktopModeEventLogger = mock(DesktopModeEventLogger::class.java)
+
+ transitionObserver = DesktopModeLoggerTransitionObserver(
+ mockShellInit, transitions, desktopModeEventLogger)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ val initRunnableCaptor = ArgumentCaptor.forClass(
+ Runnable::class.java)
+ verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(),
+ same(transitionObserver))
+ initRunnableCaptor.value.run()
+ } else {
+ transitionObserver.onInit()
+ }
+ }
+
+ @Test
+ fun testRegistersObserverAtInit() {
+ verify(transitions)
+ .registerObserver(same(
+ transitionObserver))
+ }
+
+ @Test
+ fun taskCreated_notFreeformWindow_doesNotLogSessionEnterOrTaskAdded() {
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
+ verify(desktopModeEventLogger, never()).logTaskAdded(any(), any())
+ }
+
+ @Test
+ fun taskCreated_FreeformWindowOpen_logSessionEnterAndTaskAdded() {
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_FREEFORM_INTENT))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskChanged_taskMovedToDesktopByDrag_logSessionEnterAndTaskAdded() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ // task change is finalised when drag ends
+ val transitionInfo = TransitionInfoBuilder(
+ Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_HANDLE_DRAG))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskChanged_taskMovedToDesktopByButtonTap_logSessionEnterAndTaskAdded() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_MOVE_TO_DESKTOP, 0)
+ .addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_HANDLE_MENU_BUTTON))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskChanged_existingFreeformTaskMadeVisible_logSessionEnterAndTaskAdded() {
+ val taskInfo = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
+ taskInfo.isVisibleRequested = true
+ val change = createChange(TRANSIT_CHANGE, taskInfo)
+ val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_MOVE_TO_DESKTOP, 0)
+ .addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.APP_HANDLE_MENU_BUTTON))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun taskToFront_screenWake_logSessionStartedAndTaskAdded() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0)
+ .addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+ val sessionId = transitionObserver.getLoggerSessionId()
+
+ assertThat(sessionId).isNotNull()
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!),
+ eq(EnterReason.SCREEN_ON))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ }
+
+ @Test
+ fun freeformTaskVisible_screenTurnOff_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.SCREEN_OFF))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun freeformTaskVisible_exitDesktopUsingDrag_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // window mode changing from FREEFORM to FULLSCREEN
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_EXIT_DESKTOP_MODE)
+ .addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.DRAG_TO_EXIT))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun freeformTaskVisible_exitDesktopBySwipeUp_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // recents transition
+ val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun freeformTaskVisible_taskFinished_logSessionExitAndTaskRemoved_sessionIdNull() {
+ val sessionId = 1
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // task closing
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.TASK_FINISHED))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+ }
+
+ @Test
+ fun sessionExitByRecents_cancelledAnimation_sessionRestored() {
+ val sessionId = 1
+ // add a freeform task to an existing session
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // recents transition sent freeform window to back
+ val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ val transitionInfo1 =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change)
+ .build()
+ callOnTransitionReady(transitionInfo1)
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId),
+ eq(ExitReason.RETURN_HOME_OR_OVERVIEW))
+ assertThat(transitionObserver.getLoggerSessionId()).isNull()
+
+ val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
+ callOnTransitionReady(transitionInfo2)
+
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(any(), any())
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(any(), any())
+ }
+
+ @Test
+ fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() {
+ val sessionId = 1
+ // add an existing freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // new freeform task added
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any())
+ verify(desktopModeEventLogger, never()).logSessionEnter(any(), any())
+ }
+
+ @Test
+ fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() {
+ val sessionId = 1
+ // add two existing freeform tasks
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ transitionObserver.setLoggerSessionId(sessionId)
+
+ // new freeform task added
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(2, WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any())
+ verify(desktopModeEventLogger, never()).logSessionExit(any(), any())
+ }
+
+ /**
+ * Simulate calling the onTransitionReady() method
+ */
+ private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
+ val transition = mock(IBinder::class.java)
+ val startT = mock(
+ SurfaceControl.Transaction::class.java)
+ val finishT = mock(
+ SurfaceControl.Transaction::class.java)
+
+ transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
+ }
+
+ companion object {
+ fun createTaskInfo(taskId: Int, windowMode: Int): ActivityManager.RunningTaskInfo {
+ val taskInfo = ActivityManager.RunningTaskInfo()
+ taskInfo.taskId = taskId
+ taskInfo.configuration.windowConfiguration.windowingMode = windowMode
+
+ return taskInfo
+ }
+
+ fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change {
+ val change = Change(
+ WindowContainerToken(mock(
+ IWindowContainerToken::class.java)),
+ mock(SurfaceControl::class.java))
+ change.mode = mode
+ change.taskInfo = taskInfo
+ return change
+ }
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 0136751..5df9dd3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -87,6 +87,7 @@
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
+import org.mockito.kotlin.times
import org.mockito.Mockito.`when` as whenever
import org.mockito.quality.Strictness
@@ -113,6 +114,7 @@
@Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
@Mock lateinit var dragAndDropController: DragAndDropController
@Mock lateinit var multiInstanceHelper: MultiInstanceHelper
+ @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -163,6 +165,7 @@
mToggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler,
desktopModeTaskRepository,
+ desktopModeLoggerTransitionObserver,
launchAdjacentController,
recentsTransitionHandler,
multiInstanceHelper,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 3384509..d38fc6c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -129,7 +129,7 @@
}).when(mMockExecutor).execute(any());
mShellInit = spy(new ShellInit(mMockExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mMockShellCommandHandler,
- mMockExecutor));
+ mMockDisplayInsetsController, mMockExecutor));
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 10e9e11..41a4e8d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -58,6 +58,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -96,6 +97,8 @@
private DesktopModeTaskRepository mDesktopModeTaskRepository;
@Mock
private ActivityTaskManager mActivityTaskManager;
+ @Mock
+ private DisplayInsetsController mDisplayInsetsController;
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
@@ -110,7 +113,7 @@
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
mShellInit = spy(new ShellInit(mMainExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
- mMainExecutor));
+ mDisplayInsetsController, mMainExecutor));
mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit,
mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
Optional.of(mDesktopModeTaskRepository), mMainExecutor);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 315d97e..3c387f0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -123,7 +123,7 @@
assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
MockitoAnnotations.initMocks(this);
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
- mMainExecutor));
+ mDisplayInsetsController, mMainExecutor));
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index 012c4081..ff76a2f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -40,6 +40,7 @@
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -65,6 +66,7 @@
private @Mock Context mContext;
private @Mock DisplayManager mDisplayManager;
+ private @Mock DisplayInsetsController mDisplayInsetsController;
private @Mock ShellCommandHandler mShellCommandHandler;
private @Mock ShellTaskOrganizer mTaskOrganizer;
private @Mock ShellExecutor mMainExecutor;
@@ -83,7 +85,7 @@
doReturn(super.mContext.getResources()).when(mContext).getResources();
mShellInit = spy(new ShellInit(mMainExecutor));
mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
- mMainExecutor));
+ mDisplayInsetsController, mMainExecutor));
mController = new StartingWindowController(mContext, mShellInit, mShellController,
mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool);
mShellInit.init();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 7c520c3..6292018 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -35,8 +36,8 @@
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
-import com.android.wm.shell.common.ShellExecutor;
import org.junit.After;
import org.junit.Before;
@@ -63,12 +64,15 @@
private ShellCommandHandler mShellCommandHandler;
@Mock
private Context mTestUserContext;
+ @Mock
+ private DisplayInsetsController mDisplayInsetsController;
private TestShellExecutor mExecutor;
private ShellController mController;
private TestConfigurationChangeListener mConfigChangeListener;
private TestKeyguardChangeListener mKeyguardChangeListener;
private TestUserChangeListener mUserChangeListener;
+ private TestDisplayImeChangeListener mDisplayImeChangeListener;
@Before
@@ -77,8 +81,10 @@
mKeyguardChangeListener = new TestKeyguardChangeListener();
mConfigChangeListener = new TestConfigurationChangeListener();
mUserChangeListener = new TestUserChangeListener();
+ mDisplayImeChangeListener = new TestDisplayImeChangeListener();
mExecutor = new TestShellExecutor();
- mController = new ShellController(mContext, mShellInit, mShellCommandHandler, mExecutor);
+ mController = new ShellController(mContext, mShellInit, mShellCommandHandler,
+ mDisplayInsetsController, mExecutor);
mController.onConfigurationChanged(getConfigurationCopy());
}
@@ -130,6 +136,45 @@
}
@Test
+ public void testAddDisplayImeChangeListener_ensureCallback() {
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+
+ final Rect bounds = new Rect(10, 20, 30, 40);
+ mController.onImeBoundsChanged(bounds);
+ mController.onImeVisibilityChanged(true);
+ mExecutor.flushAll();
+
+ assertTrue(mDisplayImeChangeListener.boundsChanged == 1);
+ assertTrue(bounds.equals(mDisplayImeChangeListener.lastBounds));
+ assertTrue(mDisplayImeChangeListener.visibilityChanged == 1);
+ assertTrue(mDisplayImeChangeListener.lastVisibility);
+ }
+
+ @Test
+ public void testDoubleAddDisplayImeChangeListener_ensureSingleCallback() {
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+
+ mController.onImeVisibilityChanged(true);
+ mExecutor.flushAll();
+ assertTrue(mDisplayImeChangeListener.visibilityChanged == 1);
+ }
+
+ @Test
+ public void testAddRemoveDisplayImeChangeListener_ensureNoCallback() {
+ mController.asShell().addDisplayImeChangeListener(
+ mDisplayImeChangeListener, mExecutor);
+ mController.asShell().removeDisplayImeChangeListener(mDisplayImeChangeListener);
+
+ mController.onImeVisibilityChanged(true);
+ mExecutor.flushAll();
+ assertTrue(mDisplayImeChangeListener.visibilityChanged == 0);
+ }
+
+ @Test
public void testAddUserChangeListener_ensureCallback() {
mController.addUserChangeListener(mUserChangeListener);
@@ -457,4 +502,23 @@
lastUserProfiles = profiles;
}
}
+
+ private static class TestDisplayImeChangeListener implements DisplayImeChangeListener {
+ public int boundsChanged = 0;
+ public Rect lastBounds;
+ public int visibilityChanged = 0;
+ public boolean lastVisibility = false;
+
+ @Override
+ public void onImeBoundsChanged(int displayId, Rect bounds) {
+ boundsChanged++;
+ lastBounds = bounds;
+ }
+
+ @Override
+ public void onImeVisibilityChanged(int displayId, boolean isShowing) {
+ visibilityChanged++;
+ lastVisibility = isShowing;
+ }
+ }
}
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 156be38..f33bcb7 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -11,7 +11,7 @@
name: "location_bypass"
namespace: "location"
description: "Enable location bypass appops behavior"
- bug: "301150056"
+ bug: "329151785"
}
flag {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 6cf9c6f..bf39425 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -109,5 +109,5 @@
name: "enable_null_session_in_media_browser_service"
namespace: "media_solutions"
description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
- bug: "263520343"
+ bug: "185136506"
}
diff --git a/nfc/Android.bp b/nfc/Android.bp
index 7dd16ba..7698e2b 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -76,6 +76,9 @@
"//apex_available:platform",
"com.android.nfcservices",
],
+ aconfig_declarations: [
+ "android.nfc.flags-aconfig",
+ ],
}
filegroup {
diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
index 0fcec26..d0fee44 100644
--- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java
@@ -707,7 +707,7 @@
if (pm.getModuleInfo(packageName, 0) != null) {
return true;
}
- } catch (PackageManager.NameNotFoundException ignore) {
+ } catch (PackageManager.NameNotFoundException | IllegalStateException ignore) {
}
return isPersistentSystemApp(packageName);
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
similarity index 77%
rename from packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt
rename to packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
index 6aef24d..9cfdffd 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
@@ -18,34 +18,34 @@
import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
import platform.test.screenshot.PathConfig
/** The assets path to be used by all CredentialManager screenshot tests. */
private const val ASSETS_PREFIX = "frameworks/base/packages/CredentialManager"
private const val ASSETS_PATH = "${ASSETS_PREFIX}/tests/robotests/screenshot/customization/assets"
-private const val ASSETS_PATH_ROBO =
- "${ASSETS_PREFIX}/tests/robotests/customization/assets"
+private const val ASSETS_PATH_ROBO = "${ASSETS_PREFIX}/tests/robotests/customization/assets"
private val isRobolectric = Build.FINGERPRINT.contains("robolectric")
-class CredentialManagerGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String = if (isRobolectric) ASSETS_PATH_ROBO else ASSETS_PATH
-) : GoldenImagePathManager(
+class CredentialManagerGoldenPathManager(
+ pathConfig: PathConfig,
+ assetsPathRelativeToBuildRoot: String = if (isRobolectric) ASSETS_PATH_ROBO else ASSETS_PATH
+) :
+ GoldenPathManager(
appContext = InstrumentationRegistry.getInstrumentation().context,
assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
+ InstrumentationRegistry.getInstrumentation()
.targetContext
.filesDir
.absolutePath
.toString() + "/credman_screenshots",
pathConfig = pathConfig,
-) {
+ ) {
override fun toString(): String {
// This string is appended to all actual/expected screenshots on the device, so make sure
// it is a static value.
- return "CredentialManagerGoldenImagePathManager"
+ return "CredentialManagerGoldenPathManager"
}
-}
\ No newline at end of file
+}
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
index 28d83ee..b843213 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
@@ -60,7 +60,7 @@
@get:Rule
val screenshotRule = ComposeScreenshotTestRule(
emulationSpec,
- CredentialManagerGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+ CredentialManagerGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec))
)
@get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
index 0e39493..cfdcaff 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt
@@ -26,23 +26,10 @@
/** Manager of [BackupRestoreStorage]. */
class BackupRestoreStorageManager private constructor(private val application: Application) {
- private val storages = ConcurrentHashMap<String, BackupRestoreStorage>()
+ private val storageWrappers = ConcurrentHashMap<String, StorageWrapper>()
private val executor = MoreExecutors.directExecutor()
- private val observer = Observer { reason -> notifyBackupManager(null, reason) }
-
- private val keyedObserver =
- KeyedObserver<Any?> { key, reason -> notifyBackupManager(key, reason) }
-
- private fun notifyBackupManager(key: Any?, reason: Int) {
- // prefer not triggering backup immediately after restore
- if (reason == ChangeReason.RESTORE) return
- // TODO: log storage name
- Log.d(LOG_TAG, "Notify BackupManager data changed for change: key=$key")
- BackupManager.dataChanged(application.packageName)
- }
-
/**
* Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper].
*
@@ -52,7 +39,8 @@
*/
fun addBackupAgentHelpers(backupAgentHelper: BackupAgentHelper) {
val fileStorages = mutableListOf<BackupRestoreFileStorage>()
- for ((keyPrefix, storage) in storages) {
+ for ((keyPrefix, storageWrapper) in storageWrappers) {
+ val storage = storageWrapper.storage
if (storage is BackupRestoreFileStorage) {
fileStorages.add(storage)
} else {
@@ -70,15 +58,8 @@
* The observers of the storages will be notified.
*/
fun onRestoreFinished() {
- for (storage in storages.values) {
- storage.notifyRestoreFinished()
- }
- }
-
- private fun BackupRestoreStorage.notifyRestoreFinished() {
- when (this) {
- is KeyedObservable<*> -> notifyChange(ChangeReason.RESTORE)
- is Observable -> notifyChange(ChangeReason.RESTORE)
+ for (storageWrapper in storageWrappers.values) {
+ storageWrapper.notifyRestoreFinished()
}
}
@@ -99,50 +80,82 @@
fun add(storage: BackupRestoreStorage) {
if (storage is BackupRestoreFileStorage) storage.checkFilePaths()
val name = storage.name
- val oldStorage = storages.put(name, storage)
+ val oldStorage = storageWrappers.put(name, StorageWrapper(storage))?.storage
if (oldStorage != null) {
throw IllegalStateException(
"Storage name '$name' conflicts:\n\told: $oldStorage\n\tnew: $storage"
)
}
- storage.addObserver()
- }
-
- private fun BackupRestoreStorage.addObserver() {
- when (this) {
- is KeyedObservable<*> -> addObserver(keyedObserver, executor)
- is Observable -> addObserver(observer, executor)
- else ->
- throw IllegalArgumentException(
- "$this does not implement either KeyedObservable or Observable"
- )
- }
}
/** Removes all the storages. */
fun removeAll() {
- for ((name, _) in storages) remove(name)
+ for ((name, _) in storageWrappers) remove(name)
}
/** Removes storage with given name. */
fun remove(name: String): BackupRestoreStorage? {
- val storage = storages.remove(name)
- storage?.removeObserver()
- return storage
- }
-
- private fun BackupRestoreStorage.removeObserver() {
- when (this) {
- is KeyedObservable<*> -> removeObserver(keyedObserver)
- is Observable -> removeObserver(observer)
- }
+ val storageWrapper = storageWrappers.remove(name)
+ storageWrapper?.removeObserver()
+ return storageWrapper?.storage
}
/** Returns storage with given name. */
- fun get(name: String): BackupRestoreStorage? = storages[name]
+ fun get(name: String): BackupRestoreStorage? = storageWrappers[name]?.storage
/** Returns storage with given name, exception is raised if not found. */
- fun getOrThrow(name: String): BackupRestoreStorage = storages[name]!!
+ fun getOrThrow(name: String): BackupRestoreStorage = storageWrappers[name]!!.storage
+
+ private inner class StorageWrapper(val storage: BackupRestoreStorage) :
+ Observer, KeyedObserver<Any?> {
+ init {
+ when (storage) {
+ is KeyedObservable<*> -> storage.addObserver(this, executor)
+ is Observable -> storage.addObserver(this, executor)
+ else ->
+ throw IllegalArgumentException(
+ "$this does not implement either KeyedObservable or Observable"
+ )
+ }
+ }
+
+ override fun onChanged(reason: Int) = onKeyChanged(null, reason)
+
+ override fun onKeyChanged(key: Any?, reason: Int) {
+ notifyBackupManager(key, reason)
+ }
+
+ private fun notifyBackupManager(key: Any?, reason: Int) {
+ val name = storage.name
+ // prefer not triggering backup immediately after restore
+ if (reason == ChangeReason.RESTORE) {
+ Log.d(
+ LOG_TAG,
+ "Notify BackupManager dataChanged ignored for restore: storage=$name key=$key"
+ )
+ return
+ }
+ Log.d(
+ LOG_TAG,
+ "Notify BackupManager dataChanged: storage=$name key=$key reason=$reason"
+ )
+ BackupManager.dataChanged(application.packageName)
+ }
+
+ fun removeObserver() {
+ when (storage) {
+ is KeyedObservable<*> -> storage.removeObserver(this)
+ is Observable -> storage.removeObserver(this)
+ }
+ }
+
+ fun notifyRestoreFinished() {
+ when (storage) {
+ is KeyedObservable<*> -> storage.notifyChange(ChangeReason.RESTORE)
+ is Observable -> storage.notifyChange(ChangeReason.RESTORE)
+ }
+ }
+ }
companion object {
@Volatile private var instance: BackupRestoreStorageManager? = null
diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
index 18e8fc3..f47041d 100644
--- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
@@ -85,7 +85,7 @@
*/
@RequiresApi(Build.VERSION_CODES.M)
public static void sendShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
- final Intent intent = getShowAdminSupportDetailsIntent(context, admin);
+ final Intent intent = getShowAdminSupportDetailsIntent(admin);
int targetUserId = UserHandle.myUserId();
if (admin != null) {
if (admin.user != null
@@ -98,9 +98,16 @@
}
/**
- * Gets the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}.
+ * @deprecated No context needed. Use {@link #getShowAdminSupportDetailsIntent(EnforcedAdmin)}.
*/
public static Intent getShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
+ return getShowAdminSupportDetailsIntent(admin);
+ }
+
+ /**
+ * Gets the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}.
+ */
+ public static Intent getShowAdminSupportDetailsIntent(EnforcedAdmin admin) {
final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
if (admin != null) {
if (admin.component != null) {
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
similarity index 69%
rename from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
rename to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
index f5fba7f..d590760 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
@@ -17,28 +17,25 @@
package com.android.settingslib.spa.screenshot.util
import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
import platform.test.screenshot.PathConfig
-/** A [GoldenImagePathManager] that should be used for all Settings screenshot tests. */
-class SettingsGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String
-) :
- GoldenImagePathManager(
+/** A [GoldenPathManager] that should be used for all Settings screenshot tests. */
+class SettingsGoldenPathManager(pathConfig: PathConfig, assetsPathRelativeToBuildRoot: String) :
+ GoldenPathManager(
appContext = InstrumentationRegistry.getInstrumentation().context,
assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
- .targetContext
- .filesDir
- .absolutePath
- .toString() + "/settings_screenshots",
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/settings_screenshots",
pathConfig = pathConfig,
) {
override fun toString(): String {
// This string is appended to all actual/expected screenshots on the device, so make sure
// it is a static value.
- return "SettingsGoldenImagePathManager"
+ return "SettingsGoldenPathManager"
}
}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
index ae85675..16f6b5e 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
@@ -44,7 +44,7 @@
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- SettingsGoldenImagePathManager(
+ SettingsGoldenPathManager(
getEmulatedDevicePathConfig(emulationSpec),
assetsPathRelativeToBuildRoot
)
diff --git a/packages/SettingsLib/res/layout/dialog_with_icon.xml b/packages/SettingsLib/res/layout/dialog_with_icon.xml
index 3586dcb..b21895b 100644
--- a/packages/SettingsLib/res/layout/dialog_with_icon.xml
+++ b/packages/SettingsLib/res/layout/dialog_with_icon.xml
@@ -35,12 +35,14 @@
android:id="@+id/dialog_with_icon_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:hyphenationFrequency="fullFast"
android:gravity="center"
style="@style/DialogWithIconTitle"/>
<TextView
android:id="@+id/dialog_with_icon_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:hyphenationFrequency="fullFast"
android:gravity="center"
style="@style/TextAppearanceSmall"/>
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
index 840c936..b7108c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileMappings.java
@@ -236,7 +236,8 @@
// Handle specific carrier config values for the default data SIM
int defaultDataSubId = SubscriptionManager.from(context)
.getDefaultDataSubscriptionId();
- PersistableBundle b = configMgr.getConfigForSubId(defaultDataSubId);
+ PersistableBundle b = configMgr == null ? null
+ : configMgr.getConfigForSubId(defaultDataSubId);
if (b != null) {
config.alwaysShowDataRatIcon = b.getBoolean(
CarrierConfigManager.KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL);
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
index 53daef1..69c7410 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -242,6 +242,7 @@
.setMessage(messageResId)
.setNegativeButtonText(R.string.cancel)
.setPositiveButtonText(R.string.next);
+ mCustomDialogHelper.requestFocusOnTitle();
break;
case GRANT_ADMIN_DIALOG:
mEditUserInfoView.setVisibility(View.GONE);
@@ -254,6 +255,7 @@
.setMessage(R.string.user_grant_admin_message)
.setNegativeButtonText(R.string.back)
.setPositiveButtonText(R.string.next);
+ mCustomDialogHelper.requestFocusOnTitle();
if (mIsAdmin == null) {
mCustomDialogHelper.setButtonEnabled(false);
}
@@ -265,6 +267,7 @@
.setTitle(R.string.user_info_settings_title)
.setNegativeButtonText(R.string.back)
.setPositiveButtonText(R.string.done);
+ mCustomDialogHelper.requestFocusOnTitle();
mEditUserInfoView.setVisibility(View.VISIBLE);
mGrantAdminView.setVisibility(View.GONE);
break;
@@ -273,7 +276,6 @@
&& mEditUserPhotoController.getNewUserPhotoDrawable() != null)
? mEditUserPhotoController.getNewUserPhotoDrawable()
: mSavedDrawable;
-
String newName = mUserNameView.getText().toString().trim();
String defaultName = mActivity.getString(R.string.user_new_user_name);
mUserName = !newName.isEmpty() ? newName : defaultName;
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
index 5201b3d..4cf3bc2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java
@@ -23,6 +23,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -282,4 +283,13 @@
}
return this;
}
+
+ /**
+ * Requests focus on dialog title when used. Used to let talkback know that the dialog content
+ * is updated and needs to be read from the beginning.
+ */
+ public void requestFocusOnTitle() {
+ mDialogTitle.requestFocus();
+ mDialogTitle.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 56b0bf7..1586b8f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -23,8 +23,8 @@
import com.android.settingslib.volume.shared.model.AudioStreamModel
import com.android.settingslib.volume.shared.model.RingerMode
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** Provides audio stream state and an ability to change it */
@@ -43,6 +43,9 @@
streamModel.copy(volume = processVolume(streamModel, ringerMode, isZenMuted))
}
+ val ringerMode: StateFlow<RingerMode>
+ get() = audioRepository.ringerMode
+
suspend fun setVolume(audioStream: AudioStream, volume: Int) =
audioRepository.setVolume(audioStream, volume)
@@ -52,9 +55,14 @@
/** Checks if the volume can be changed via the UI. */
fun canChangeVolume(audioStream: AudioStream): Flow<Boolean> {
return if (audioStream.value == AudioManager.STREAM_NOTIFICATION) {
- getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { !it.isMuted }
+ combine(
+ notificationsSoundPolicyInteractor.isZenMuted(audioStream),
+ getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { it.isMuted },
+ ) { isZenMuted, isRingMuted ->
+ !isZenMuted && !isRingMuted
+ }
} else {
- flowOf(true)
+ notificationsSoundPolicyInteractor.isZenMuted(audioStream).map { !it }
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 30d5d4b..eaec617 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -269,6 +269,7 @@
Settings.Secure.STYLUS_POINTER_ICON_ENABLED,
Settings.Secure.CAMERA_EXTENSIONS_FALLBACK,
Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED,
- Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS
+ Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+ Settings.Secure.AUDIO_DEVICE_INVENTORY
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 893932f..046d6e2 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -424,5 +424,6 @@
VALIDATORS.put(Secure.STYLUS_POINTER_ICON_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CAMERA_EXTENSIONS_FALLBACK, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.IMMERSIVE_MODE_CONFIRMATIONS, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.AUDIO_DEVICE_INVENTORY, ANY_STRING_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 46c89900..6eb2dd0 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -670,7 +670,6 @@
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED,
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
Settings.Secure.AUTOMATIC_STORAGE_MANAGER_TURNED_OFF_BY_POLICY,
- Settings.Secure.AUDIO_DEVICE_INVENTORY, // not controllable by user
Settings.Secure.AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED, // not controllable by user
Settings.Secure.BACKUP_AUTO_RESTORE,
Settings.Secure.BACKUP_ENABLED,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 98591e9..e62c77dc 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -179,6 +179,7 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
+ <uses-permission android:name="android.permission.RECORD_SENSITIVE_CONTENT"/>
<!-- Assist -->
<uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index da06830..85bdb29 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -617,3 +617,14 @@
description: "enables new focus outline for qs tiles when focused on with physical keyboard"
bug: "312899524"
}
+
+flag {
+ name: "edgeback_gesture_handler_get_running_tasks_background"
+ namespace: "systemui"
+ description: "Decide whether to get the running tasks from activity manager in EdgebackGestureHandler"
+ " class on the background thread."
+ bug: "325041960"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index 5d5f12e..3f57f88 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -337,6 +337,7 @@
if (ghostedView is LaunchableView) {
// Restore the ghosted view visibility.
ghostedView.setShouldBlockVisibilityChanges(false)
+ ghostedView.onActivityLaunchAnimationEnd()
} else {
// Make the ghosted view visible. We ensure that the view is considered VISIBLE by
// accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
index ed8e705..da6ccaa 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
@@ -38,6 +38,9 @@
* @param block whether we should block/postpone all calls to `setVisibility`.
*/
fun setShouldBlockVisibilityChanges(block: Boolean)
+
+ /** Perform an action when the activity launch animation ends */
+ fun onActivityLaunchAnimationEnd() {}
}
/** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
index ef15c84..9a99649 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
@@ -21,22 +21,25 @@
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.DragInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
-import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -44,17 +47,28 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
-import com.android.compose.modifiers.padding
+import androidx.compose.ui.util.fastFirst
+import androidx.compose.ui.util.fastFirstOrNull
import com.android.compose.theme.LocalAndroidColorScheme
/**
@@ -62,15 +76,16 @@
*
* @param onValueChangeFinished is called when the slider settles on a [value]. This callback
* shouldn't be used to react to value changes. Use [onValueChange] instead
- * @param interactionSource - the [MutableInteractionSource] representing the stream of Interactions
+ * @param interactionSource the [MutableInteractionSource] representing the stream of Interactions
* for this slider. You can create and pass in your own remembered instance to observe
* Interactions and customize the appearance / behavior of this slider in different states.
- * @param colors - slider color scheme.
- * @param draggingCornersRadius - radius of the slider indicator when the user drags it
- * @param icon - icon at the start of the slider. Icon is limited to a square space at the start of
- * the slider
- * @param label - control shown next to the icon.
+ * @param colors determine slider color scheme.
+ * @param draggingCornersRadius is the radius of the slider indicator when the user drags it
+ * @param icon at the start of the slider. Icon is limited to a square space at the start of the
+ * slider
+ * @param label is shown next to the icon.
*/
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlatformSlider(
value: Float,
@@ -86,7 +101,7 @@
label: (@Composable (isDragging: Boolean) -> Unit)? = null,
) {
val sliderHeight: Dp = 64.dp
- val iconWidth: Dp = sliderHeight
+ val thumbSize: Dp = sliderHeight
var isDragging by remember { mutableStateOf(false) }
LaunchedEffect(interactionSource) {
interactionSource.interactions.collect { interaction ->
@@ -101,16 +116,6 @@
}
}
}
- val paddingStart by
- animateDpAsState(
- targetValue =
- if ((!isDragging && value == valueRange.start) || icon == null) {
- 16.dp
- } else {
- 0.dp
- },
- label = "LabelIconSpacingAnimation"
- )
Box(modifier = modifier.height(sliderHeight)) {
Slider(
@@ -126,130 +131,275 @@
sliderState = it,
enabled = enabled,
colors = colors,
- iconWidth = iconWidth,
draggingCornersRadius = draggingCornersRadius,
sliderHeight = sliderHeight,
+ thumbSize = thumbSize,
isDragging = isDragging,
- modifier = Modifier,
+ label = label,
+ icon = icon,
+ modifier = Modifier.fillMaxSize(),
)
},
- thumb = { Spacer(Modifier.width(iconWidth).height(sliderHeight)) },
+ thumb = { Spacer(Modifier.size(thumbSize)) },
)
- if (icon != null || label != null) {
- Row(modifier = Modifier.fillMaxSize()) {
- icon?.let { iconComposable ->
- Box(
- modifier = Modifier.fillMaxHeight().aspectRatio(1f),
- contentAlignment = Alignment.Center,
- ) {
- iconComposable(isDragging)
- }
- }
-
- label?.let { labelComposable ->
- Box(
- modifier =
- Modifier.fillMaxHeight()
- .weight(1f)
- .padding(
- start = { paddingStart.roundToPx() },
- end = { sliderHeight.roundToPx() / 2 },
- ),
- contentAlignment = Alignment.CenterStart,
- ) {
- labelComposable(isDragging)
- }
- }
- }
- }
+ Spacer(
+ Modifier.padding(8.dp)
+ .size(4.dp)
+ .align(Alignment.CenterEnd)
+ .background(color = colors.indicatorColor, shape = CircleShape)
+ )
}
}
+private enum class TrackComponent(val zIndex: Float) {
+ Background(0f),
+ Icon(1f),
+ Label(1f),
+}
+
@Composable
private fun Track(
sliderState: SliderState,
enabled: Boolean,
colors: PlatformSliderColors,
- iconWidth: Dp,
draggingCornersRadius: Dp,
sliderHeight: Dp,
+ thumbSize: Dp,
isDragging: Boolean,
+ icon: (@Composable (isDragging: Boolean) -> Unit)?,
+ label: (@Composable (isDragging: Boolean) -> Unit)?,
modifier: Modifier = Modifier,
) {
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
- val iconWidthPx: Float
- val halfIconWidthPx: Float
- val targetIndicatorRadiusPx: Float
- val halfSliderHeightPx: Float
- with(LocalDensity.current) {
- halfSliderHeightPx = sliderHeight.toPx() / 2
- iconWidthPx = iconWidth.toPx()
- halfIconWidthPx = iconWidthPx / 2
- targetIndicatorRadiusPx =
- if (isDragging) draggingCornersRadius.toPx() else halfSliderHeightPx
- }
+ var drawingState: DrawingState by remember { mutableStateOf(DrawingState()) }
+ Layout(
+ modifier = modifier,
+ content = {
+ TrackBackground(
+ modifier = Modifier.layoutId(TrackComponent.Background),
+ drawingState = drawingState,
+ enabled = enabled,
+ colors = colors,
+ draggingCornersRadiusActive = draggingCornersRadius,
+ draggingCornersRadiusIdle = sliderHeight / 2,
+ isDragging = isDragging,
+ )
+ if (icon != null) {
+ Box(
+ modifier = Modifier.layoutId(TrackComponent.Icon).clip(CircleShape),
+ contentAlignment = Alignment.Center,
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides
+ if (enabled) colors.iconColor else colors.disabledIconColor
+ ) {
+ icon(isDragging)
+ }
+ }
+ }
+ if (label != null) {
+ val offsetX by
+ animateFloatAsState(
+ targetValue =
+ if (enabled) {
+ if (drawingState.isLabelOnTopOfIndicator) {
+ drawingState.iconWidth.coerceAtLeast(
+ LocalDensity.current.run { 16.dp.toPx() }
+ )
+ } else {
+ val indicatorWidth =
+ drawingState.indicatorRight - drawingState.indicatorLeft
+ indicatorWidth + LocalDensity.current.run { 16.dp.toPx() }
+ }
+ } else {
+ drawingState.iconWidth
+ },
+ label = "LabelIconSpacingAnimation"
+ )
+ Box(
+ modifier =
+ Modifier.layoutId(TrackComponent.Label).offset {
+ IntOffset(offsetX.toInt(), 0)
+ },
+ contentAlignment = Alignment.CenterStart,
+ ) {
+ CompositionLocalProvider(
+ LocalContentColor provides
+ colors.getLabelColor(
+ isEnabled = enabled,
+ isLabelOnTopOfTheIndicator = drawingState.isLabelOnTopOfIndicator,
+ )
+ ) {
+ label(isDragging)
+ }
+ }
+ }
+ },
+ measurePolicy =
+ TrackMeasurePolicy(
+ sliderState = sliderState,
+ thumbSize = LocalDensity.current.run { thumbSize.roundToPx() },
+ isRtl = isRtl,
+ onDrawingStateMeasured = { drawingState = it }
+ )
+ )
+}
- val indicatorRadiusPx: Float by
- animateFloatAsState(
- targetValue = targetIndicatorRadiusPx,
+@Composable
+private fun TrackBackground(
+ drawingState: DrawingState,
+ enabled: Boolean,
+ colors: PlatformSliderColors,
+ draggingCornersRadiusActive: Dp,
+ draggingCornersRadiusIdle: Dp,
+ isDragging: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ val indicatorRadiusDp: Dp by
+ animateDpAsState(
+ targetValue =
+ if (isDragging) draggingCornersRadiusActive else draggingCornersRadiusIdle,
label = "PlatformSliderCornersAnimation",
)
val trackColor = colors.getTrackColor(enabled)
val indicatorColor = colors.getIndicatorColor(enabled)
- val trackCornerRadius = CornerRadius(halfSliderHeightPx, halfSliderHeightPx)
- val indicatorCornerRadius = CornerRadius(indicatorRadiusPx, indicatorRadiusPx)
Canvas(modifier.fillMaxSize()) {
+ val trackCornerRadius = CornerRadius(size.height / 2, size.height / 2)
val trackPath = Path()
trackPath.addRoundRect(
RoundRect(
- left = -halfIconWidthPx,
+ left = 0f,
top = 0f,
- right = size.width + halfIconWidthPx,
- bottom = size.height,
+ right = drawingState.totalWidth,
+ bottom = drawingState.totalHeight,
cornerRadius = trackCornerRadius,
)
)
drawPath(path = trackPath, color = trackColor)
+ val indicatorCornerRadius = CornerRadius(indicatorRadiusDp.toPx(), indicatorRadiusDp.toPx())
clipPath(trackPath) {
val indicatorPath = Path()
- if (isRtl) {
- indicatorPath.addRoundRect(
- RoundRect(
- left =
- size.width -
- size.width * sliderState.coercedNormalizedValue -
- halfIconWidthPx,
- top = 0f,
- right = size.width + iconWidthPx,
- bottom = size.height,
- topLeftCornerRadius = indicatorCornerRadius,
- topRightCornerRadius = trackCornerRadius,
- bottomRightCornerRadius = trackCornerRadius,
- bottomLeftCornerRadius = indicatorCornerRadius,
- )
+ indicatorPath.addRoundRect(
+ RoundRect(
+ left = drawingState.indicatorLeft,
+ top = drawingState.indicatorTop,
+ right = drawingState.indicatorRight,
+ bottom = drawingState.indicatorBottom,
+ topLeftCornerRadius = trackCornerRadius,
+ topRightCornerRadius = indicatorCornerRadius,
+ bottomRightCornerRadius = indicatorCornerRadius,
+ bottomLeftCornerRadius = trackCornerRadius,
)
- } else {
- indicatorPath.addRoundRect(
- RoundRect(
- left = -halfIconWidthPx,
- top = 0f,
- right = size.width * sliderState.coercedNormalizedValue + halfIconWidthPx,
- bottom = size.height,
- topLeftCornerRadius = trackCornerRadius,
- topRightCornerRadius = indicatorCornerRadius,
- bottomRightCornerRadius = indicatorCornerRadius,
- bottomLeftCornerRadius = trackCornerRadius,
- )
- )
- }
+ )
drawPath(path = indicatorPath, color = indicatorColor)
}
}
}
+/** Measures track components sizes and calls [onDrawingStateMeasured] when it's done. */
+private class TrackMeasurePolicy(
+ private val sliderState: SliderState,
+ private val thumbSize: Int,
+ private val isRtl: Boolean,
+ private val onDrawingStateMeasured: (DrawingState) -> Unit,
+) : MeasurePolicy {
+
+ override fun MeasureScope.measure(
+ measurables: List<Measurable>,
+ constraints: Constraints
+ ): MeasureResult {
+ // Slider adds a paddings to the Track to make spase for thumb
+ val desiredWidth = constraints.maxWidth + thumbSize
+ val desiredHeight = constraints.maxHeight
+ val backgroundPlaceable: Placeable =
+ measurables
+ .fastFirst { it.layoutId == TrackComponent.Background }
+ .measure(Constraints(desiredWidth, desiredWidth, desiredHeight, desiredHeight))
+
+ val iconPlaceable: Placeable? =
+ measurables
+ .fastFirstOrNull { it.layoutId == TrackComponent.Icon }
+ ?.measure(
+ Constraints(
+ minWidth = desiredHeight,
+ maxWidth = desiredHeight,
+ minHeight = desiredHeight,
+ maxHeight = desiredHeight,
+ )
+ )
+
+ val iconSize = iconPlaceable?.width ?: 0
+ val labelMaxWidth = (desiredWidth - iconSize) / 2
+ val labelPlaceable: Placeable? =
+ measurables
+ .fastFirstOrNull { it.layoutId == TrackComponent.Label }
+ ?.measure(
+ Constraints(
+ minWidth = 0,
+ maxWidth = labelMaxWidth,
+ minHeight = desiredHeight,
+ maxHeight = desiredHeight,
+ )
+ )
+
+ val drawingState =
+ if (isRtl) {
+ DrawingState(
+ isRtl = true,
+ totalWidth = desiredWidth.toFloat(),
+ totalHeight = desiredHeight.toFloat(),
+ indicatorLeft =
+ (desiredWidth - iconSize) * (1 - sliderState.coercedNormalizedValue),
+ indicatorTop = 0f,
+ indicatorRight = desiredWidth.toFloat(),
+ indicatorBottom = desiredHeight.toFloat(),
+ iconWidth = iconSize.toFloat(),
+ labelWidth = labelPlaceable?.width?.toFloat() ?: 0f,
+ )
+ } else {
+ DrawingState(
+ isRtl = false,
+ totalWidth = desiredWidth.toFloat(),
+ totalHeight = desiredHeight.toFloat(),
+ indicatorLeft = 0f,
+ indicatorTop = 0f,
+ indicatorRight =
+ iconSize + (desiredWidth - iconSize) * sliderState.coercedNormalizedValue,
+ indicatorBottom = desiredHeight.toFloat(),
+ iconWidth = iconSize.toFloat(),
+ labelWidth = labelPlaceable?.width?.toFloat() ?: 0f,
+ )
+ }
+
+ onDrawingStateMeasured(drawingState)
+
+ return layout(desiredWidth, desiredHeight) {
+ backgroundPlaceable.placeRelative(0, 0, TrackComponent.Background.zIndex)
+
+ iconPlaceable?.placeRelative(0, 0, TrackComponent.Icon.zIndex)
+ labelPlaceable?.placeRelative(0, 0, TrackComponent.Label.zIndex)
+ }
+ }
+}
+
+private data class DrawingState(
+ val isRtl: Boolean = false,
+ val totalWidth: Float = 0f,
+ val totalHeight: Float = 0f,
+ val indicatorLeft: Float = 0f,
+ val indicatorTop: Float = 0f,
+ val indicatorRight: Float = 0f,
+ val indicatorBottom: Float = 0f,
+ val iconWidth: Float = 0f,
+ val labelWidth: Float = 0f,
+)
+
+private val DrawingState.isLabelOnTopOfIndicator: Boolean
+ get() = labelWidth < indicatorRight - indicatorLeft - iconWidth
+
/** [SliderState.value] normalized using [SliderState.valueRange]. The result belongs to [0, 1] */
private val SliderState.coercedNormalizedValue: Float
get() {
@@ -268,17 +418,19 @@
* @param trackColor fills the track of the slider. This is a "background" of the slider
* @param indicatorColor fills the slider from the start to the value
* @param iconColor is the default icon color
- * @param labelColor is the default icon color
+ * @param labelColorOnIndicator is the label color for when it's shown on top of the indicator
+ * @param labelColorOnTrack is the label color for when it's shown on top of the track
* @param disabledTrackColor is the [trackColor] when the PlatformSlider#enabled == false
* @param disabledIndicatorColor is the [indicatorColor] when the PlatformSlider#enabled == false
* @param disabledIconColor is the [iconColor] when the PlatformSlider#enabled == false
- * @param disabledLabelColor is the [labelColor] when the PlatformSlider#enabled == false
+ * @param disabledLabelColor is the label color when the PlatformSlider#enabled == false
*/
data class PlatformSliderColors(
val trackColor: Color,
val indicatorColor: Color,
val iconColor: Color,
- val labelColor: Color,
+ val labelColorOnIndicator: Color,
+ val labelColorOnTrack: Color,
val disabledTrackColor: Color,
val disabledIndicatorColor: Color,
val disabledIconColor: Color,
@@ -300,10 +452,11 @@
@Composable
private fun lightThemePlatformSliderColors() =
PlatformSliderColors(
- trackColor = MaterialTheme.colorScheme.tertiaryContainer,
- indicatorColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
- iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
- labelColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ trackColor = LocalAndroidColorScheme.current.tertiaryFixedDim,
+ indicatorColor = MaterialTheme.colorScheme.tertiary,
+ iconColor = MaterialTheme.colorScheme.onTertiary,
+ labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
+ labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -314,10 +467,11 @@
@Composable
private fun darkThemePlatformSliderColors() =
PlatformSliderColors(
- trackColor = MaterialTheme.colorScheme.onTertiary,
- indicatorColor = LocalAndroidColorScheme.current.onTertiaryFixedVariant,
+ trackColor = MaterialTheme.colorScheme.tertiary,
+ indicatorColor = MaterialTheme.colorScheme.tertiary,
iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
- labelColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary,
+ labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed,
disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest,
disabledIconColor = MaterialTheme.colorScheme.outline,
@@ -329,3 +483,14 @@
private fun PlatformSliderColors.getIndicatorColor(isEnabled: Boolean): Color =
if (isEnabled) indicatorColor else disabledIndicatorColor
+
+private fun PlatformSliderColors.getLabelColor(
+ isEnabled: Boolean,
+ isLabelOnTopOfTheIndicator: Boolean
+): Color {
+ return if (isEnabled) {
+ if (isLabelOnTopOfTheIndicator) labelColorOnIndicator else labelColorOnTrack
+ } else {
+ disabledLabelColor
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 96520b2..7acb4d5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -19,61 +19,31 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import com.android.compose.animation.scene.Edge
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
/** The lock screen scene shows when the device is locked. */
@SysUISingleton
class LockscreenScene
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
viewModel: LockscreenSceneViewModel,
private val lockscreenContent: Lazy<LockscreenContent>,
) : ComposableScene {
override val key = Scenes.Lockscreen
override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- combine(
- viewModel.upDestinationSceneKey,
- viewModel.leftDestinationSceneKey,
- viewModel.downFromTopEdgeDestinationSceneKey,
- ) { upKey, leftKey, downFromTopEdgeKey ->
- destinationScenes(
- up = upKey,
- left = leftKey,
- downFromTopEdge = downFromTopEdgeKey,
- )
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue =
- destinationScenes(
- up = viewModel.upDestinationSceneKey.value,
- left = viewModel.leftDestinationSceneKey.value,
- downFromTopEdge = viewModel.downFromTopEdgeDestinationSceneKey.value,
- )
- )
+ viewModel.destinationScenes
@Composable
override fun SceneScope.Content(
@@ -84,22 +54,6 @@
modifier = modifier,
)
}
-
- private fun destinationScenes(
- up: SceneKey?,
- left: SceneKey?,
- downFromTopEdge: SceneKey?,
- ): Map<UserAction, UserActionResult> {
- return buildMap {
- up?.let { this[Swipe(SwipeDirection.Up)] = UserActionResult(up) }
- left?.let { this[Swipe(SwipeDirection.Left)] = UserActionResult(left) }
- downFromTopEdge?.let {
- this[Swipe(fromSource = Edge.Top, direction = SwipeDirection.Down)] =
- UserActionResult(downFromTopEdge)
- }
- this[Swipe(direction = SwipeDirection.Down)] = UserActionResult(Scenes.Shade)
- }
- }
}
@Composable
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 51464d0..31201c2f 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
@@ -373,7 +373,6 @@
) {
if (viewModel.isMediaVisible()) {
val density = LocalDensity.current
- val layoutWidth = remember { mutableStateOf(0) }
val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded)
MediaCarousel(
@@ -389,7 +388,7 @@
layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) }
},
mediaHost = mediaHost,
- layoutWidth = layoutWidth.value,
+ layoutWidth = 0,
layoutHeight = with(density) { mediaHeight.toPx() }.toInt(),
carouselController = mediaCarouselController,
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index 8ac84ff..b1fbe35 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.anc.ui.composable
import android.content.Context
+import android.view.ContextThemeWrapper
import android.view.View
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
@@ -73,15 +74,16 @@
AndroidView<SliceView>(
modifier = Modifier.fillMaxWidth(),
factory = { context: Context ->
- SliceView(context).apply {
- mode = SliceView.MODE_LARGE
- isScrollable = false
- importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
- setShowTitleItems(true)
- addOnLayoutChangeListener(
- OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
- )
- }
+ SliceView(ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel))
+ .apply {
+ mode = SliceView.MODE_LARGE
+ isScrollable = false
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ setShowTitleItems(true)
+ addOnLayoutChangeListener(
+ OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
+ )
+ }
},
update = { sliceView: SliceView -> sliceView.slice = slice }
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 4d810df..81d2da0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -42,9 +42,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
@@ -61,12 +58,13 @@
@Composable
fun ColumnVolumeSliders(
viewModels: List<SliderViewModel>,
+ isExpanded: Boolean,
+ onExpandedChanged: (Boolean) -> Unit,
sliderColors: PlatformSliderColors,
isExpandable: Boolean,
modifier: Modifier = Modifier,
) {
require(viewModels.isNotEmpty())
- var isExpanded: Boolean by remember(isExpandable) { mutableStateOf(!isExpandable) }
val transition = updateTransition(isExpanded, label = "CollapsableSliders")
Column(modifier = modifier) {
Row(
@@ -85,7 +83,7 @@
if (isExpandable) {
ExpandButton(
isExpanded = isExpanded,
- onExpandedChanged = { isExpanded = it },
+ onExpandedChanged = onExpandedChanged,
sliderColors,
Modifier,
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 18a62dc..3e0aee5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -22,6 +22,7 @@
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -30,6 +31,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSlider
import com.android.compose.PlatformSliderColors
import com.android.systemui.common.ui.compose.Icon
@@ -54,7 +56,7 @@
if (isDragging) {
Text(text = value.toInt().toString())
} else {
- state.icon?.let { Icon(icon = it) }
+ state.icon?.let { Icon(modifier = Modifier.size(24.dp), icon = it) }
}
},
colors = sliderColors,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
index 1ca18de..fdf8ee8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
@@ -48,8 +48,11 @@
modifier = modifier.fillMaxWidth(),
)
} else {
+ val isExpanded by viewModel.isExpanded.collectAsState()
ColumnVolumeSliders(
viewModels = sliderViewModels,
+ isExpanded = isExpanded,
+ onExpandedChanged = viewModel::onExpandedChanged,
sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(),
isExpandable = isPortrait,
modifier = modifier.fillMaxWidth(),
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 6114499..63ec54f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -31,6 +31,7 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
+import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
@@ -353,10 +354,7 @@
// If the swipe was not committed or if the swipe distance is not computed yet, don't do
// anything.
- if (
- swipeTransition._currentScene != toScene ||
- distance == SwipeTransition.DistanceUnspecified
- ) {
+ if (swipeTransition._currentScene != toScene || distance == DistanceUnspecified) {
return fromScene to 0f
}
@@ -418,7 +416,7 @@
var targetScene: Scene
var targetOffset: Float
if (
- distance != SwipeTransition.DistanceUnspecified &&
+ distance != DistanceUnspecified &&
shouldCommitSwipe(
offset,
distance,
@@ -444,8 +442,8 @@
if (targetScene == fromScene) {
0f
} else {
- check(distance != SwipeTransition.DistanceUnspecified) {
- "distance is equal to ${SwipeTransition.DistanceUnspecified}"
+ check(distance != DistanceUnspecified) {
+ "distance is equal to $DistanceUnspecified"
}
distance
}
@@ -628,6 +626,12 @@
/** The spec to use when animating this transition to either [fromScene] or [toScene]. */
lateinit var swipeSpec: SpringSpec<Float>
+ override val overscrollScope: OverscrollScope =
+ object : OverscrollScope {
+ override val absoluteDistance: Float
+ get() = distance().absoluteValue
+ }
+
private var lastDistance = DistanceUnspecified
/** Whether [TransitionState.Transition.finish] was called on this transition. */
@@ -753,10 +757,6 @@
/** The job in which [animatable] is animated. */
val job: Job,
)
-
- companion object {
- const val DistanceUnspecified = 0f
- }
}
private object DefaultSwipeDistance : UserActionDistance {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 86124df..e6f5d58 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -236,19 +236,28 @@
interface HasOverscrollProperties {
/**
- * The position of the [TransitionState.Transition.toScene].
+ * The position of the [Transition.toScene].
*
* Used to understand the direction of the overscroll.
*/
val isUpOrLeft: Boolean
/**
- * The relative orientation between [TransitionState.Transition.fromScene] and
- * [TransitionState.Transition.toScene].
+ * The relative orientation between [Transition.fromScene] and [Transition.toScene].
*
* Used to understand the orientation of the overscroll.
*/
val orientation: Orientation
+
+ /**
+ * Scope which can be used in the Overscroll DSL to define a transformation based on the
+ * distance between [Transition.fromScene] and [Transition.toScene].
+ */
+ val overscrollScope: OverscrollScope
+
+ companion object {
+ const val DistanceUnspecified = 0f
+ }
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 2dd41cd..b466143 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -30,6 +30,7 @@
import com.android.compose.animation.scene.transformation.DrawScale
import com.android.compose.animation.scene.transformation.EdgeTranslate
import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
import com.android.compose.animation.scene.transformation.ScaleSize
@@ -124,7 +125,7 @@
overscrollSpecs.fastForEach { spec ->
if (spec.orientation == orientation && filter(spec)) {
if (match != null) {
- error("Found multiple transition specs for transition $scene")
+ error("Found multiple overscroll specs for overscroll $scene")
}
match = spec
}
@@ -297,6 +298,7 @@
) {
when (current) {
is Translate,
+ is OverscrollTranslate,
is EdgeTranslate,
is AnchoredTranslate -> {
throwIfNotNull(offset, element, name = "offset")
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index bc52a28..2c109a3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -22,6 +22,7 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
/** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */
fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
@@ -88,8 +89,7 @@
): OverscrollSpec
}
-@TransitionDsl
-interface OverscrollBuilder : PropertyTransformationBuilder {
+interface BaseTransitionBuilder : PropertyTransformationBuilder {
/**
* The distance it takes for this transition to animate from 0% to 100% when it is driven by a
* [UserAction].
@@ -120,7 +120,7 @@
}
@TransitionDsl
-interface TransitionBuilder : OverscrollBuilder, PropertyTransformationBuilder {
+interface TransitionBuilder : BaseTransitionBuilder {
/**
* The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
* the transition is triggered (i.e. it is not gesture-based).
@@ -176,6 +176,24 @@
fun reversed(builder: TransitionBuilder.() -> Unit)
}
+@TransitionDsl
+interface OverscrollBuilder : BaseTransitionBuilder {
+ /** Translate the element(s) matching [matcher] by ([x], [y]) pixels. */
+ fun translate(
+ matcher: ElementMatcher,
+ x: OverscrollScope.() -> Float = { 0f },
+ y: OverscrollScope.() -> Float = { 0f },
+ )
+}
+
+interface OverscrollScope {
+ /**
+ * Return the absolute distance between fromScene and toScene, if available, otherwise
+ * [DistanceUnspecified].
+ */
+ val absoluteDistance: Float
+}
+
/**
* An interface to decide where we should draw shared Elements or compose MovableElements.
*
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 65e8ea5..1c9080f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -31,6 +31,7 @@
import com.android.compose.animation.scene.transformation.DrawScale
import com.android.compose.animation.scene.transformation.EdgeTranslate
import com.android.compose.animation.scene.transformation.Fade
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
import com.android.compose.animation.scene.transformation.ScaleSize
@@ -114,7 +115,7 @@
}
}
-internal open class OverscrollBuilderImpl : OverscrollBuilder {
+internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
val transformations = mutableListOf<Transformation>()
private var range: TransformationRange? = null
protected var reversed = false
@@ -130,7 +131,7 @@
range = null
}
- private fun transformation(transformation: PropertyTransformation<*>) {
+ protected fun transformation(transformation: PropertyTransformation<*>) {
val transformation =
if (range != null) {
RangedPropertyTransformation(transformation, range!!)
@@ -185,7 +186,7 @@
}
}
-internal class TransitionBuilderImpl : OverscrollBuilderImpl(), TransitionBuilder {
+internal class TransitionBuilderImpl : BaseTransitionBuilderImpl(), TransitionBuilder {
override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
override var swipeSpec: SpringSpec<Float>? = null
override var distance: UserActionDistance? = null
@@ -226,3 +227,13 @@
fractionRange(start, end, builder)
}
}
+
+internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), OverscrollBuilder {
+ override fun translate(
+ matcher: ElementMatcher,
+ x: OverscrollScope.() -> Float,
+ y: OverscrollScope.() -> Float
+ ) {
+ transformation(OverscrollTranslate(matcher, x, y))
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index 04d5914..849c9d7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -21,11 +21,11 @@
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.Element
import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.OverscrollScope
import com.android.compose.animation.scene.Scene
import com.android.compose.animation.scene.SceneTransitionLayoutImpl
import com.android.compose.animation.scene.TransitionState
-/** Translate an element by a fixed amount of density-independent pixels. */
internal class Translate(
override val matcher: ElementMatcher,
private val x: Dp = 0.dp,
@@ -47,3 +47,28 @@
}
}
}
+
+internal class OverscrollTranslate(
+ override val matcher: ElementMatcher,
+ val x: OverscrollScope.() -> Float = { 0f },
+ val y: OverscrollScope.() -> Float = { 0f },
+) : PropertyTransformation<Offset> {
+ override fun transform(
+ layoutImpl: SceneTransitionLayoutImpl,
+ scene: Scene,
+ element: Element,
+ sceneState: Element.SceneState,
+ transition: TransitionState.Transition,
+ value: Offset,
+ ): Offset {
+ // As this object is created by OverscrollBuilderImpl and we retrieve the current
+ // OverscrollSpec only when the transition implements HasOverscrollProperties, we can assume
+ // that this method was invoked after performing this check.
+ val overscrollProperties = transition as TransitionState.HasOverscrollProperties
+
+ return Offset(
+ x = value.x + overscrollProperties.overscrollScope.x(),
+ y = value.y + overscrollProperties.overscrollScope.y(),
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 059a10e..26e01fe 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -539,24 +539,20 @@
}
}
- @Test
- fun elementTransitionDuringOverscroll() {
+ private fun setupOverscrollScenario(
+ layoutWidth: Dp,
+ layoutHeight: Dp,
+ sceneTransitions: SceneTransitionsBuilder.() -> Unit,
+ firstScroll: Float
+ ): MutableSceneTransitionLayoutStateImpl {
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
var touchSlop = 0f
- val overscrollTranslateY = 10.dp
- val layoutWidth = 200.dp
- val layoutHeight = 400.dp
val state =
MutableSceneTransitionLayoutState(
initialScene = TestScenes.SceneA,
- transitions =
- transitions {
- overscroll(TestScenes.SceneB, Orientation.Vertical) {
- translate(TestElements.Foo, y = overscrollTranslateY)
- }
- }
+ transitions = transitions(sceneTransitions),
)
as MutableSceneTransitionLayoutStateImpl
@@ -585,9 +581,30 @@
rule.onRoot().performTouchInput {
val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
down(middleTop)
- // Scroll 50%
- moveBy(Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+ val firstScrollHeight = layoutHeight.toPx() * firstScroll
+ moveBy(Offset(0f, touchSlop + firstScrollHeight), delayMillis = 1_000)
}
+ return state
+ }
+
+ @Test
+ fun elementTransitionDuringOverscroll() {
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+ val overscrollTranslateY = 10.dp
+
+ val state =
+ setupOverscrollScenario(
+ layoutWidth = layoutWidth,
+ layoutHeight = layoutHeight,
+ sceneTransitions = {
+ overscroll(TestScenes.SceneB, Orientation.Vertical) {
+ // On overscroll 100% -> Foo should translate by overscrollTranslateY
+ translate(TestElements.Foo, y = overscrollTranslateY)
+ }
+ },
+ firstScroll = 0.5f, // Scroll 50%
+ )
val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
fooElement.assertTopPositionInRootIsEqualTo(0.dp)
@@ -691,4 +708,48 @@
assertThat(state.currentOverscrollSpec).isNotNull()
fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
}
+
+ @Test
+ fun elementTransitionWithDistanceDuringOverscroll() {
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+ val state =
+ setupOverscrollScenario(
+ layoutWidth = layoutWidth,
+ layoutHeight = layoutHeight,
+ sceneTransitions = {
+ overscroll(TestScenes.SceneB, Orientation.Vertical) {
+ // On overscroll 100% -> Foo should translate by layoutHeight
+ translate(TestElements.Foo, y = { absoluteDistance })
+ }
+ },
+ firstScroll = 1f, // 100% scroll
+ )
+
+ val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
+ fooElement.assertTopPositionInRootIsEqualTo(0.dp)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 50%
+ moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+ }
+
+ val transition = state.currentTransition
+ assertThat(transition).isNotNull()
+
+ // Scroll 150% (100% scroll + 50% overscroll)
+ assertThat(transition!!.progress).isEqualTo(1.5f)
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 0.5f)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 100%
+ moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
+ }
+
+ // Scroll 250% (100% scroll + 150% overscroll)
+ assertThat(transition.progress).isEqualTo(2.5f)
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index c9c3ecc..825fe13 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -22,9 +22,9 @@
import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.Orientation
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.Transformation
import com.android.compose.animation.scene.transformation.TransformationRange
-import com.android.compose.animation.scene.transformation.Translate
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -228,12 +228,14 @@
@Test
fun overscrollSpec() {
val transitions = transitions {
- overscroll(TestScenes.SceneA, Orientation.Vertical) { translate(TestElements.Bar) }
+ overscroll(TestScenes.SceneA, Orientation.Vertical) {
+ translate(TestElements.Bar, x = { 1f }, y = { 2f })
+ }
}
val overscrollSpec = transitions.overscrollSpecs.single()
val transformation = overscrollSpec.transformationSpec.transformations.single()
- assertThat(transformation).isInstanceOf(Translate::class.java)
+ assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java)
}
companion object {
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
index 153d2b8..73a66c6 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
@@ -38,6 +38,10 @@
override val isUserInputOngoing: Boolean = isUserInputOngoing
override val isUpOrLeft: Boolean = isUpOrLeft
override val orientation: Orientation = orientation
+ override val overscrollScope: OverscrollScope =
+ object : OverscrollScope {
+ override val absoluteDistance = 0f
+ }
override fun finish(): Job {
error("finish() is not supported in test transitions")
diff --git a/packages/SystemUI/customization/res/values/ids.xml b/packages/SystemUI/customization/res/values/ids.xml
new file mode 100644
index 0000000..5eafbfc
--- /dev/null
+++ b/packages/SystemUI/customization/res/values/ids.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- View ids for elements in large weather clock -->
+ <item type="id" name="weather_clock_time" />
+ <item type="id" name="weather_clock_date" />
+ <item type="id" name="weather_clock_weather_icon" />
+ <item type="id" name="weather_clock_temperature" />
+ <item type="id" name="weather_clock_alarm_dnd" />
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
index 09fdd11..bd1403a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
@@ -36,6 +36,7 @@
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.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -201,6 +202,7 @@
@Test
fun verifySimPin() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -208,8 +210,7 @@
whenever(telephonyManager.supplyIccLockPin(anyString()))
.thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 1))
- val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0))
- runCurrent()
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isNull()
verify(keyguardUpdateMonitor).reportSimUnlocked(1)
@@ -218,6 +219,7 @@
@Test
fun verifySimPin_incorrect_oneRemainingAttempt() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -229,9 +231,7 @@
1,
)
)
-
- val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0))
- runCurrent()
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isNull()
val errorDialogMessage by collectLastValue(bouncerSimRepository.errorDialogMessage)
@@ -245,6 +245,7 @@
@Test
fun verifySimPin_incorrect_threeRemainingAttempts() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
@@ -257,8 +258,7 @@
)
)
- val msg = underTest.verifySim(listOf(0, 0, 0, 0))
- runCurrent()
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isEqualTo("Enter SIM PIN. You have 3 remaining attempts.")
}
@@ -266,10 +266,11 @@
@Test
fun verifySimPin_notCorrectLength_tooShort() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
- val msg = underTest.verifySim(listOf(0))
+ verifySim(listOf(0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
@@ -277,10 +278,12 @@
@Test
fun verifySimPin_notCorrectLength_tooLong() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
+
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(false)
- val msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
@@ -288,6 +291,7 @@
@Test
fun verifySimPuk() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
whenever(telephonyManager.createForSubscriptionId(anyInt()))
.thenReturn(telephonyManager)
whenever(telephonyManager.supplyIccLockPuk(anyString(), anyString()))
@@ -295,13 +299,13 @@
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- var msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint))
- msg = underTest.verifySim(listOf(0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_enter_confirm_pin_hint))
- msg = underTest.verifySim(listOf(0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isNull()
runCurrent()
@@ -311,37 +315,49 @@
@Test
fun verifySimPuk_inputTooShort() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
+
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- val msg = underTest.verifySim(listOf(0, 0, 0, 0))
+
+ verifySim(listOf(0, 0, 0, 0))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_puk_hint))
}
@Test
fun verifySimPuk_pinNotCorrectLength() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
- val msg = underTest.verifySim(listOf(0, 0, 0))
+ verifySim(listOf(0, 0, 0))
+
assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
@Test
fun verifySimPuk_confirmedPinDoesNotMatch() =
testScope.runTest {
+ val msg by collectLastValue(underTest.bouncerMessageChanged)
+
bouncerSimRepository.setSubscriptionId(1)
bouncerSimRepository.setSimPukLocked(true)
- underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
- underTest.verifySim(listOf(0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ verifySim(listOf(0, 0, 0, 0))
- val msg = underTest.verifySim(listOf(0, 0, 0, 1))
+ verifySim(listOf(0, 0, 0, 1))
assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint))
}
+ private suspend fun TestScope.verifySim(pinDigits: List<Int>) {
+ runCurrent()
+ underTest.verifySim(pinDigits)
+ }
+
@Test
fun onErrorDialogDismissed_clearsErrorDialogMessageInRepository() {
bouncerSimRepository.setSimVerificationErrorMessage("abc")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
index 2e9ee5c..4a7757b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt
@@ -21,6 +21,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -35,6 +36,7 @@
private val kosmos = testKosmos()
private val biometricSettingsRepository = kosmos.biometricSettingsRepository
private val underTest = kosmos.deviceEntryBiometricSettingsInteractor
+ private val testScope = kosmos.testScope
@Test
fun isCoex_true() = runTest {
@@ -59,4 +61,25 @@
biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
assertThat(isCoex).isFalse()
}
+
+ @Test
+ fun authenticationFlags_providesAuthFlagsFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.authenticationFlags)
+ .isSameInstanceAs(biometricSettingsRepository.authenticationFlags)
+ }
+
+ @Test
+ fun isFaceAuthEnrolledAndEnabled_providesValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.isFaceAuthEnrolledAndEnabled)
+ .isSameInstanceAs(biometricSettingsRepository.isFaceAuthEnrolledAndEnabled)
+ }
+
+ @Test
+ fun isFingerprintAuthEnrolledAndEnabled_providesValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.isFingerprintAuthEnrolledAndEnabled)
+ .isSameInstanceAs(biometricSettingsRepository.isFingerprintEnrolledAndEnabled)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 4f44705..70ceb2a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -19,6 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -27,8 +28,21 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.AdaptiveAuthRequest
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.BouncerLockedOut
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.PolicyLockdown
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.SecurityTimeout
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
@@ -36,6 +50,7 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -230,8 +245,8 @@
assertThat(canSwipeToEnter).isFalse()
trustRepository.setCurrentUserTrusted(true)
- runCurrent()
faceAuthRepository.isAuthenticated.value = false
+ runCurrent()
assertThat(canSwipeToEnter).isTrue()
}
@@ -383,6 +398,204 @@
assertThat(isUnlocked).isTrue()
}
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ null,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to null
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ null,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() =
+ testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+ kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ "not mainline reboot"
+ )
+ runCurrent()
+
+ verifyRestrictionReasonsForAuthFlags(
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ DeviceNotUnlockedSinceReboot,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to
+ AdaptiveAuthRequest,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to
+ BouncerLockedOut,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ SecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ UserLockdown,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ NonStrongBiometricsSecurityTimeout,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ UnattendedUpdate,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ PolicyLockdown,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
+ TrustAgentDisabled,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ TrustAgentDisabled,
+ )
+ }
+
+ @Test
+ fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() =
+ testScope.runTest {
+ val deviceEntryRestrictionReason by
+ collectLastValue(underTest.deviceEntryRestrictionReason)
+ kosmos.fakeSystemPropertiesHelper.set(
+ DeviceEntryInteractor.SYS_BOOT_REASON_PROP,
+ DeviceEntryInteractor.REBOOT_MAINLINE_UPDATE
+ )
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(
+ userId = 1,
+ flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ )
+ )
+ runCurrent()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(false)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason).isNull()
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+ kosmos.fakeTrustRepository.setTrustUsuallyManaged(true)
+ runCurrent()
+
+ assertThat(deviceEntryRestrictionReason)
+ .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate)
+ }
+
+ private fun TestScope.verifyRestrictionReasonsForAuthFlags(
+ vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?>
+ ) {
+ val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason)
+
+ authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) ->
+ kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(userId = 1, flag = flag)
+ )
+ runCurrent()
+
+ if (expectedReason == null) {
+ assertThat(deviceEntryRestrictionReason).isNull()
+ } else {
+ assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason)
+ }
+ }
+ }
+
private fun switchToScene(sceneKey: SceneKey) {
sceneInteractor.changeScene(sceneKey, "reason")
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
new file mode 100644
index 0000000..8f03717
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import android.os.VibrationEffect
+import android.testing.TestableLooper.RunWithLooper
+import android.view.MotionEvent
+import android.view.View
+import androidx.test.core.view.MotionEventBuilder
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWithLooper(setAsMainLooper = true)
+class QSLongPressEffectTest : SysuiTestCase() {
+
+ @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
+ @Mock private lateinit var vibratorHelper: VibratorHelper
+ @Mock private lateinit var testView: View
+ @get:Rule val animatorTestRule = AnimatorTestRule(this)
+ private val kosmos = testKosmos()
+
+ private val effectDuration = 400
+ private val lowTickDuration = 12
+ private val spinDuration = 133
+
+ private lateinit var longPressEffect: QSLongPressEffect
+
+ @Before
+ fun setup() {
+ whenever(
+ vibratorHelper.getPrimitiveDurations(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ VibrationEffect.Composition.PRIMITIVE_SPIN,
+ )
+ )
+ .thenReturn(intArrayOf(lowTickDuration, spinDuration))
+
+ longPressEffect =
+ QSLongPressEffect(
+ vibratorHelper,
+ effectDuration,
+ )
+ }
+
+ @Test
+ fun onActionDown_whileIdle_startsWait() = testWithScope {
+ // GIVEN an action down event occurs
+ val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
+ longPressEffect.onTouch(testView, downEvent)
+
+ // THEN the effect moves to the TIMEOUT_WAIT state
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+ }
+
+ @Test
+ fun onActionCancel_whileWaiting_goesIdle() = testWhileWaiting {
+ // GIVEN an action cancel occurs
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // THEN the effect goes back to idle and does not start
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ assertEffectDidNotStart()
+ }
+
+ @Test
+ fun onActionUp_whileWaiting_performsClick() = testWhileWaiting {
+ // GIVEN an action is being collected
+ val action by collectLastValue(longPressEffect.actionType)
+
+ // GIVEN an action up occurs
+ val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
+ longPressEffect.onTouch(testView, upEvent)
+
+ // THEN the action to invoke is the click action and the effect does not start
+ assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK)
+ assertEffectDidNotStart()
+ }
+
+ @Test
+ fun onWaitComplete_whileWaiting_beginsEffect() = testWhileWaiting {
+ // GIVEN the pressed timeout is complete
+ advanceTimeBy(QSLongPressEffect.PRESSED_TIMEOUT + 10L)
+
+ // THEN the effect starts
+ assertEffectStarted()
+ }
+
+ @Test
+ fun onActionUp_whileEffectHasBegun_reversesEffect() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // WHEN an action up occurs
+ val upEvent = buildMotionEvent(MotionEvent.ACTION_UP)
+ longPressEffect.onTouch(testView, upEvent)
+
+ // THEN the effect gets reversed at 50% progress
+ assertEffectReverses(0.5f)
+ }
+
+ @Test
+ fun onActionCancel_whileEffectHasBegun_reversesEffect() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // WHEN an action cancel occurs
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // THEN the effect gets reversed at 50% progress
+ assertEffectReverses(0.5f)
+ }
+
+ @Test
+ fun onAnimationComplete_effectEnds() = testWhileRunning {
+ // GIVEN that the animation completes
+ animatorTestRule.advanceTimeBy(effectDuration + 10L)
+
+ // THEN the long-press effect completes
+ assertEffectCompleted()
+ }
+
+ @Test
+ fun onActionDown_whileRunningBackwards_resets() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // GIVEN an action cancel occurs and the effect gets reversed
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // GIVEN an action down occurs
+ val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN)
+ longPressEffect.onTouch(testView, downEvent)
+
+ // THEN the effect resets
+ assertEffectResets()
+ }
+
+ @Test
+ fun onAnimationComplete_whileRunningBackwards_goesToIdle() = testWhileRunning {
+ // GIVEN that the effect is at the middle of its completion (progress of 50%)
+ animatorTestRule.advanceTimeBy(effectDuration / 2L)
+
+ // GIVEN an action cancel occurs and the effect gets reversed
+ val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL)
+ longPressEffect.onTouch(testView, cancelEvent)
+
+ // GIVEN that the animation completes after a sufficient amount of time
+ animatorTestRule.advanceTimeBy(effectDuration.toLong())
+
+ // THEN the state goes to [QSLongPressEffect.State.IDLE]
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ }
+
+ private fun buildMotionEvent(action: Int): MotionEvent =
+ MotionEventBuilder.newBuilder().setAction(action).build()
+
+ private fun testWithScope(test: suspend TestScope.() -> Unit) =
+ with(kosmos) {
+ testScope.runTest {
+ // GIVEN an effect with a testing scope
+ longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+ // THEN run the test
+ test()
+ }
+ }
+
+ private fun testWhileWaiting(test: suspend TestScope.() -> Unit) =
+ with(kosmos) {
+ testScope.runTest {
+ // GIVEN an effect with a testing scope
+ longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+ // GIVEN the TIMEOUT_WAIT state is entered
+ val downEvent =
+ MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_DOWN).build()
+ longPressEffect.onTouch(testView, downEvent)
+
+ // THEN run the test
+ test()
+ }
+ }
+
+ private fun testWhileRunning(test: suspend TestScope.() -> Unit) =
+ with(kosmos) {
+ testScope.runTest {
+ // GIVEN an effect with a testing scope
+ longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler))
+
+ // GIVEN the down event that enters the TIMEOUT_WAIT state
+ val downEvent =
+ MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_DOWN).build()
+ longPressEffect.onTouch(testView, downEvent)
+
+ // GIVEN that the timeout completes and the effect starts
+ advanceTimeBy(QSLongPressEffect.PRESSED_TIMEOUT + 10L)
+
+ // THEN run the test
+ test()
+ }
+ }
+
+ /**
+ * Asserts that the effect started by checking that:
+ * 1. The effect progress is 0f
+ * 2. Initial hint haptics are played
+ * 3. The internal state is [QSLongPressEffect.State.RUNNING_FORWARD]
+ */
+ private fun TestScope.assertEffectStarted() {
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+ val longPressHint =
+ LongPressHapticBuilder.createLongPressHint(
+ lowTickDuration,
+ spinDuration,
+ effectDuration,
+ )
+
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_FORWARD)
+ assertThat(effectProgress).isEqualTo(0f)
+ assertThat(longPressHint).isNotNull()
+ verify(vibratorHelper).vibrate(longPressHint!!)
+ }
+
+ /**
+ * Asserts that the effect did not start by checking that:
+ * 1. No effect progress is emitted
+ * 2. No haptics are played
+ * 3. The internal state is not [QSLongPressEffect.State.RUNNING_BACKWARDS] or
+ * [QSLongPressEffect.State.RUNNING_FORWARD]
+ */
+ private fun TestScope.assertEffectDidNotStart() {
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+
+ assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_FORWARD)
+ assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
+ assertThat(effectProgress).isNull()
+ verify(vibratorHelper, never()).vibrate(any(/* type= */ VibrationEffect::class.java))
+ }
+
+ /**
+ * Asserts that the effect completes by checking that:
+ * 1. The progress is null
+ * 2. The final snap haptics are played
+ * 3. The internal state goes back to [QSLongPressEffect.State.IDLE]
+ * 4. The action to perform on the tile is the long-press action
+ */
+ private fun TestScope.assertEffectCompleted() {
+ val action by collectLastValue(longPressEffect.actionType)
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+ val snapEffect = LongPressHapticBuilder.createSnapEffect()
+
+ assertThat(effectProgress).isNull()
+ assertThat(snapEffect).isNotNull()
+ verify(vibratorHelper).vibrate(snapEffect!!)
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
+ assertThat(action).isEqualTo(QSLongPressEffect.ActionType.LONG_PRESS)
+ }
+
+ /**
+ * Assert that the effect gets reverted by checking that:
+ * 1. The internal state is [QSLongPressEffect.State.RUNNING_BACKWARDS]
+ * 2. The reverse haptics plays at the point where the animation was paused
+ */
+ private fun assertEffectReverses(pausedProgress: Float) {
+ val reverseHaptics =
+ LongPressHapticBuilder.createReversedEffect(
+ pausedProgress,
+ lowTickDuration,
+ effectDuration,
+ )
+
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
+ assertThat(reverseHaptics).isNotNull()
+ verify(vibratorHelper).vibrate(reverseHaptics!!)
+ }
+
+ /**
+ * Asserts that the effect resets by checking that:
+ * 1. The effect progress resets to 0
+ * 2. The internal state goes back to [QSLongPressEffect.State.TIMEOUT_WAIT]
+ */
+ private fun TestScope.assertEffectResets() {
+ val effectProgress by collectLastValue(longPressEffect.effectProgress)
+ assertThat(effectProgress).isEqualTo(0f)
+
+ assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 19950a5..2fd2ef1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -19,9 +19,12 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -31,86 +34,129 @@
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.kosmos.testScope
-import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.domain.startable.shadeStartable
+import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.Parameter
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class LockscreenSceneViewModelTest : SysuiTestCase() {
+ companion object {
+ @Parameters(
+ name =
+ "canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," +
+ " isSingleShade={3}, isCommunalAvailable={4}"
+ )
+ @JvmStatic
+ fun combinations() = buildList {
+ repeat(32) { combination ->
+ add(
+ arrayOf(
+ /* canSwipeToEnter= */ combination and 1 != 0,
+ /* downWithTwoPointers= */ combination and 2 != 0,
+ /* downFromEdge= */ combination and 4 != 0,
+ /* isSingleShade= */ combination and 8 != 0,
+ /* isCommunalAvailable= */ combination and 16 != 0,
+ )
+ )
+ }
+ }
+
+ @JvmStatic
+ @BeforeClass
+ fun setUp() {
+ val combinationStrings =
+ combinations().map { array ->
+ check(array.size == 5)
+ "${array[4]},${array[3]},${array[2]},${array[1]},${array[0]}"
+ }
+ val uniqueCombinations = combinationStrings.toSet()
+ assertThat(combinationStrings).hasSize(uniqueCombinations.size)
+ }
+
+ private fun expectedDownDestination(
+ downFromEdge: Boolean,
+ isSingleShade: Boolean,
+ ): SceneKey {
+ return if (downFromEdge && isSingleShade) Scenes.QuickSettings else Scenes.Shade
+ }
+ }
+
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ @JvmField @Parameter(0) var canSwipeToEnter: Boolean = false
+ @JvmField @Parameter(1) var downWithTwoPointers: Boolean = false
+ @JvmField @Parameter(2) var downFromEdge: Boolean = false
+ @JvmField @Parameter(3) var isSingleShade: Boolean = true
+ @JvmField @Parameter(4) var isCommunalAvailable: Boolean = false
+
private val underTest by lazy { createLockscreenSceneViewModel() }
@Test
- fun upTransitionSceneKey_canSwipeToUnlock_gone() =
+ @EnableFlags(Flags.FLAG_COMMUNAL_HUB)
+ fun destinationScenes() =
testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.None
- )
kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
- kosmos.fakeDeviceEntryRepository.setUnlocked(true)
- sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
-
- assertThat(upTransitionSceneKey).isEqualTo(Scenes.Gone)
- }
-
- @Test
- fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
- testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
- AuthenticationMethodModel.Pin
+ if (canSwipeToEnter) {
+ AuthenticationMethodModel.None
+ } else {
+ AuthenticationMethodModel.Pin
+ }
)
- kosmos.fakeDeviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(canSwipeToEnter)
sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ kosmos.shadeRepository.setShadeMode(
+ if (isSingleShade) {
+ ShadeMode.Single
+ } else {
+ ShadeMode.Split
+ }
+ )
+ kosmos.setCommunalAvailable(isCommunalAvailable)
- assertThat(upTransitionSceneKey).isEqualTo(Scenes.Bouncer)
- }
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
- fun leftTransitionSceneKey_communalIsAvailable_communal() =
- testScope.runTest {
- val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey)
- assertThat(leftDestinationSceneKey).isNull()
+ assertThat(
+ destinationScenes
+ ?.get(
+ Swipe(
+ SwipeDirection.Down,
+ fromSource = Edge.Top.takeIf { downFromEdge },
+ pointerCount = if (downWithTwoPointers) 2 else 1,
+ )
+ )
+ ?.toScene
+ )
+ .isEqualTo(
+ expectedDownDestination(
+ downFromEdge = downFromEdge,
+ isSingleShade = isSingleShade,
+ )
+ )
- kosmos.setCommunalAvailable(true)
- runCurrent()
- assertThat(leftDestinationSceneKey).isEqualTo(Scenes.Communal)
- }
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+ .isEqualTo(if (canSwipeToEnter) Scenes.Gone else Scenes.Bouncer)
- @Test
- fun downFromTopEdgeDestinationSceneKey_whenNotSplitShade_quickSettings() =
- testScope.runTest {
- overrideResource(R.bool.config_use_split_notification_shade, false)
- kosmos.shadeStartable.start()
- val sceneKey by collectLastValue(underTest.downFromTopEdgeDestinationSceneKey)
- assertThat(sceneKey).isEqualTo(Scenes.QuickSettings)
- }
-
- @Test
- fun downFromTopEdgeDestinationSceneKey_whenSplitShade_null() =
- testScope.runTest {
- overrideResource(R.bool.config_use_split_notification_shade, true)
- kosmos.shadeStartable.start()
- val sceneKey by collectLastValue(underTest.downFromTopEdgeDestinationSceneKey)
- assertThat(sceneKey).isNull()
+ assertThat(destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene)
+ .isEqualTo(Scenes.Communal.takeIf { isCommunalAvailable })
}
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
index a1f885c..c0e5a9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
@@ -114,7 +114,7 @@
DisabledByPolicyInteractor.PolicyResult.TileDisabled(ADMIN)
)
- val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(context, ADMIN)
+ val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(ADMIN)
assertThat(result).isTrue()
verify(activityStarter).postStartActivityDismissingKeyguard(intentCaptor.capture(), any())
assertThat(intentCaptor.value.filterEquals(expectedIntent)).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 42c3354..af9abcd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -26,7 +26,6 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.internal.R
import com.android.internal.util.EmergencyAffordanceManager
import com.android.internal.util.emergencyAffordanceManager
@@ -317,8 +316,8 @@
@Test
fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
testScope.runTest {
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -337,8 +336,8 @@
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -356,7 +355,7 @@
emulateUserDrivenTransition(to = Scenes.Shade)
assertCurrentScene(Scenes.Shade)
- val upDestinationSceneKey = destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Lockscreen)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -379,7 +378,7 @@
emulateUserDrivenTransition(to = Scenes.Shade)
assertCurrentScene(Scenes.Shade)
- val upDestinationSceneKey = destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -447,8 +446,8 @@
fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
testScope.runTest {
unlockDevice()
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
}
@@ -469,8 +468,8 @@
fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(
to = upDestinationSceneKey,
@@ -487,8 +486,8 @@
fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
@@ -507,8 +506,8 @@
testScope.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
startPhoneCall()
- val upDestinationSceneKey by
- collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+ val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes)
+ val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
index d3fa360..cd79ed1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -54,7 +54,7 @@
private val kosmos = Kosmos()
private val testScope = kosmos.testScope
private val sceneInteractor = kosmos.sceneInteractor
- private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+ private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
private lateinit var shadeInteractor: ShadeInteractor
private lateinit var underTest: ShadeControllerSceneImpl
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index a2f3ccb..e06efe8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -113,6 +113,28 @@
}
@Test
+ fun zenMuted_cantChange() {
+ with(kosmos) {
+ testScope.runTest {
+ notificationsSoundPolicyRepository.updateNotificationPolicy()
+ notificationsSoundPolicyRepository.updateZenMode(
+ ZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
+ )
+
+ val canChangeVolume by
+ collectLastValue(
+ underTest.canChangeVolume(AudioStream(AudioManager.STREAM_NOTIFICATION))
+ )
+
+ underTest.setMuted(AudioStream(AudioManager.STREAM_RING), true)
+ runCurrent()
+
+ assertThat(canChangeVolume).isFalse()
+ }
+ }
+ }
+
+ @Test
fun streamIsMuted_getStream_volumeZero() {
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index f51e109..7341015 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -304,6 +304,15 @@
<!-- An explanation text that the password needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
<string name="kg_prompt_reason_timeout_password">For additional security, use password instead</string>
+ <!-- An explanation text that the pin needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+ <string name="kg_prompt_added_security_pin">PIN required for additional security</string>
+
+ <!-- An explanation text that the pattern needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+ <string name="kg_prompt_added_security_pattern">Pattern required for additional security</string>
+
+ <!-- An explanation text that the password needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] -->
+ <string name="kg_prompt_added_security_password">Password required for additional security</string>
+
<!-- An explanation text that the credential needs to be entered because a device admin has
locked the device. [CHAR LIMIT=80] -->
<string name="kg_prompt_reason_device_admin">Device locked by admin</string>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 25596cc..b8f20f6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1536,8 +1536,12 @@
<!-- Media device casting volume slider label [CHAR_LIMIT=20] -->
<string name="media_device_cast">Cast</string>
- <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=40]-->
+ <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=50]-->
<string name="stream_notification_unavailable">Unavailable because ring is muted</string>
+ <!-- A message shown when the alarm volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]-->
+ <string name="stream_alarm_unavailable">Unavailable because Do Not Disturb is on</string>
+ <!-- A message shown when the media volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]-->
+ <string name="stream_media_unavailable">Unavailable because Do Not Disturb is on</string>
<!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on vibrate. [CHAR_LIMIT=NONE] -->
<!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on silent (muted). [CHAR_LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4e7809a..59516be 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -965,6 +965,10 @@
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
</style>
+ <style name="Widget.SliceView.VolumePanel">
+ <item name="hideHeaderRow">true</item>
+ </style>
+
<style name="Theme.VolumePanelActivity.Popup" parent="@style/Theme.SystemUI.Dialog">
<item name="android:dialogCornerRadius">44dp</item>
<item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainerHigh
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 84c8ea7..26e91b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -122,7 +122,7 @@
case PROMPT_REASON_USER_REQUEST:
return R.string.kg_prompt_after_user_lockdown_password;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- return R.string.kg_prompt_reason_timeout_password;
+ return R.string.kg_prompt_added_security_password;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_password;
case PROMPT_REASON_TRUSTAGENT_EXPIRED:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index bf8900d..caa74780 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -323,7 +323,7 @@
resId = R.string.kg_prompt_after_user_lockdown_pattern;
break;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- resId = R.string.kg_prompt_reason_timeout_pattern;
+ resId = R.string.kg_prompt_added_security_pattern;
break;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
resId = R.string.kg_prompt_reason_timeout_pattern;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index bcab6f0..fbe9edf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -134,7 +134,7 @@
case PROMPT_REASON_USER_REQUEST:
return R.string.kg_prompt_after_user_lockdown_pin;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
- return R.string.kg_prompt_reason_timeout_pin;
+ return R.string.kg_prompt_added_security_pin;
case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
return R.string.kg_prompt_reason_timeout_pin;
case PROMPT_REASON_TRUSTAGENT_EXPIRED:
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 9de71c1..8bd675c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -881,7 +881,7 @@
final Runnable endActionRunnable = () -> {
setVisibility(View.INVISIBLE);
- if (Flags.customBiometricPrompt()) {
+ if (Flags.customBiometricPrompt() && constraintBp()) {
mPromptSelectorInteractorProvider.get().resetPrompt();
}
removeWindowIfAttached();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index b87fadf..4d88f49 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -18,6 +18,7 @@
import android.hardware.biometrics.Flags
import android.hardware.biometrics.PromptInfo
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed
@@ -151,6 +152,7 @@
val hasCredentialViewShown = kind.value !is PromptKind.Biometric
val showBpForCredential =
Flags.customBiometricPrompt() &&
+ constraintBp() &&
!Utils.isBiometricAllowed(promptInfo) &&
isDeviceCredentialAllowed(promptInfo) &&
promptInfo.contentView != null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index e48f05d..7bb75bf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -22,6 +22,7 @@
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.Flags
import android.hardware.face.FaceManager
import android.text.method.ScrollingMovementMethod
import android.util.Log
@@ -166,11 +167,14 @@
titleView.text = viewModel.title.first()
subtitleView.text = viewModel.subtitle.first()
descriptionView.text = viewModel.description.first()
- BiometricCustomizedViewBinder.bind(
- customizedViewContainer,
- view.requireViewById(R.id.space_above_content),
- viewModel
- )
+
+ if (Flags.customBiometricPrompt() && constraintBp()) {
+ BiometricCustomizedViewBinder.bind(
+ customizedViewContainer,
+ view.requireViewById(R.id.space_above_content),
+ viewModel
+ )
+ }
// set button listeners
negativeButton.setOnClickListener { legacyCallback.onButtonNegative() }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 61aeffe..34b50e4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -29,6 +29,7 @@
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import com.android.systemui.Flags.bpTalkback
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -284,7 +285,7 @@
promptSelectorInteractor.prompt
.map {
when {
- !customBiometricPrompt() || it == null -> null
+ !(customBiometricPrompt() && constraintBp()) || it == null -> null
it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
else ->
@@ -304,7 +305,7 @@
promptSelectorInteractor.prompt
.map {
when {
- !customBiometricPrompt() || it == null -> ""
+ !(customBiometricPrompt() && constraintBp()) || it == null -> ""
it.logoDescription != null -> it.logoDescription
else ->
try {
@@ -329,7 +330,7 @@
/** Custom content view for the prompt. */
val contentView: Flow<PromptContentView?> =
promptSelectorInteractor.prompt
- .map { if (customBiometricPrompt()) it?.contentView else null }
+ .map { if (customBiometricPrompt() && constraintBp()) it?.contentView else null }
.distinctUntilChanged()
private val originalDescription =
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index c25e748..7f6fc91 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -23,10 +23,12 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Flags
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
import com.android.systemui.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.bouncer.shared.model.BouncerMessageStrings
import com.android.systemui.bouncer.shared.model.Message
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -35,46 +37,6 @@
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.res.R.string.bouncer_face_not_recognized
-import com.android.systemui.res.R.string.keyguard_enter_password
-import com.android.systemui.res.R.string.keyguard_enter_pattern
-import com.android.systemui.res.R.string.keyguard_enter_pin
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_password
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pattern
-import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pin
-import com.android.systemui.res.R.string.kg_bio_try_again_or_password
-import com.android.systemui.res.R.string.kg_bio_try_again_or_pattern
-import com.android.systemui.res.R.string.kg_bio_try_again_or_pin
-import com.android.systemui.res.R.string.kg_face_locked_out
-import com.android.systemui.res.R.string.kg_fp_not_recognized
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_password
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pattern
-import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pin
-import com.android.systemui.res.R.string.kg_prompt_after_adaptive_auth_lock
-import com.android.systemui.res.R.string.kg_prompt_after_dpm_lock
-import com.android.systemui.res.R.string.kg_prompt_after_update_password
-import com.android.systemui.res.R.string.kg_prompt_after_update_pattern
-import com.android.systemui.res.R.string.kg_prompt_after_update_pin
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_password
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pattern
-import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pin
-import com.android.systemui.res.R.string.kg_prompt_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_password_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_pattern_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_pin_auth_timeout
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_password
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_pattern
-import com.android.systemui.res.R.string.kg_prompt_reason_restart_pin
-import com.android.systemui.res.R.string.kg_prompt_unattended_update
-import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
-import com.android.systemui.res.R.string.kg_trust_agent_disabled
-import com.android.systemui.res.R.string.kg_unlock_with_password_or_fp
-import com.android.systemui.res.R.string.kg_unlock_with_pattern_or_fp
-import com.android.systemui.res.R.string.kg_unlock_with_pin_or_fp
-import com.android.systemui.res.R.string.kg_wrong_input_try_fp_suggestion
-import com.android.systemui.res.R.string.kg_wrong_password_try_again
-import com.android.systemui.res.R.string.kg_wrong_pattern_try_again
-import com.android.systemui.res.R.string.kg_wrong_pin_try_again
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.Quint
import javax.inject.Inject
@@ -130,17 +92,22 @@
repository.setMessage(
when (biometricSourceType) {
BiometricSourceType.FINGERPRINT ->
- incorrectFingerprintInput(currentSecurityMode)
+ BouncerMessageStrings.incorrectFingerprintInput(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
BiometricSourceType.FACE ->
- incorrectFaceInput(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.incorrectFaceInput(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
else ->
- defaultMessage(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.defaultMessage(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
}
)
}
@@ -189,45 +156,79 @@
trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
) {
if (wasRebootedForMainlineUpdate) {
- authRequiredForMainlineUpdate(currentSecurityMode)
+ BouncerMessageStrings.authRequiredForMainlineUpdate(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else {
- authRequiredAfterReboot(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterReboot(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
}
} else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
- authRequiredAfterPrimaryAuthTimeout(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterPrimaryAuthTimeout(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
- authRequiredAfterAdminLockdown(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterAdminLockdown(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else if (
- trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
+ trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredForUnattendedUpdate
) {
- authRequiredForUnattendedUpdate(currentSecurityMode)
+ BouncerMessageStrings.authRequiredForUnattendedUpdate(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else if (fpLockedOut) {
- class3AuthLockedOut(currentSecurityMode)
+ BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel())
+ .toMessage()
} else if (faceLockedOut) {
if (isFaceAuthClass3) {
- class3AuthLockedOut(currentSecurityMode)
+ BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel())
+ .toMessage()
} else {
- faceLockedOut(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.faceLockedOut(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
}
} else if (flags.isSomeAuthRequiredAfterAdaptiveAuthRequest) {
- authRequiredAfterAdaptiveAuthRequest(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.authRequiredAfterAdaptiveAuthRequest(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (
trustOrBiometricsAvailable &&
flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
) {
- nonStrongAuthTimeout(
- currentSecurityMode,
- isFingerprintAuthCurrentlyAllowed.value
- )
+ BouncerMessageStrings.nonStrongAuthTimeout(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterUserRequest) {
- trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.trustAgentDisabled(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
- trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.trustAgentDisabled(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
} else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
- authRequiredAfterUserLockdown(currentSecurityMode)
+ BouncerMessageStrings.authRequiredAfterUserLockdown(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
} else {
defaultMessage
}
@@ -244,7 +245,11 @@
override fun onTick(millisUntilFinished: Long) {
val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt()
- val message = primaryAuthLockedOut(currentSecurityMode)
+ val message =
+ BouncerMessageStrings.primaryAuthLockedOut(
+ currentSecurityMode.toAuthModel()
+ )
+ .toMessage()
message.message?.animate = false
message.message?.formatterArgs =
mutableMapOf<String, Any>(Pair("count", secondsRemaining))
@@ -258,7 +263,11 @@
if (!Flags.revampedBouncerMessages()) return
repository.setMessage(
- incorrectSecurityInput(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ BouncerMessageStrings.incorrectSecurityInput(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
)
}
@@ -285,7 +294,12 @@
}
private val defaultMessage: BouncerMessageModel
- get() = defaultMessage(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value)
+ get() =
+ BouncerMessageStrings.defaultMessage(
+ currentSecurityMode.toAuthModel(),
+ isFingerprintAuthCurrentlyAllowed.value
+ )
+ .toMessage()
fun onPrimaryBouncerUserInput() {
if (!Flags.revampedBouncerMessages()) return
@@ -354,283 +368,35 @@
return BouncerMessageModel(
message =
Message(
- messageResId = defaultMessage(securityMode, fpAuthIsAllowed).message?.messageResId,
+ messageResId =
+ BouncerMessageStrings.defaultMessage(
+ securityMode.toAuthModel(),
+ fpAuthIsAllowed
+ )
+ .toMessage()
+ .message
+ ?.messageResId,
animate = false
),
secondaryMessage = Message(message = secondaryMessage, animate = false)
)
}
-private fun defaultMessage(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) {
- defaultMessageWithFingerprint(securityMode)
- } else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
- SecurityMode.Password -> Pair(keyguard_enter_password, 0)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, 0)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun defaultMessageWithFingerprint(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, 0)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectSecurityInput(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) {
- incorrectSecurityInputWithFingerprint(securityMode)
- } else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0)
- SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0)
- SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, 0)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion)
- SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion)
- SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, kg_wrong_input_try_fp_suggestion)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectFingerprintInput(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern)
- SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password)
- SecurityMode.PIN -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectFaceInput(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode)
- else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern)
- SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password)
- SecurityMode.PIN -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun incorrectFaceInputWithFingerprintAllowed(
- securityMode: SecurityMode
-): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, bouncer_face_not_recognized)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun biometricLockout(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterReboot(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterAdaptiveAuthRequest(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(securityMode)
- else
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.Password ->
- Pair(keyguard_enter_password, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_adaptive_auth_lock)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(
- securityMode: SecurityMode
-): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern ->
- Pair(kg_unlock_with_pattern_or_fp, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.Password ->
- Pair(kg_unlock_with_password_or_fp, kg_prompt_after_adaptive_auth_lock)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_after_adaptive_auth_lock)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern)
- SecurityMode.Password ->
- Pair(keyguard_enter_password, kg_prompt_after_user_lockdown_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_unattended_update)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredForMainlineUpdate(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_update_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_update_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_update_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun nonStrongAuthTimeout(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) {
- nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
- } else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_auth_timeout)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_auth_timeout)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun faceLockedOut(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) faceLockedOutButFingerprintAvailable(securityMode)
- else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_face_locked_out)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun faceLockedOutButFingerprintAvailable(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_face_locked_out)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_face_locked_out)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_face_locked_out)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun class3AuthLockedOut(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun trustAgentDisabled(
- securityMode: SecurityMode,
- fpAuthIsAllowed: Boolean
-): BouncerMessageModel {
- return if (fpAuthIsAllowed) trustAgentDisabledWithFingerprintAllowed(securityMode)
- else
- when (securityMode) {
- SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled)
- SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled)
- SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_trust_agent_disabled)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun trustAgentDisabledWithFingerprintAllowed(
- securityMode: SecurityMode
-): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled)
- SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled)
- SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_trust_agent_disabled)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
-private fun primaryAuthLockedOut(securityMode: SecurityMode): BouncerMessageModel {
- return when (securityMode) {
- SecurityMode.Pattern ->
- Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern)
- SecurityMode.Password ->
- Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_password)
- SecurityMode.PIN ->
- Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pin)
- else -> Pair(0, 0)
- }.toMessage()
-}
-
private fun Pair<Int, Int>.toMessage(): BouncerMessageModel {
return BouncerMessageModel(
message = Message(messageResId = this.first, animate = false),
secondaryMessage = Message(messageResId = this.second, animate = false)
)
}
+
+private fun SecurityMode.toAuthModel(): AuthenticationMethodModel {
+ return when (this) {
+ SecurityMode.Invalid -> AuthenticationMethodModel.None
+ SecurityMode.None -> AuthenticationMethodModel.None
+ SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
+ SecurityMode.Password -> AuthenticationMethodModel.Password
+ SecurityMode.PIN -> AuthenticationMethodModel.Pin
+ SecurityMode.SimPin -> AuthenticationMethodModel.Sim
+ SecurityMode.SimPuk -> AuthenticationMethodModel.Sim
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
index c3d4cb3..7d3075a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
@@ -44,6 +44,8 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -70,6 +72,9 @@
val isLockedEsim: StateFlow<Boolean?> = repository.isLockedEsim
val errorDialogMessage: StateFlow<String?> = repository.errorDialogMessage
+ private val _bouncerMessageChanged = MutableSharedFlow<String?>()
+ val bouncerMessageChanged: SharedFlow<String?> = _bouncerMessageChanged
+
/** Returns the default message for the sim pin screen. */
fun getDefaultMessage(): String {
val isEsimLocked = repository.isLockedEsim.value ?: false
@@ -81,7 +86,7 @@
return ""
}
- var count = telephonyManager.activeModemCount
+ val count = telephonyManager.activeModemCount
val info: SubscriptionInfo? = repository.activeSubscriptionInfo.value
val displayName = info?.displayName
var msg: String =
@@ -156,32 +161,24 @@
repository.setSimVerificationErrorMessage(null)
}
- /**
- * Based on sim state, unlock the locked sim with the given credentials.
- *
- * @return Any message that should show associated with the provided input. Null means that no
- * message needs to be shown.
- */
- suspend fun verifySim(input: List<Any>): String? {
+ /** Based on sim state, unlock the locked sim with the given credentials. */
+ suspend fun verifySim(input: List<Any>) {
+ val code = input.joinToString(separator = "")
if (repository.isSimPukLocked.value) {
- return verifySimPuk(input.joinToString(separator = ""))
+ verifySimPuk(code)
+ } else {
+ verifySimPin(code)
}
-
- return verifySimPin(input.joinToString(separator = ""))
}
- /**
- * Verifies the input and unlocks the locked sim with a 4-8 digit pin code.
- *
- * @return Any message that should show associated with the provided input. Null means that no
- * message needs to be shown.
- */
- private suspend fun verifySimPin(input: String): String? {
+ /** Verifies the input and unlocks the locked sim with a 4-8 digit pin code. */
+ private suspend fun verifySimPin(input: String) {
val subscriptionId = repository.subscriptionId.value
// A SIM PIN is 4 to 8 decimal digits according to
// GSM 02.17 version 5.0.1, Section 5.6 PIN Management
if (input.length < MIN_SIM_PIN_LENGTH || input.length > MAX_SIM_PIN_LENGTH) {
- return resources.getString(R.string.kg_invalid_sim_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_pin_hint))
+ return
}
val result =
withContext(backgroundDispatcher) {
@@ -190,8 +187,10 @@
telephonyManager.supplyIccLockPin(input)
}
when (result.result) {
- PinResult.PIN_RESULT_TYPE_SUCCESS ->
+ PinResult.PIN_RESULT_TYPE_SUCCESS -> {
keyguardUpdateMonitor.reportSimUnlocked(subscriptionId)
+ _bouncerMessageChanged.emit(null)
+ }
PinResult.PIN_RESULT_TYPE_INCORRECT -> {
if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) {
// Show a dialog to display the remaining number of attempts to verify the sim
@@ -199,24 +198,22 @@
repository.setSimVerificationErrorMessage(
getPinPasswordErrorMessage(result.attemptsRemaining)
)
+ _bouncerMessageChanged.emit(null)
} else {
- return getPinPasswordErrorMessage(result.attemptsRemaining)
+ _bouncerMessageChanged.emit(
+ getPinPasswordErrorMessage(result.attemptsRemaining)
+ )
}
}
}
-
- return null
}
/**
* Verifies the input and unlocks the locked sim with a puk code instead of pin.
*
* This occurs after incorrectly verifying the sim pin multiple times.
- *
- * @return Any message that should show associated with the provided input. Null means that no
- * message needs to be shown.
*/
- private suspend fun verifySimPuk(entry: String): String? {
+ private suspend fun verifySimPuk(entry: String) {
val (enteredSimPuk, enteredSimPin) = repository.simPukInputModel
val subscriptionId: Int = repository.subscriptionId.value
@@ -224,10 +221,11 @@
if (enteredSimPuk == null) {
if (entry.length >= MIN_SIM_PUK_LENGTH) {
repository.setSimPukUserInput(enteredSimPuk = entry)
- return resources.getString(R.string.kg_puk_enter_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_puk_enter_pin_hint))
} else {
- return resources.getString(R.string.kg_invalid_sim_puk_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_puk_hint))
}
+ return
}
// Stage 2: Set a new sim pin to lock the sim card.
@@ -237,10 +235,11 @@
enteredSimPuk = enteredSimPuk,
enteredSimPin = entry,
)
- return resources.getString(R.string.kg_enter_confirm_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_enter_confirm_pin_hint))
} else {
- return resources.getString(R.string.kg_invalid_sim_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_pin_hint))
}
+ return
}
// Stage 3: Confirm the newly set sim pin.
@@ -250,7 +249,8 @@
resources.getString(R.string.kg_invalid_confirm_pin_hint)
)
repository.setSimPukUserInput(enteredSimPuk = enteredSimPuk)
- return resources.getString(R.string.kg_puk_enter_pin_hint)
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_puk_enter_pin_hint))
+ return
}
val result =
@@ -261,9 +261,11 @@
resetSimPukUserInput()
when (result.result) {
- PinResult.PIN_RESULT_TYPE_SUCCESS ->
+ PinResult.PIN_RESULT_TYPE_SUCCESS -> {
keyguardUpdateMonitor.reportSimUnlocked(subscriptionId)
- PinResult.PIN_RESULT_TYPE_INCORRECT ->
+ _bouncerMessageChanged.emit(null)
+ }
+ PinResult.PIN_RESULT_TYPE_INCORRECT -> {
if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) {
// Show a dialog to display the remaining number of attempts to verify the sim
// puk to the user.
@@ -274,17 +276,21 @@
isEsimLocked = repository.isLockedEsim.value == true
)
)
+ _bouncerMessageChanged.emit(null)
} else {
- return getPukPasswordErrorMessage(
- result.attemptsRemaining,
- isDefault = false,
- isEsimLocked = repository.isLockedEsim.value == true
+ _bouncerMessageChanged.emit(
+ getPukPasswordErrorMessage(
+ result.attemptsRemaining,
+ isDefault = false,
+ isEsimLocked = repository.isLockedEsim.value == true
+ )
)
}
- else -> return resources.getString(R.string.kg_password_puk_failed)
+ }
+ else -> {
+ _bouncerMessageChanged.emit(resources.getString(R.string.kg_password_puk_failed))
+ }
}
-
- return null
}
private fun getPinPasswordErrorMessage(attemptsRemaining: Int): String {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt
new file mode 100644
index 0000000..cb12ce5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.shared.model
+
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
+import com.android.systemui.res.R
+
+typealias BouncerMessagePair = Pair<Int, Int>
+
+val BouncerMessagePair.primaryMessage: Int
+ get() = this.first
+
+val BouncerMessagePair.secondaryMessage: Int
+ get() = this.second
+
+object BouncerMessageStrings {
+ private val EmptyMessage = Pair(0, 0)
+
+ fun defaultMessage(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), 0)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), 0)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), 0)
+ else -> EmptyMessage
+ }
+ }
+
+ fun incorrectSecurityInput(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMessage = incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed)
+ return when (securityMode) {
+ Pattern -> Pair(R.string.kg_wrong_pattern_try_again, secondaryMessage)
+ Password -> Pair(R.string.kg_wrong_password_try_again, secondaryMessage)
+ Pin -> Pair(R.string.kg_wrong_pin_try_again, secondaryMessage)
+ else -> EmptyMessage
+ }
+ }
+
+ private fun incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed: Boolean): Int {
+ return if (fpAuthIsAllowed) R.string.kg_wrong_input_try_fp_suggestion else 0
+ }
+
+ fun incorrectFingerprintInput(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ val primaryMessage = R.string.kg_fp_not_recognized
+ return when (securityMode) {
+ Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern)
+ Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password)
+ Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun incorrectFaceInput(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode)
+ else {
+ val primaryMessage = R.string.bouncer_face_not_recognized
+ when (securityMode) {
+ Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern)
+ Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password)
+ Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin)
+ else -> EmptyMessage
+ }
+ }
+ }
+
+ private fun incorrectFaceInputWithFingerprintAllowed(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.bouncer_face_not_recognized
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(true), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(true), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(true), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterReboot(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_reason_restart_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_reason_restart_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_reason_restart_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterAdminLockdown(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_prompt_after_dpm_lock
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(false), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(false), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterAdaptiveAuthRequest(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_prompt_after_adaptive_auth_lock
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterUserLockdown(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern ->
+ Pair(patternDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredForUnattendedUpdate(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_added_security_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_added_security_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_added_security_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredForMainlineUpdate(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_after_update_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_update_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_update_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun authRequiredAfterPrimaryAuthTimeout(
+ securityMode: AuthenticationMethodModel
+ ): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_pattern_auth_timeout)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_prompt_password_auth_timeout)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_pin_auth_timeout)
+ else -> EmptyMessage
+ }
+ }
+
+ fun nonStrongAuthTimeout(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_prompt_auth_timeout
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun faceLockedOut(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_face_locked_out
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun class3AuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(false), R.string.kg_bio_too_many_attempts_pattern)
+ Password ->
+ Pair(passwordDefaultMessage(false), R.string.kg_bio_too_many_attempts_password)
+ Pin -> Pair(pinDefaultMessage(false), R.string.kg_bio_too_many_attempts_pin)
+ else -> EmptyMessage
+ }
+ }
+
+ fun trustAgentDisabled(
+ securityMode: AuthenticationMethodModel,
+ fpAuthIsAllowed: Boolean
+ ): BouncerMessagePair {
+ val secondaryMsg = R.string.kg_trust_agent_disabled
+ return when (securityMode) {
+ Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg)
+ else -> EmptyMessage
+ }
+ }
+
+ fun primaryAuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair {
+ return when (securityMode) {
+ Pattern ->
+ Pair(
+ R.string.kg_too_many_failed_attempts_countdown,
+ R.string.kg_primary_auth_locked_out_pattern
+ )
+ Password ->
+ Pair(
+ R.string.kg_too_many_failed_attempts_countdown,
+ R.string.kg_primary_auth_locked_out_password
+ )
+ Pin ->
+ Pair(
+ R.string.kg_too_many_failed_attempts_countdown,
+ R.string.kg_primary_auth_locked_out_pin
+ )
+ else -> EmptyMessage
+ }
+ }
+
+ private fun patternDefaultMessage(fingerprintAllowed: Boolean): Int {
+ return if (fingerprintAllowed) R.string.kg_unlock_with_pattern_or_fp
+ else R.string.keyguard_enter_pattern
+ }
+
+ private fun pinDefaultMessage(fingerprintAllowed: Boolean): Int {
+ return if (fingerprintAllowed) R.string.kg_unlock_with_pin_or_fp
+ else R.string.keyguard_enter_pin
+ }
+
+ private fun passwordDefaultMessage(fingerprintAllowed: Boolean): Int {
+ return if (fingerprintAllowed) R.string.kg_unlock_with_password_or_fp
+ else R.string.keyguard_enter_password
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 7f4a029..e910a92 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -157,8 +157,7 @@
if (authenticationMethod == AuthenticationMethodModel.Sim) {
viewModelScope.launch {
isSimUnlockingDialogVisible.value = true
- val msg = simBouncerInteractor.verifySim(getInput())
- interactor.setMessage(msg)
+ simBouncerInteractor.verifySim(getInput())
isSimUnlockingDialogVisible.value = false
clearInput()
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
index 96171aa..d495fac 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
@@ -18,6 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -31,9 +32,22 @@
constructor(
repository: BiometricSettingsRepository,
) {
+
+ /**
+ * Flags that control the device entry authentication behavior.
+ *
+ * This exposes why biometrics may not be currently allowed.
+ */
+ val authenticationFlags: Flow<AuthenticationFlags> = repository.authenticationFlags
+
+ /** Whether the current user has enrolled and enabled fingerprint auth. */
+ val isFingerprintAuthEnrolledAndEnabled: Flow<Boolean> =
+ repository.isFingerprintEnrolledAndEnabled
+
val fingerprintAuthCurrentlyAllowed: Flow<Boolean> =
repository.isFingerprintAuthCurrentlyAllowed
- val faceAuthEnrolledAndEnabled: Flow<Boolean> = repository.isFaceAuthEnrolledAndEnabled
+ /** Whether the current user has enrolled and enabled face auth. */
+ val isFaceAuthEnrolledAndEnabled: Flow<Boolean> = repository.isFaceAuthEnrolledAndEnabled
val faceAuthCurrentlyAllowed: Flow<Boolean> = repository.isFaceAuthCurrentlyAllowed
/** Whether both fingerprint and face are enrolled and enabled for device entry. */
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
index 99bd25b..7733de4 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -32,6 +32,10 @@
/** Current detection status */
val detectionStatus: Flow<FaceDetectionStatus>
+ val lockedOut: Flow<Boolean>
+
+ val authenticated: Flow<Boolean>
+
/** Can face auth be run right now */
fun canFaceAuthRun(): Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index a5f6f7c..8059993 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.deviceentry.domain.interactor
+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.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
@@ -23,14 +25,20 @@
import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class DeviceEntryFingerprintAuthInteractor
@Inject
constructor(
repository: DeviceEntryFingerprintAuthRepository,
+ biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
) {
/** Whether fingerprint authentication is currently running or not */
val isRunning: Flow<Boolean> = repository.isRunning
@@ -47,4 +55,21 @@
repository.authenticationStatus.filterIsInstance<ErrorFingerprintAuthenticationStatus>()
val fingerprintHelp: Flow<HelpFingerprintAuthenticationStatus> =
repository.authenticationStatus.filterIsInstance<HelpFingerprintAuthenticationStatus>()
+
+ /**
+ * Whether fingerprint authentication is currently allowed for the user. This is true if the
+ * user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by
+ * [com.android.systemui.keyguard.shared.model.AuthenticationFlags] and not locked out due to
+ * too many incorrect attempts.
+ */
+ val isFingerprintAuthCurrentlyAllowed: Flow<Boolean> =
+ combine(isLockedOut, biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair)
+ .map { (lockedOut, currentlyAllowed) -> !lockedOut && currentlyAllowed }
+
+ /**
+ * Whether the fingerprint sensor is present under the display as opposed to being on the power
+ * button or behind/rear of the phone.
+ */
+ val isSensorUnderDisplay =
+ fingerprintPropertyRepository.sensorType.map(FingerprintSensorType::isUdfps)
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 029a4f3..fa2421a 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -16,24 +16,30 @@
package com.android.systemui.deviceentry.domain.interactor
+import androidx.annotation.VisibleForTesting
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason
+import com.android.systemui.flags.SystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.util.kotlin.Quad
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
@@ -55,10 +61,13 @@
private val repository: DeviceEntryRepository,
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
- deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
- trustRepository: TrustRepository,
+ faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
+ private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ private val trustInteractor: TrustInteractor,
flags: SceneContainerFlags,
deviceUnlockedInteractor: DeviceUnlockedInteractor,
+ private val systemPropertiesHelper: SystemPropertiesHelper,
) {
/**
* Whether the device is unlocked.
@@ -96,8 +105,8 @@
*/
private val isPassivelyAuthenticated =
merge(
- trustRepository.isCurrentUserTrusted,
- deviceEntryFaceAuthRepository.isAuthenticated,
+ trustInteractor.isTrusted,
+ faceAuthInteractor.authenticated,
)
.onStart { emit(false) }
@@ -134,6 +143,67 @@
initialValue = null,
)
+ private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
+ private val fingerprintEnrolledAndEnabled =
+ biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled
+ private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled
+
+ private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> =
+ combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple)
+
+ /**
+ * Reason why device entry is restricted to certain authentication methods for the current user.
+ *
+ * Emits null when there are no device entry restrictions active.
+ */
+ val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> =
+ faceOrFingerprintOrTrustEnabled.flatMapLatest {
+ (faceEnabled, fingerprintEnabled, trustEnabled) ->
+ if (faceEnabled || fingerprintEnabled || trustEnabled) {
+ combine(
+ biometricSettingsInteractor.authenticationFlags,
+ faceAuthInteractor.lockedOut,
+ fingerprintAuthInteractor.isLockedOut,
+ trustInteractor.isTrustAgentCurrentlyAllowed,
+ ::Quad
+ )
+ .map { (authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged) ->
+ when {
+ authFlags.isPrimaryAuthRequiredAfterReboot &&
+ wasRebootedForMainlineUpdate ->
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate
+ authFlags.isPrimaryAuthRequiredAfterReboot ->
+ DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot
+ authFlags.isPrimaryAuthRequiredAfterDpmLockdown ->
+ DeviceEntryRestrictionReason.PolicyLockdown
+ authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown
+ authFlags.isPrimaryAuthRequiredForUnattendedUpdate ->
+ DeviceEntryRestrictionReason.UnattendedUpdate
+ authFlags.isPrimaryAuthRequiredAfterTimeout ->
+ DeviceEntryRestrictionReason.SecurityTimeout
+ authFlags.isPrimaryAuthRequiredAfterLockout ->
+ DeviceEntryRestrictionReason.BouncerLockedOut
+ isFingerprintLockedOut ->
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+ isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() ->
+ DeviceEntryRestrictionReason.StrongBiometricsLockedOut
+ isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut
+ authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest ->
+ DeviceEntryRestrictionReason.AdaptiveAuthRequest
+ (trustEnabled && !trustManaged) &&
+ (authFlags.someAuthRequiredAfterTrustAgentExpired ||
+ authFlags.someAuthRequiredAfterUserRequest) ->
+ DeviceEntryRestrictionReason.TrustAgentDisabled
+ authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ->
+ DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout
+ else -> null
+ }
+ }
+ } else {
+ flowOf(null)
+ }
+ }
+
/**
* Attempt to enter the device and dismiss the lockscreen. If authentication is required to
* unlock the device it will transition to bouncer.
@@ -187,4 +257,12 @@
}
}
}
+
+ private val wasRebootedForMainlineUpdate
+ get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
+
+ companion object {
+ @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
+ @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
index fd6fbc9..98deda09 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
@@ -77,7 +77,7 @@
private fun startUpdatingFaceHelpMessageDeferral() {
scope.launch {
- biometricSettingsInteractor.faceAuthEnrolledAndEnabled
+ biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
.flatMapLatest { faceEnrolledAndEnabled ->
if (faceEnrolledAndEnabled) {
faceAcquired
@@ -94,7 +94,7 @@
}
scope.launch {
- biometricSettingsInteractor.faceAuthEnrolledAndEnabled
+ biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
.flatMapLatest { faceEnrolledAndEnabled ->
if (faceEnrolledAndEnabled) {
faceHelp
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
index 3b94166..65f3eb7 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
@@ -31,10 +31,10 @@
*/
@SysUISingleton
class NoopDeviceEntryFaceAuthInteractor @Inject constructor() : DeviceEntryFaceAuthInteractor {
- override val authenticationStatus: Flow<FaceAuthenticationStatus>
- get() = emptyFlow()
- override val detectionStatus: Flow<FaceDetectionStatus>
- get() = emptyFlow()
+ override val authenticationStatus: Flow<FaceAuthenticationStatus> = emptyFlow()
+ override val detectionStatus: Flow<FaceDetectionStatus> = emptyFlow()
+ override val lockedOut: Flow<Boolean> = emptyFlow()
+ override val authenticated: Flow<Boolean> = emptyFlow()
override fun canFaceAuthRun(): Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 0c9fbc2..a7266503 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -272,6 +272,8 @@
/** Provide the status of face detection */
override val detectionStatus = repository.detectionStatus
+ override val lockedOut: Flow<Boolean> = repository.isLockedOut
+ override val authenticated: Flow<Boolean> = repository.isAuthenticated
private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
if (repository.isLockedOut.value) {
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt
new file mode 100644
index 0000000..5b672ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.shared.model
+
+/** List of reasons why device entry can be restricted to certain authentication methods. */
+enum class DeviceEntryRestrictionReason {
+ /**
+ * Reason: Lockdown initiated by the user.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ UserLockdown,
+
+ /**
+ * Reason: Not unlocked since reboot.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ DeviceNotUnlockedSinceReboot,
+
+ /**
+ * Reason: Not unlocked since reboot after a mainline update.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ DeviceNotUnlockedSinceMainlineUpdate,
+
+ /**
+ * Reason: Lockdown initiated by admin through installed device policy
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ PolicyLockdown,
+
+ /**
+ * Reason: Device entry credentials need to be used for an unattended update at a later point in
+ * time.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ UnattendedUpdate,
+
+ /**
+ * Reason: Device was not unlocked using PIN/Pattern/Password for a prolonged period of time.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ SecurityTimeout,
+
+ /**
+ * Reason: A "class 3"/strong biometrics device entry method was locked out after many incorrect
+ * authentication attempts.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ *
+ * @see
+ * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+ */
+ StrongBiometricsLockedOut,
+
+ /**
+ * Reason: A weak (class 2)/convenience (class 3) strength face biometrics device entry method
+ * was locked out after many incorrect authentication attempts.
+ *
+ * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+ *
+ * @see
+ * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+ */
+ NonStrongFaceLockedOut,
+
+ /**
+ * Reason: Device was last unlocked using a weak/convenience strength biometrics device entry
+ * method and a stronger authentication method wasn't used to unlock the device for a prolonged
+ * period of time.
+ *
+ * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+ *
+ * @see
+ * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes)
+ */
+ NonStrongBiometricsSecurityTimeout,
+
+ /**
+ * Reason: A trust agent that was granting trust has either expired or disabled by the user by
+ * opening the power menu.
+ *
+ * Restriction: Only non trust agent device entry methods are allowed.
+ */
+ TrustAgentDisabled,
+
+ /**
+ * Reason: Theft protection is enabled after too many unlock attempts.
+ *
+ * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed.
+ */
+ AdaptiveAuthRequest,
+
+ /**
+ * Reason: Bouncer was locked out after too many incorrect authentication attempts.
+ *
+ * Restriction: Only bouncer based device entry is allowed.
+ */
+ BouncerLockedOut,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 6fa20de..1e3c604 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -20,36 +20,34 @@
import javax.inject.Inject
import javax.inject.Singleton
-/**
- * Proxy to make {@link SystemProperties} easily testable.
- */
+/** Proxy to make {@link SystemProperties} easily testable. */
@Singleton
open class SystemPropertiesHelper @Inject constructor() {
- fun get(name: String): String {
+ open fun get(name: String): String {
return SystemProperties.get(name)
}
- fun get(name: String, def: String?): String {
+ open fun get(name: String, def: String?): String {
return SystemProperties.get(name, def)
}
- fun getBoolean(name: String, default: Boolean): Boolean {
+ open fun getBoolean(name: String, default: Boolean): Boolean {
return SystemProperties.getBoolean(name, default)
}
- fun setBoolean(name: String, value: Boolean) {
+ open fun setBoolean(name: String, value: Boolean) {
SystemProperties.set(name, if (value) "1" else "0")
}
- fun set(name: String, value: String) {
+ open fun set(name: String, value: String) {
SystemProperties.set(name, value)
}
- fun set(name: String, value: Int) {
+ open fun set(name: String, value: Int) {
set(name, value.toString())
}
- fun erase(name: String) {
+ open fun erase(name: String) {
set(name, "")
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt
new file mode 100644
index 0000000..0143b85
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import android.os.VibrationEffect
+import android.util.Log
+import kotlin.math.max
+
+object LongPressHapticBuilder {
+
+ const val INVALID_DURATION = 0 /* in ms */
+
+ private const val TAG = "LongPressHapticBuilder"
+ private const val SPIN_SCALE = 0.2f
+ private const val CLICK_SCALE = 0.5f
+ private const val LOW_TICK_SCALE = 0.08f
+ private const val WARMUP_TIME = 75 /* in ms */
+ private const val DAMPING_TIME = 24 /* in ms */
+
+ /** Create the signal that indicates that a long-press action is available. */
+ fun createLongPressHint(
+ lowTickDuration: Int,
+ spinDuration: Int,
+ effectDuration: Int
+ ): VibrationEffect? {
+ if (lowTickDuration == 0 || spinDuration == 0) {
+ Log.d(
+ TAG,
+ "The LOW_TICK and/or SPIN primitives are not supported. No signal created.",
+ )
+ return null
+ }
+ if (effectDuration < WARMUP_TIME + spinDuration + DAMPING_TIME) {
+ Log.d(
+ TAG,
+ "Cannot fit long-press hint signal in the effect duration. No signal created",
+ )
+ return null
+ }
+
+ val nLowTicks = WARMUP_TIME / lowTickDuration
+ val rampDownLowTicks = DAMPING_TIME / lowTickDuration
+ val composition = VibrationEffect.startComposition()
+
+ // Warmup low ticks
+ repeat(nLowTicks) {
+ composition.addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ LOW_TICK_SCALE,
+ 0,
+ )
+ }
+
+ // Spin effect
+ composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, SPIN_SCALE, 0)
+
+ // Damping low ticks
+ repeat(rampDownLowTicks) { i ->
+ composition.addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ LOW_TICK_SCALE / (i + 1),
+ 0,
+ )
+ }
+
+ return composition.compose()
+ }
+
+ /** Create a "snapping" effect that triggers at the end of a long-press gesture */
+ fun createSnapEffect(): VibrationEffect? =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, CLICK_SCALE, 0)
+ .compose()
+
+ /** Creates a signal that indicates the reversal of the long-press animation. */
+ fun createReversedEffect(
+ pausedProgress: Float,
+ lowTickDuration: Int,
+ effectDuration: Int,
+ ): VibrationEffect? {
+ val duration = pausedProgress * effectDuration
+ if (duration == 0f) return null
+
+ if (lowTickDuration == 0) {
+ Log.d(TAG, "Cannot play reverse haptics because LOW_TICK is not supported")
+ return null
+ }
+
+ val nLowTicks = (duration / lowTickDuration).toInt()
+ if (nLowTicks == 0) return null
+
+ val composition = VibrationEffect.startComposition()
+ var scale: Float
+ val step = LOW_TICK_SCALE / nLowTicks
+ repeat(nLowTicks) { i ->
+ scale = max(LOW_TICK_SCALE - step * i, 0f)
+ composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale, 0)
+ }
+ return composition.compose()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
new file mode 100644
index 0000000..ec72a14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.os.VibrationEffect
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.animation.AccelerateDecelerateInterpolator
+import androidx.annotation.VisibleForTesting
+import androidx.core.animation.doOnCancel
+import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
+import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * A class that handles the long press visuo-haptic effect for a QS tile.
+ *
+ * The class is also a [View.OnTouchListener] to handle the touch events, clicks and long-press
+ * gestures of the tile. The class also provides a [State] that can be used to determine the current
+ * state of the long press effect.
+ *
+ * @property[vibratorHelper] The [VibratorHelper] to deliver haptic effects.
+ * @property[effectDuration] The duration of the effect in ms.
+ */
+class QSLongPressEffect(
+ private val vibratorHelper: VibratorHelper?,
+ private val effectDuration: Int,
+) : View.OnTouchListener {
+
+ /** Current state */
+ var state = State.IDLE
+ @VisibleForTesting set
+
+ /** Flows for view control and action */
+ private val _effectProgress = MutableStateFlow<Float?>(null)
+ val effectProgress = _effectProgress.asStateFlow()
+
+ private val _actionType = MutableStateFlow<ActionType?>(null)
+ val actionType = _actionType.asStateFlow()
+
+ /** Haptic effects */
+ private val durations =
+ vibratorHelper?.getPrimitiveDurations(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ VibrationEffect.Composition.PRIMITIVE_SPIN
+ )
+
+ private val longPressHint =
+ LongPressHapticBuilder.createLongPressHint(
+ durations?.get(0) ?: LongPressHapticBuilder.INVALID_DURATION,
+ durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION,
+ effectDuration
+ )
+
+ private val snapEffect = LongPressHapticBuilder.createSnapEffect()
+
+ /* A coroutine scope and a timer job that waits for the pressedTimeout */
+ var scope: CoroutineScope? = null
+ private var waitJob: Job? = null
+
+ private val effectAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = effectDuration.toLong()
+ interpolator = AccelerateDecelerateInterpolator()
+
+ doOnStart { handleAnimationStart() }
+ addUpdateListener { _effectProgress.value = animatedValue as Float }
+ doOnEnd { handleAnimationComplete() }
+ doOnCancel { handleAnimationCancel() }
+ }
+
+ private fun reverse() {
+ val pausedProgress = effectAnimator.animatedFraction
+ val effect =
+ LongPressHapticBuilder.createReversedEffect(
+ pausedProgress,
+ durations?.get(0) ?: 0,
+ effectDuration,
+ )
+ vibratorHelper?.cancel()
+ vibrate(effect)
+ effectAnimator.reverse()
+ }
+
+ private fun vibrate(effect: VibrationEffect?) {
+ if (vibratorHelper != null && effect != null) {
+ vibratorHelper.vibrate(effect)
+ }
+ }
+
+ /**
+ * Handle relevant touch events for the operation of a Tile.
+ *
+ * A click action is performed following the relevant logic that originates from the
+ * [MotionEvent.ACTION_UP] event depending on the current state.
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(view: View?, event: MotionEvent?): Boolean {
+ when (event?.actionMasked) {
+ MotionEvent.ACTION_DOWN -> handleActionDown()
+ MotionEvent.ACTION_UP -> handleActionUp()
+ MotionEvent.ACTION_CANCEL -> handleActionCancel()
+ }
+ return true
+ }
+
+ private fun handleActionDown() {
+ when (state) {
+ State.IDLE -> {
+ startPressedTimeoutWait()
+ state = State.TIMEOUT_WAIT
+ }
+ State.RUNNING_BACKWARDS -> effectAnimator.cancel()
+ else -> {}
+ }
+ }
+
+ private fun startPressedTimeoutWait() {
+ waitJob =
+ scope?.launch {
+ try {
+ delay(PRESSED_TIMEOUT)
+ handleTimeoutComplete()
+ } catch (_: CancellationException) {
+ state = State.IDLE
+ }
+ }
+ }
+
+ private fun handleActionUp() {
+ when (state) {
+ State.TIMEOUT_WAIT -> {
+ waitJob?.cancel()
+ _actionType.value = ActionType.CLICK
+ state = State.IDLE
+ }
+ State.RUNNING_FORWARD -> {
+ reverse()
+ state = State.RUNNING_BACKWARDS
+ }
+ else -> {}
+ }
+ }
+
+ private fun handleActionCancel() {
+ when (state) {
+ State.TIMEOUT_WAIT -> {
+ waitJob?.cancel()
+ state = State.IDLE
+ }
+ State.RUNNING_FORWARD -> {
+ reverse()
+ state = State.RUNNING_BACKWARDS
+ }
+ else -> {}
+ }
+ }
+
+ private fun handleAnimationStart() {
+ vibrate(longPressHint)
+ state = State.RUNNING_FORWARD
+ }
+
+ /** This function is called both when an animator completes or gets cancelled */
+ private fun handleAnimationComplete() {
+ if (state == State.RUNNING_FORWARD) {
+ vibrate(snapEffect)
+ _actionType.value = ActionType.LONG_PRESS
+ _effectProgress.value = null
+ }
+ if (state != State.TIMEOUT_WAIT) {
+ // This will happen if the animator did not finish by being cancelled
+ state = State.IDLE
+ }
+ }
+
+ private fun handleAnimationCancel() {
+ _effectProgress.value = 0f
+ startPressedTimeoutWait()
+ state = State.TIMEOUT_WAIT
+ }
+
+ private fun handleTimeoutComplete() {
+ if (state == State.TIMEOUT_WAIT && !effectAnimator.isRunning) {
+ effectAnimator.start()
+ }
+ }
+
+ fun clearActionType() {
+ _actionType.value = null
+ }
+
+ enum class State {
+ IDLE, /* The effect is idle waiting for touch input */
+ TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */
+ RUNNING_FORWARD, /* The effect is running normally */
+ RUNNING_BACKWARDS, /* The effect was interrupted and is now running backwards */
+ }
+
+ /* A type of action to perform on the view depending on the effect's state and logic */
+ enum class ActionType {
+ CLICK,
+ LONG_PRESS,
+ }
+
+ companion object {
+ /**
+ * A timeout to let the tile resolve if it is being swiped/scrolled. Since QS tiles are
+ * inside a scrollable container, they will be considered pressed only after a tap timeout.
+ */
+ val PRESSED_TIMEOUT = ViewConfiguration.getTapTimeout().toLong() + 20L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
new file mode 100644
index 0000000..e298154
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.qs
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.qs.tileimpl.QSTileViewImpl
+import kotlinx.coroutines.launch
+
+object QSLongPressEffectViewBinder {
+
+ fun bind(
+ tile: QSTileViewImpl,
+ effect: QSLongPressEffect?,
+ ) {
+ if (effect == null) return
+
+ tile.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ effect.scope = this
+
+ launch {
+ effect.effectProgress.collect { progress ->
+ progress?.let {
+ if (it == 0f) {
+ tile.bringToFront()
+ }
+ tile.updateLongPressEffectProperties(it)
+ }
+ }
+ }
+
+ launch {
+ effect.actionType.collect { action ->
+ action?.let {
+ when (it) {
+ QSLongPressEffect.ActionType.CLICK -> tile.performClick()
+ QSLongPressEffect.ActionType.LONG_PRESS -> tile.performLongClick()
+ }
+ effect.clearActionType()
+ }
+ }
+ }
+ }
+ }
+ }
+}
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 f5f5571..882f231 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
@@ -175,7 +175,6 @@
pw.println("isFingerprintAuthCurrentlyAllowed=${isFingerprintAuthCurrentlyAllowed.value}")
pw.println("isNonStrongBiometricAllowed=${isNonStrongBiometricAllowed.value}")
pw.println("isStrongBiometricAllowed=${isStrongBiometricAllowed.value}")
- pw.println("isFingerprintEnabledByDevicePolicy=${isFingerprintEnabledByDevicePolicy.value}")
}
/** UserId of the current selected user. */
@@ -324,22 +323,14 @@
else isNonStrongBiometricAllowed
}
- private val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
- selectedUserId
- .flatMapLatest { userId ->
- devicePolicyChangedForAllUsers
- .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
- .flowOn(backgroundDispatcher)
- .distinctUntilChanged()
- }
- .stateIn(
- scope,
- started = SharingStarted.Eagerly,
- initialValue =
- devicePolicyManager.isFingerprintDisabled(
- userRepository.getSelectedUserInfo().id
- )
- )
+ private val isFingerprintEnabledByDevicePolicy: Flow<Boolean> =
+ selectedUserId.flatMapLatest { userId ->
+ devicePolicyChangedForAllUsers
+ .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
+ .onStart { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
+ .flowOn(backgroundDispatcher)
+ .distinctUntilChanged()
+ }
override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> =
isFingerprintEnrolled
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt
new file mode 100644
index 0000000..2ff6e16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.TrustRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Encapsulates any state relevant to trust agents and trust grants. */
+@SysUISingleton
+class TrustInteractor @Inject constructor(repository: TrustRepository) {
+ /**
+ * Whether the current user has a trust agent enabled. This is true if the user has at least one
+ * trust agent enabled in settings.
+ */
+ val isEnrolledAndEnabled: StateFlow<Boolean> = repository.isCurrentUserTrustUsuallyManaged
+
+ /**
+ * Whether the current user's trust agent is currently allowed, this will be false if trust
+ * agent is disabled for any reason (security timeout, disabled on lock screen by opening the
+ * power menu, etc), it does not include temporary biometric lockouts.
+ */
+ val isTrustAgentCurrentlyAllowed: StateFlow<Boolean> = repository.isCurrentUserTrustManaged
+
+ /** Whether the current user is trusted by any of the enabled trust agents. */
+ val isTrusted: Flow<Boolean> = repository.isCurrentUserTrusted
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
index 08904b6..d6f3634 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
@@ -32,6 +32,9 @@
val isPrimaryAuthRequiredAfterTimeout =
containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
+ val isPrimaryAuthRequiredAfterLockout =
+ containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT)
+
val isPrimaryAuthRequiredAfterDpmLockdown =
containsFlag(
flag,
@@ -47,7 +50,7 @@
LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
)
- val primaryAuthRequiredForUnattendedUpdate =
+ val isPrimaryAuthRequiredForUnattendedUpdate =
containsFlag(
flag,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 288ef3c..993e81b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -14,9 +14,15 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -27,9 +33,10 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the lockscreen scene. */
@@ -44,37 +51,69 @@
val longPress: KeyguardLongPressViewModel,
val notifications: NotificationsPlaceholderViewModel,
) {
- /** The key of the scene we should switch to when swiping up. */
- val upDestinationSceneKey: StateFlow<SceneKey> =
- deviceEntryInteractor.isUnlocked
- .map { isUnlocked -> upDestinationSceneKey(isUnlocked) }
+ val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ combine(
+ deviceEntryInteractor.isUnlocked,
+ communalInteractor.isCommunalAvailable,
+ shadeInteractor.shadeMode,
+ ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+ destinationScenes(
+ isDeviceUnlocked = isDeviceUnlocked,
+ isCommunalAvailable = isCommunalAvailable,
+ shadeMode = shadeMode,
+ )
+ }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = upDestinationSceneKey(deviceEntryInteractor.isUnlocked.value),
+ initialValue =
+ destinationScenes(
+ isDeviceUnlocked = deviceEntryInteractor.isUnlocked.value,
+ isCommunalAvailable = false,
+ shadeMode = shadeInteractor.shadeMode.value,
+ ),
)
- private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey {
- return if (isUnlocked) Scenes.Gone else Scenes.Bouncer
+ private fun destinationScenes(
+ isDeviceUnlocked: Boolean,
+ isCommunalAvailable: Boolean,
+ shadeMode: ShadeMode,
+ ): Map<UserAction, UserActionResult> {
+ val quickSettingsIfSingleShade =
+ if (shadeMode is ShadeMode.Single) {
+ Scenes.QuickSettings
+ } else {
+ Scenes.Shade
+ }
+
+ return mapOf(
+ Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
+ Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
+
+ // Swiping down from the top edge goes to QS (or shade if in split shade mode).
+ swipeDownFromTop(pointerCount = 1) to quickSettingsIfSingleShade,
+ swipeDownFromTop(pointerCount = 2) to quickSettingsIfSingleShade,
+
+ // Swiping down, not from the edge, always navigates to the shade scene.
+ swipeDown(pointerCount = 1) to Scenes.Shade,
+ swipeDown(pointerCount = 2) to Scenes.Shade,
+ )
+ .filterValues { it != null }
+ .mapValues { checkNotNull(it.value) }
}
- /** The key of the scene we should switch to when swiping left. */
- val leftDestinationSceneKey: StateFlow<SceneKey?> =
- communalInteractor.isCommunalAvailable
- .map { available -> if (available) Scenes.Communal else null }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = null,
- )
+ private fun swipeDownFromTop(pointerCount: Int): Swipe {
+ return Swipe(
+ SwipeDirection.Down,
+ fromSource = Edge.Top,
+ pointerCount = pointerCount,
+ )
+ }
- /** The key of the scene we should switch to when swiping down from the top edge. */
- val downFromTopEdgeDestinationSceneKey: StateFlow<SceneKey?> =
- shadeInteractor.shadeMode
- .map { shadeMode -> Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single } }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = null,
- )
+ private fun swipeDown(pointerCount: Int): Swipe {
+ return Swipe(
+ SwipeDirection.Down,
+ pointerCount = pointerCount,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 91c86df..9d0ea5e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -19,6 +19,7 @@
import static android.view.InputDevice.SOURCE_TOUCHPAD;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
+import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground;
import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
@@ -54,7 +55,6 @@
import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.InputEvent;
-import android.view.InputMonitor;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -104,6 +104,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -151,7 +152,12 @@
private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
- mGestureBlockingActivityRunning = isGestureBlockingActivityRunning();
+ if (edgebackGestureHandlerGetRunningTasksBackground()) {
+ mBackgroundExecutor.execute(() -> mGestureBlockingActivityRunning.set(
+ isGestureBlockingActivityRunning()));
+ } else {
+ mGestureBlockingActivityRunning.set(isGestureBlockingActivityRunning());
+ }
}
@Override
public void onTaskCreated(int taskId, ComponentName componentName) {
@@ -241,6 +247,8 @@
private final PointF mDownPoint = new PointF();
private final PointF mEndPoint = new PointF();
+ private AtomicBoolean mGestureBlockingActivityRunning = new AtomicBoolean();
+
private boolean mThresholdCrossed = false;
private boolean mAllowGesture = false;
private boolean mLogGesture = false;
@@ -256,7 +264,6 @@
private boolean mIsEnabled;
private boolean mIsNavBarShownTransiently;
private boolean mIsBackGestureAllowed;
- private boolean mGestureBlockingActivityRunning;
private boolean mIsNewBackAffordanceEnabled;
private boolean mIsTrackpadGestureFeaturesEnabled;
private boolean mIsTrackpadThreeFingerSwipe;
@@ -1017,7 +1024,7 @@
mInRejectedExclusion = false;
boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());
boolean isBackAllowedCommon = !mDisabledForQuickstep && mIsBackGestureAllowed
- && !mGestureBlockingActivityRunning
+ && !mGestureBlockingActivityRunning.get()
&& !QuickStepContract.isBackGestureDisabled(mSysUiFlags,
mIsTrackpadThreeFingerSwipe)
&& !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev);
@@ -1053,8 +1060,8 @@
curTime, curTimeStr, mAllowGesture, mIsTrackpadThreeFingerSwipe,
mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed,
QuickStepContract.isBackGestureDisabled(mSysUiFlags,
- mIsTrackpadThreeFingerSwipe),
- mDisabledForQuickstep, mGestureBlockingActivityRunning, mIsInPip, mDisplaySize,
+ mIsTrackpadThreeFingerSwipe), mDisabledForQuickstep,
+ mGestureBlockingActivityRunning.get(), mIsInPip, mDisplaySize,
mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion));
} else if (mAllowGesture || mLogGesture) {
if (!mThresholdCrossed) {
@@ -1236,7 +1243,7 @@
pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed);
pw.println(" mIsGestureHandlingEnabled=" + mIsGestureHandlingEnabled);
pw.println(" mIsNavBarShownTransiently=" + mIsNavBarShownTransiently);
- pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning);
+ pw.println(" mGestureBlockingActivityRunning=" + mGestureBlockingActivityRunning.get());
pw.println(" mAllowGesture=" + mAllowGesture);
pw.println(" mUseMLModel=" + mUseMLModel);
pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 2440651..cd65119 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -38,6 +38,7 @@
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
@@ -90,9 +91,11 @@
FalsingManager falsingManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
SplitShadeStateController splitShadeStateController,
- SceneContainerFlags sceneContainerFlags) {
+ SceneContainerFlags sceneContainerFlags,
+ VibratorHelper vibratorHelper) {
super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
- metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
+ metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController,
+ vibratorHelper);
mTunerService = tunerService;
mQsCustomizerController = qsCustomizerController;
mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 975c871..5e12b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -39,6 +39,7 @@
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileViewImpl;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.animation.DisappearParameters;
@@ -87,6 +88,8 @@
private SplitShadeStateController mSplitShadeStateController;
+ private final VibratorHelper mVibratorHelper;
+
@VisibleForTesting
protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
new QSPanel.OnConfigurationChangedListener() {
@@ -144,7 +147,8 @@
UiEventLogger uiEventLogger,
QSLogger qsLogger,
DumpManager dumpManager,
- SplitShadeStateController splitShadeStateController
+ SplitShadeStateController splitShadeStateController,
+ VibratorHelper vibratorHelper
) {
super(view);
mHost = host;
@@ -158,6 +162,7 @@
mSplitShadeStateController = splitShadeStateController;
mShouldUseSplitNotificationShade =
mSplitShadeStateController.shouldUseSplitNotificationShade(getResources());
+ mVibratorHelper = vibratorHelper;
}
@Override
@@ -300,7 +305,8 @@
}
private void addTile(final QSTile tile, boolean collapsedView) {
- final QSTileViewImpl tileView = new QSTileViewImpl(getContext(), collapsedView);
+ final QSTileViewImpl tileView = new QSTileViewImpl(
+ getContext(), collapsedView, mVibratorHelper);
final TileRecord r = new TileRecord(tile, tileView);
// TODO(b/250618218): Remove the QSLogger in QSTileViewImpl once we know the root cause of
// b/250618218.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index a8e88da..05bb088 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -32,6 +32,7 @@
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.leak.RotationUtils;
@@ -56,10 +57,11 @@
@Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
Provider<Boolean> usingCollapsedLandscapeMediaProvider,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager, SplitShadeStateController splitShadeStateController
+ DumpManager dumpManager, SplitShadeStateController splitShadeStateController,
+ VibratorHelper vibratorHelper
) {
super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
- uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
+ uiEventLogger, qsLogger, dumpManager, splitShadeStateController, vibratorHelper);
mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
new file mode 100644
index 0000000..a2ded6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl
+
+/**
+ * List of properties that define the state of a tile during a long-press gesture.
+ *
+ * These properties are used during animation if a tile supports a long-press action.
+ */
+data class QSLongPressProperties(
+ var xScale: Float,
+ var yScale: Float,
+ var cornerRadius: Float,
+ var backgroundColor: Int,
+ var labelColor: Int,
+ var secondaryLabelColor: Int,
+ var chevronColor: Int,
+ var overlayColor: Int,
+ var iconColor: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 35cac4b..1456747 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -587,7 +587,7 @@
name = "handleClick";
if (mState.disabledByPolicy) {
Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, mEnforcedAdmin);
+ mEnforcedAdmin);
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
} else {
mQSLogger.logHandleClick(mTileSpec, msg.arg1);
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 6cc682a..63963de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -26,6 +26,7 @@
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RippleDrawable
import android.os.Trace
@@ -36,6 +37,7 @@
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
+import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
@@ -48,9 +50,12 @@
import com.android.app.tracing.traceSection
import com.android.settingslib.Utils
import com.android.systemui.Flags
+import com.android.systemui.Flags.quickSettingsVisualHapticsLongpress
import com.android.systemui.FontSizeUtils
import com.android.systemui.animation.LaunchableView
import com.android.systemui.animation.LaunchableViewDelegate
+import com.android.systemui.haptics.qs.QSLongPressEffect
+import com.android.systemui.haptics.qs.QSLongPressEffectViewBinder
import com.android.systemui.plugins.qs.QSIconView
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTile.AdapterState
@@ -58,12 +63,15 @@
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.children
import java.util.Objects
private const val TAG = "QSTileViewImpl"
open class QSTileViewImpl @JvmOverloads constructor(
context: Context,
- private val collapsed: Boolean = false
+ private val collapsed: Boolean = false,
+ private val vibratorHelper: VibratorHelper? = null,
) : QSTileView(context), HeightOverrideable, LaunchableView {
companion object {
@@ -163,6 +171,7 @@
private var lastStateDescription: CharSequence? = null
private var tileState = false
private var lastState = INVALID
+ private var lastIconTint = 0
private val launchableViewDelegate = LaunchableViewDelegate(
this,
superSetVisibility = { super.setVisibility(it) },
@@ -171,6 +180,12 @@
private val locInScreen = IntArray(2)
+ /** Visuo-haptic long-press effects */
+ private var longPressEffect: QSLongPressEffect? = null
+ private var initialLongPressProperties: QSLongPressProperties? = null
+ private var finalLongPressProperties: QSLongPressProperties? = null
+ private val colorEvaluator = ArgbEvaluator.getInstance()
+
init {
val typedValue = TypedValue()
if (!getContext().theme.resolveAttribute(R.attr.isQsTheme, typedValue, true)) {
@@ -339,6 +354,9 @@
true
}
)
+ if (quickSettingsVisualHapticsLongpress()) {
+ isHapticFeedbackEnabled = false // Haptics will be handled by the [QSLongPressEffect]
+ }
}
private fun init(
@@ -589,6 +607,27 @@
lastState = state.state
lastDisabledByPolicy = state.disabledByPolicy
+ lastIconTint = icon.getColor(state)
+
+ // Long-press effects
+ if (quickSettingsVisualHapticsLongpress()){
+ if (state.handlesLongClick) {
+ // initialize the long-press effect and set it as the touch listener
+ showRippleEffect = false
+ initializeLongPressEffect()
+ setOnTouchListener(longPressEffect)
+ QSLongPressEffectViewBinder.bind(this, longPressEffect)
+ } else {
+ // Long-press effects might have been enabled before but the new state does not
+ // handle a long-press. In this case, we go back to the behaviour of a regular tile
+ // and clean-up the resources
+ showRippleEffect = isClickable
+ setOnTouchListener(null)
+ longPressEffect = null
+ initialLongPressProperties = null
+ finalLongPressProperties = null
+ }
+ }
}
private fun setAllColors(
@@ -709,6 +748,140 @@
}
}
+ override fun onActivityLaunchAnimationEnd() = resetLongPressEffectProperties()
+
+ fun updateLongPressEffectProperties(effectProgress: Float) {
+ if (!isLongClickable) return
+ setAllColors(
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.backgroundColor ?: 0,
+ finalLongPressProperties?.backgroundColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.labelColor ?: 0,
+ finalLongPressProperties?.labelColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.secondaryLabelColor ?: 0,
+ finalLongPressProperties?.secondaryLabelColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.chevronColor ?: 0,
+ finalLongPressProperties?.chevronColor ?: 0,
+ ) as Int,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.overlayColor ?: 0,
+ finalLongPressProperties?.overlayColor ?: 0,
+ ) as Int,
+ )
+ icon.setTint(
+ icon.mIcon as ImageView,
+ colorEvaluator.evaluate(
+ effectProgress,
+ initialLongPressProperties?.iconColor ?: 0,
+ finalLongPressProperties?.iconColor ?: 0,
+ ) as Int,
+ )
+
+ val newScaleX =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.xScale ?: 1f,
+ finalLongPressProperties?.xScale ?: 1f,
+ )
+ val newScaleY =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.xScale ?: 1f,
+ finalLongPressProperties?.xScale ?: 1f,
+ )
+ val newRadius =
+ interpolateFloat(
+ effectProgress,
+ initialLongPressProperties?.cornerRadius ?: 0f,
+ finalLongPressProperties?.cornerRadius ?: 0f,
+ )
+ scaleX = newScaleX
+ scaleY = newScaleY
+ for (child in children) {
+ child.scaleX = 1f / newScaleX
+ child.scaleY = 1f / newScaleY
+ }
+ changeCornerRadius(newRadius)
+ }
+
+ private fun interpolateFloat(fraction: Float, start: Float, end: Float): Float =
+ start + fraction * (end - start)
+
+ private fun resetLongPressEffectProperties() {
+ scaleY = 1f
+ scaleX = 1f
+ for (child in children) {
+ child.scaleY = 1f
+ child.scaleX = 1f
+ }
+ changeCornerRadius(resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat())
+ setAllColors(
+ getBackgroundColorForState(lastState, lastDisabledByPolicy),
+ getLabelColorForState(lastState, lastDisabledByPolicy),
+ getSecondaryLabelColorForState(lastState, lastDisabledByPolicy),
+ getChevronColorForState(lastState, lastDisabledByPolicy),
+ getOverlayColorForState(lastState),
+ )
+ icon.setTint(icon.mIcon as ImageView, lastIconTint)
+ }
+
+ private fun initializeLongPressEffect() {
+ initializeLongPressProperties()
+ longPressEffect =
+ QSLongPressEffect(
+ vibratorHelper,
+ ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout(),
+ )
+ }
+
+ private fun initializeLongPressProperties() {
+ initialLongPressProperties =
+ QSLongPressProperties(
+ /* xScale= */1f,
+ /* yScale= */1f,
+ resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat(),
+ getBackgroundColorForState(lastState),
+ getLabelColorForState(lastState),
+ getSecondaryLabelColorForState(lastState),
+ getChevronColorForState(lastState),
+ getOverlayColorForState(lastState),
+ lastIconTint,
+ )
+
+ finalLongPressProperties =
+ QSLongPressProperties(
+ /* xScale= */1.1f,
+ /* yScale= */1.2f,
+ resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat() - 20,
+ getBackgroundColorForState(Tile.STATE_ACTIVE),
+ getLabelColorForState(Tile.STATE_ACTIVE),
+ getSecondaryLabelColorForState(Tile.STATE_ACTIVE),
+ getChevronColorForState(Tile.STATE_ACTIVE),
+ getOverlayColorForState(Tile.STATE_ACTIVE),
+ Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive),
+ )
+ }
+
+ private fun changeCornerRadius(radius: Float) {
+ for (i in 0 until backgroundDrawable.numberOfLayers) {
+ val layer = backgroundDrawable.getDrawable(i)
+ if (layer is GradientDrawable) {
+ layer.cornerRadius = radius
+ }
+ }
+ }
+
@VisibleForTesting
internal fun getCurrentColors(): List<Int> = listOf(
backgroundColor,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 32deb30..6b654be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -34,11 +34,11 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.PseudoGridView;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.user.UserSwitchDialogController;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -186,7 +186,7 @@
(UserRecord) view.getTag();
if (userRecord.isDisabledByAdmin()) {
final Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, userRecord.enforcedAdmin);
+ userRecord.enforcedAdmin);
mController.startActivity(intent);
} else if (userRecord.isSwitchToEnabled) {
MetricsLogger.action(mContext, MetricsEvent.QS_SWITCH_USER);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
index d1f8945..87b89ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt
@@ -96,10 +96,7 @@
is PolicyResult.TileEnabled -> false
is PolicyResult.TileDisabled -> {
val intent =
- RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- context,
- policyResult.admin
- )
+ RestrictedLockUtils.getShowAdminSupportDetailsIntent(policyResult.admin)
activityStarter.postStartActivityDismissingKeyguard(intent, 0)
true
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 0d9b702..8a84496 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -19,6 +19,7 @@
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
+import android.content.pm.LauncherApps
import android.content.res.Resources
import android.net.Uri
import android.os.Handler
@@ -93,6 +94,10 @@
}
ACTION_STOP,
ACTION_STOP_NOTIF -> {
+ // ViewCapture needs to save it's data before it is disabled, or else the data will
+ // be lost. This is expected to change in the near future, and when that happens
+ // this line should be removed.
+ getSystemService(LauncherApps::class.java)?.saveViewCaptureData()
TraceUtils.traceStop(contentResolver)
}
ACTION_SHARE -> {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index f01e9be..1f6d212 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -21,9 +21,7 @@
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
-import android.graphics.drawable.Drawable
import android.util.Log
-import android.view.Display
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.ScrollCaptureResponse
@@ -32,45 +30,53 @@
import android.view.WindowInsets
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
+import androidx.appcompat.content.res.AppCompatResources
import com.android.internal.logging.UiEventLogger
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.res.R
import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
/**
* Legacy implementation of screenshot view methods. Just proxies the calls down into the original
* ScreenshotView.
*/
-class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLogger) :
- ScreenshotViewProxy {
+class LegacyScreenshotViewProxy
+@AssistedInject
+constructor(
+ private val logger: UiEventLogger,
+ flags: FeatureFlags,
+ @Assisted private val context: Context,
+ @Assisted private val displayId: Int
+) : ScreenshotViewProxy {
override val view: ScreenshotView =
LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
override val screenshotPreview: View
-
- override var defaultDisplay: Int = Display.DEFAULT_DISPLAY
- set(value) {
- view.setDefaultDisplay(value)
- }
- override var defaultTimeoutMillis: Long = 6000
- set(value) {
- view.setDefaultTimeoutMillis(value)
- }
- override var flags: FeatureFlags? = null
- set(value) {
- view.setFlags(value)
- }
override var packageName: String = ""
set(value) {
+ field = value
view.setPackageName(value)
}
override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
set(value) {
+ field = value
view.setCallbacks(value)
}
override var screenshot: ScreenshotData? = null
set(value) {
- view.setScreenshot(value)
+ field = value
+ value?.let {
+ val badgeBg =
+ AppCompatResources.getDrawable(context, R.drawable.overlay_badge_background)
+ val user = it.userHandle
+ if (badgeBg != null && user != null) {
+ view.badgeScreenshot(context.packageManager.getUserBadgedIcon(badgeBg, user))
+ }
+ view.setScreenshot(it)
+ }
}
override val isAttachedToWindow
@@ -82,6 +88,8 @@
init {
view.setUiEventLogger(logger)
+ view.setDefaultDisplay(displayId)
+ view.setFlags(flags)
addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
if (LogConfig.DEBUG_WINDOW) {
@@ -95,8 +103,6 @@
override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets)
override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets)
- override fun badgeScreenshot(userBadgedIcon: Drawable) = view.badgeScreenshot(userBadgedIcon)
-
override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator =
view.createScreenshotDropInAnimation(screenRect, showFlash)
@@ -130,14 +136,17 @@
response: ScrollCaptureResponse,
screenBitmap: Bitmap,
newScreenshot: Bitmap,
- screenshotTakenInPortrait: Boolean
- ) =
+ screenshotTakenInPortrait: Boolean,
+ onTransitionPrepared: Runnable,
+ ) {
view.prepareScrollingTransition(
response,
screenBitmap,
newScreenshot,
screenshotTakenInPortrait
)
+ view.post { onTransitionPrepared.run() }
+ }
override fun startLongScreenshotTransition(
transitionDestination: Rect,
@@ -155,10 +164,19 @@
override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
- override fun getViewTreeObserver(): ViewTreeObserver = view.viewTreeObserver
-
- override fun post(runnable: Runnable) {
- view.post(runnable)
+ override fun prepareEntranceAnimation(runnable: Runnable) {
+ view.viewTreeObserver.addOnPreDrawListener(
+ object : ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "onPreDraw: startAnimation")
+ }
+ view.viewTreeObserver.removeOnPreDrawListener(this)
+ runnable.run()
+ return true
+ }
+ }
+ )
}
private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) {
@@ -166,7 +184,7 @@
if (LogConfig.DEBUG_INPUT) {
Log.d(TAG, "Predictive Back callback dispatched")
}
- onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+ onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
}
view.addOnAttachStateChangeListener(
object : View.OnAttachStateChangeListener {
@@ -201,7 +219,7 @@
if (LogConfig.DEBUG_INPUT) {
Log.d(TAG, "onKeyEvent: $keyCode")
}
- onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+ onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
return true
}
return false
@@ -210,10 +228,9 @@
)
}
- class Factory : ScreenshotViewProxy.Factory {
- override fun getProxy(context: Context, logger: UiEventLogger): ScreenshotViewProxy {
- return LegacyScreenshotViewProxy(context, logger)
- }
+ @AssistedFactory
+ interface Factory : ScreenshotViewProxy.Factory {
+ override fun getProxy(context: Context, displayId: Int): LegacyScreenshotViewProxy
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 6bab956..198a29c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -228,7 +228,7 @@
// From WizardManagerHelper.java
private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
- private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
+ static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
private final WindowContext mContext;
private final FeatureFlags mFlags;
@@ -344,7 +344,7 @@
mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
- mViewProxy = viewProxyFactory.getProxy(mContext, mUiEventLogger);
+ mViewProxy = viewProxyFactory.getProxy(mContext, mDisplayId);
mScreenshotHandler.setOnTimeoutRunnable(() -> {
if (DEBUG_UI) {
@@ -460,7 +460,7 @@
attachWindow();
- boolean showFlash = true;
+ boolean showFlash;
if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
if (screenshot.getScreenBounds() != null
&& aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(),
@@ -472,15 +472,14 @@
screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(),
screenshot.getBitmap().getHeight()));
}
+ } else {
+ showFlash = true;
}
- prepareAnimation(screenshot.getScreenBounds(), showFlash, () -> {
- mMessageContainerController.onScreenshotTaken(screenshot);
- });
+ mViewProxy.prepareEntranceAnimation(
+ () -> startAnimation(screenshot.getScreenBounds(), showFlash,
+ () -> mMessageContainerController.onScreenshotTaken(screenshot)));
- mViewProxy.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
- mContext.getDrawable(R.drawable.overlay_badge_background),
- screenshot.getUserHandle()));
mViewProxy.setScreenshot(screenshot);
// ignore system bar insets for the purpose of window layout
@@ -596,9 +595,6 @@
setWindowFocusable(false);
}
});
- mViewProxy.setFlags(mFlags);
- mViewProxy.setDefaultDisplay(mDisplayId);
- mViewProxy.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
if (DEBUG_WINDOW) {
Log.d(TAG, "setContentView: " + mViewProxy.getView());
@@ -606,22 +602,6 @@
setContentView(mViewProxy.getView());
}
- private void prepareAnimation(Rect screenRect, boolean showFlash,
- Runnable onAnimationComplete) {
- mViewProxy.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- if (DEBUG_WINDOW) {
- Log.d(TAG, "onPreDraw: startAnimation");
- }
- mViewProxy.getViewTreeObserver().removeOnPreDrawListener(this);
- startAnimation(screenRect, showFlash, onAnimationComplete);
- return true;
- }
- });
- }
-
private void enqueueScrollCaptureRequest(UserHandle owner) {
// Wait until this window is attached to request because it is
// the reference used to locate the target window (below).
@@ -706,10 +686,14 @@
Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId,
new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
- mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
- mScreenshotTakenInPortrait);
- // delay starting scroll capture to make sure the scrim is up before the app moves
- mViewProxy.post(() -> runBatchScrollCapture(response, owner));
+ if (newScreenshot != null) {
+ // delay starting scroll capture to make sure scrim is up before the app moves
+ mViewProxy.prepareScrollingTransition(
+ response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait,
+ () -> runBatchScrollCapture(response, owner));
+ } else {
+ Log.wtf(TAG, "failed to capture current screenshot for scroll transition");
+ }
});
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "requestScrollCapture failed", e);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 8a8766d..1c5a8a1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -26,6 +26,7 @@
import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
import static com.android.systemui.screenshot.LogConfig.logTag;
+import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS;
import static java.util.Objects.requireNonNull;
@@ -33,6 +34,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.BroadcastOptions;
import android.app.Notification;
@@ -168,7 +170,6 @@
private ScreenshotData mScreenshotData;
private final InteractionJankMonitor mInteractionJankMonitor;
- private long mDefaultTimeoutOfTimeoutHandler;
private FeatureFlags mFlags;
private final Bundle mInteractiveBroadcastOption;
@@ -244,10 +245,6 @@
return InteractionJankMonitor.getInstance();
}
- void setDefaultTimeoutMillis(long timeout) {
- mDefaultTimeoutOfTimeoutHandler = timeout;
- }
-
public void hideScrollChip() {
mScrollChip.setVisibility(View.GONE);
}
@@ -755,7 +752,7 @@
InteractionJankMonitor.Configuration.Builder.withView(
CUJ_TAKE_SCREENSHOT, mScreenshotStatic)
.setTag("Actions")
- .setTimeout(mDefaultTimeoutOfTimeoutHandler);
+ .setTimeout(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
mInteractionJankMonitor.begin(builder);
}
});
@@ -781,7 +778,7 @@
return animator;
}
- void badgeScreenshot(Drawable badge) {
+ void badgeScreenshot(@Nullable Drawable badge) {
mScreenshotBadge.setImageDrawable(badge);
mScreenshotBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index d5c7f95..182b889 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -21,23 +21,16 @@
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
-import android.graphics.drawable.Drawable
import android.view.ScrollCaptureResponse
import android.view.View
import android.view.ViewGroup
-import android.view.ViewTreeObserver
import android.view.WindowInsets
-import com.android.internal.logging.UiEventLogger
-import com.android.systemui.flags.FeatureFlags
/** Abstraction of the surface between ScreenshotController and ScreenshotView */
interface ScreenshotViewProxy {
val view: ViewGroup
val screenshotPreview: View
- var defaultDisplay: Int
- var defaultTimeoutMillis: Long
- var flags: FeatureFlags?
var packageName: String
var callbacks: ScreenshotView.ScreenshotViewCallback?
var screenshot: ScreenshotData?
@@ -49,7 +42,6 @@
fun reset()
fun updateInsets(insets: WindowInsets)
fun updateOrientation(insets: WindowInsets)
- fun badgeScreenshot(userBadgedIcon: Drawable)
fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
fun addQuickShareChip(quickShareAction: Notification.Action)
fun setChipIntents(imageData: ScreenshotController.SavedImageData)
@@ -61,7 +53,8 @@
response: ScrollCaptureResponse,
screenBitmap: Bitmap,
newScreenshot: Bitmap,
- screenshotTakenInPortrait: Boolean
+ screenshotTakenInPortrait: Boolean,
+ onTransitionPrepared: Runnable,
)
fun startLongScreenshotTransition(
transitionDestination: Rect,
@@ -73,10 +66,9 @@
fun stopInputListening()
fun requestFocus()
fun announceForAccessibility(string: String)
- fun getViewTreeObserver(): ViewTreeObserver
- fun post(runnable: Runnable)
+ fun prepareEntranceAnimation(runnable: Runnable)
interface Factory {
- fun getProxy(context: Context, logger: UiEventLogger): ScreenshotViewProxy
+ fun getProxy(context: Context, displayId: Int): ScreenshotViewProxy
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
index 71c2cb4..5df6c45 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java
@@ -40,7 +40,7 @@
private final Context mContext;
private Runnable mOnTimeout;
- private int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS;
+ int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS;
@Inject
public TimeoutHandler(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index a00c81d..cdb9abb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -86,7 +86,8 @@
ScreenshotSoundControllerImpl screenshotSoundProviderImpl);
@Provides
- static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory() {
- return new LegacyScreenshotViewProxy.Factory();
+ static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory(
+ LegacyScreenshotViewProxy.Factory legacyScreenshotViewProxyFactory) {
+ return legacyScreenshotViewProxyFactory;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 861a2ed..539b0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -19,6 +19,7 @@
import static com.android.systemui.Flags.hapticBrightnessSlider;
import android.content.Context;
+import android.content.Intent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -33,6 +34,7 @@
import com.android.systemui.classifier.Classifier;
import com.android.systemui.haptics.slider.HapticSliderViewBinder;
import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.VibratorHelper;
@@ -62,6 +64,7 @@
private final UiEventLogger mUiEventLogger;
private final SeekableSliderHapticPlugin mBrightnessSliderHapticPlugin;
+ private final ActivityStarter mActivityStarter;
private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() {
@Override
@@ -84,11 +87,13 @@
BrightnessSliderView brightnessSliderView,
FalsingManager falsingManager,
UiEventLogger uiEventLogger,
- SeekableSliderHapticPlugin brightnessSliderHapticPlugin) {
+ SeekableSliderHapticPlugin brightnessSliderHapticPlugin,
+ ActivityStarter activityStarter) {
super(brightnessSliderView);
mFalsingManager = falsingManager;
mUiEventLogger = uiEventLogger;
mBrightnessSliderHapticPlugin = brightnessSliderHapticPlugin;
+ mActivityStarter = activityStarter;
}
/**
@@ -131,7 +136,15 @@
@Override
public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
- mView.setEnforcedAdmin(admin);
+ if (admin == null) {
+ mView.setAdminBlocker(null);
+ } else {
+ mView.setAdminBlocker(() -> {
+ Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(admin);
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ return true;
+ });
+ }
}
private void setMirror(ToggleSlider toggleSlider) {
@@ -259,18 +272,21 @@
private final UiEventLogger mUiEventLogger;
private final VibratorHelper mVibratorHelper;
private final SystemClock mSystemClock;
+ private final ActivityStarter mActivityStarter;
@Inject
public Factory(
FalsingManager falsingManager,
UiEventLogger uiEventLogger,
VibratorHelper vibratorHelper,
- SystemClock clock
+ SystemClock clock,
+ ActivityStarter activityStarter
) {
mFalsingManager = falsingManager;
mUiEventLogger = uiEventLogger;
mVibratorHelper = vibratorHelper;
mSystemClock = clock;
+ mActivityStarter = activityStarter;
}
/**
@@ -292,7 +308,8 @@
if (hapticBrightnessSlider()) {
HapticSliderViewBinder.bind(viewRoot, plugin);
}
- return new BrightnessSliderController(root, mFalsingManager, mUiEventLogger, plugin);
+ return new BrightnessSliderController(
+ root, mFalsingManager, mUiEventLogger, plugin, mActivityStarter);
}
/** Get the layout to inflate based on what slider to use */
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
index c43d20c..92006a4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java
@@ -31,7 +31,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.res.R;
@@ -120,9 +119,8 @@
* @param admin
* @see ToggleSeekBar#setEnforcedAdmin
*/
- public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
- mSlider.setEnabled(admin == null);
- mSlider.setEnforcedAdmin(admin);
+ void setAdminBlocker(ToggleSeekBar.AdminBlocker blocker) {
+ mSlider.setAdminBlocker(blocker);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
index a5a0ae7..288ff09 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
@@ -17,20 +17,15 @@
package com.android.systemui.settings.brightness;
import android.content.Context;
-import android.content.Intent;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.SeekBar;
-import com.android.settingslib.RestrictedLockUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.plugins.ActivityStarter;
-
public class ToggleSeekBar extends SeekBar {
private String mAccessibilityLabel;
- private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null;
+ private AdminBlocker mAdminBlocker;
public ToggleSeekBar(Context context) {
super(context);
@@ -46,10 +41,7 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (mEnforcedAdmin != null) {
- Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, mEnforcedAdmin);
- Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
+ if (mAdminBlocker != null && mAdminBlocker.block()) {
return true;
}
if (!isEnabled()) {
@@ -71,7 +63,12 @@
}
}
- public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
- mEnforcedAdmin = admin;
+ void setAdminBlocker(AdminBlocker blocker) {
+ mAdminBlocker = blocker;
+ setEnabled(blocker == null);
+ }
+
+ interface AdminBlocker {
+ boolean block();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b867550..8b791de 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1753,10 +1753,11 @@
}
private void updateKeyguardStatusViewAlignment(boolean animate) {
+ boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
if (migrateClocksToBlueprint()) {
+ mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
return;
}
- boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
ConstraintLayout layout = mNotificationContainerParent;
mKeyguardStatusViewController.updateAlignment(
layout, mSplitShadeEnabled, shouldBeCentered, animate);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 589537e..c05c3c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -353,7 +353,7 @@
nowExpanded = !isExpanded();
setUserExpanded(nowExpanded);
}
- notifyHeightChanged(true);
+ notifyHeightChanged(/* needsAnimation= */ true);
mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_EXPANDER, nowExpanded);
}
@@ -776,7 +776,7 @@
mChildrenContainer.updateGroupOverflow();
}
if (intrinsicBefore != getIntrinsicHeight()) {
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
if (isHeadsUp) {
mMustStayOnScreen = true;
@@ -824,7 +824,7 @@
if (mChildrenContainer != null) {
mChildrenContainer.setHeaderVisibleAmount(headerVisibleAmount);
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
}
@@ -1086,7 +1086,7 @@
boolean wasAboveShelf = isAboveShelf();
mIsPinned = pinned;
if (intrinsicHeight != getIntrinsicHeight()) {
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
if (pinned) {
setAnimationRunning(true);
@@ -2609,7 +2609,7 @@
onExpansionChanged(true /* userAction */, wasExpanded);
if (!wasExpanded && isExpanded()
&& getActualHeight() != getIntrinsicHeight()) {
- notifyHeightChanged(true /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ true);
}
}
@@ -2621,7 +2621,7 @@
if (mIsSummaryWithChildren) {
mChildrenContainer.onExpansionChanged();
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
updateShelfIconColor();
}
@@ -2659,7 +2659,7 @@
if (expand != mIsSystemExpanded) {
final boolean wasExpanded = isExpanded();
mIsSystemExpanded = expand;
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
onExpansionChanged(false /* userAction */, wasExpanded);
if (mIsSummaryWithChildren) {
mChildrenContainer.updateGroupOverflow();
@@ -2678,7 +2678,7 @@
if (mIsSummaryWithChildren) {
mChildrenContainer.updateGroupOverflow();
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
if (isAboveShelf() != wasAboveShelf) {
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
@@ -2835,7 +2835,7 @@
super.onLayout(changed, left, top, right, bottom);
if (intrinsicBefore != getIntrinsicHeight()
&& (intrinsicBefore != 0 || getActualHeight() > 0)) {
- notifyHeightChanged(true /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ true);
}
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
mMenuRow.onParentHeightUpdate();
@@ -2878,7 +2878,7 @@
mSensitiveHiddenInGeneral = hideSensitive;
int intrinsicAfter = getIntrinsicHeight();
if (intrinsicBefore != intrinsicAfter) {
- notifyHeightChanged(true);
+ notifyHeightChanged(/* needsAnimation= */ true);
}
}
@@ -3015,7 +3015,7 @@
if (isChildInGroup()) {
mGroupExpansionManager.setGroupExpanded(mEntry, true);
}
- notifyHeightChanged(false /* needsAnimation */);
+ notifyHeightChanged(/* needsAnimation= */ false);
}
public void setChildrenExpanded(boolean expanded, boolean animate) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index d4960d7..712f65d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -328,7 +328,14 @@
GLANCEABLE_HUB_OVER_DREAM {
@Override
public void prepare(ScrimState previousState) {
- GLANCEABLE_HUB.prepare(previousState);
+ // No scrims should be visible by default in this state.
+ mBehindAlpha = 0;
+ mNotifAlpha = 0;
+ mFrontAlpha = 0;
+
+ mFrontTint = Color.TRANSPARENT;
+ mBehindTint = mBackgroundColor;
+ mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT;
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
index 71df8e5..1bceee9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
@@ -36,3 +36,7 @@
/** Current media state is unknown yet. */
data object Unknown : MediaDeviceSession
}
+
+/** Returns true when the audio is playing for the [MediaDeviceSession]. */
+fun MediaDeviceSession.isPlaying(): Boolean =
+ this is MediaDeviceSession.Active && playbackState?.isActive == true
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 37661b5..d49cb1e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -24,6 +24,7 @@
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import javax.inject.Inject
@@ -110,9 +111,6 @@
null,
)
- private fun MediaDeviceSession.isPlaying(): Boolean =
- this is MediaDeviceSession.Active && playbackState?.isActive == true
-
fun onBarClick(expandable: Expandable) {
actionsInteractor.onBarClick(mediaDeviceSession.value, expandable)
volumePanelViewModel.dismissPanel()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index faf7434..532e517 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -21,6 +21,7 @@
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor
@@ -54,14 +55,6 @@
AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer,
AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm,
)
- private val mutedIconsByStream =
- mapOf(
- AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_off,
- AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_off,
- )
private val labelsByStream =
mapOf(
AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music,
@@ -74,6 +67,8 @@
mapOf(
AudioStream(AudioManager.STREAM_NOTIFICATION) to
R.string.stream_notification_unavailable,
+ AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm_unavailable,
+ AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable,
)
private var value = 0f
@@ -81,8 +76,9 @@
combine(
audioVolumeInteractor.getAudioStream(audioStream),
audioVolumeInteractor.canChangeVolume(audioStream),
- ) { model, isEnabled ->
- model.toState(value, isEnabled)
+ audioVolumeInteractor.ringerMode,
+ ) { model, isEnabled, ringerMode ->
+ model.toState(value, isEnabled, ringerMode)
}
.stateIn(coroutineScope, SharingStarted.Eagerly, EmptyState)
@@ -100,7 +96,11 @@
}
}
- private fun AudioStreamModel.toState(value: Float, isEnabled: Boolean): State {
+ private fun AudioStreamModel.toState(
+ value: Float,
+ isEnabled: Boolean,
+ ringerMode: RingerMode,
+ ): State {
return State(
value =
volumeSliderInteractor.processVolumeToValue(
@@ -110,7 +110,7 @@
isMuted,
),
valueRange = volumeSliderInteractor.displayValueRange,
- icon = getIcon(this),
+ icon = getIcon(ringerMode),
label = labelsByStream[audioStream]?.let(context::getString)
?: error("No label for the stream: $audioStream"),
disabledMessage = disabledTextByStream[audioStream]?.let(context::getString),
@@ -119,14 +119,31 @@
)
}
- private fun getIcon(model: AudioStreamModel): Icon {
- val isMutedOrNoVolume = model.isMuted || model.volume == model.minVolume
+ private fun AudioStreamModel.getIcon(ringerMode: RingerMode): Icon {
+ val isMutedOrNoVolume = isMuted || volume == minVolume
val iconRes =
if (isMutedOrNoVolume) {
- mutedIconsByStream
+ when (audioStream.value) {
+ AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off
+ AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off
+ AudioManager.STREAM_RING ->
+ if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+ R.drawable.ic_volume_ringer_vibrate
+ } else {
+ R.drawable.ic_volume_off
+ }
+ AudioManager.STREAM_NOTIFICATION ->
+ if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
+ R.drawable.ic_volume_ringer_vibrate
+ } else {
+ R.drawable.ic_volume_off
+ }
+ AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off
+ else -> null
+ }
} else {
- iconsByStream
- }[audioStream]
+ iconsByStream[audioStream]
+ }
?: error("No icon for the stream: $audioStream")
return Icon.Resource(iconRes, null)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 2824323..aaee24b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -18,6 +18,8 @@
import android.media.AudioManager
import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying
import com.android.systemui.volume.panel.component.volume.domain.interactor.CastVolumeInteractor
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
@@ -28,12 +30,17 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
+import kotlinx.coroutines.launch
/**
* Controls the behaviour of the whole audio
@@ -46,6 +53,7 @@
constructor(
@VolumePanelScope private val scope: CoroutineScope,
castVolumeInteractor: CastVolumeInteractor,
+ mediaOutputInteractor: MediaOutputInteractor,
private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
) {
@@ -90,4 +98,17 @@
remoteSessionsViewModels + streamViewModels
}
.stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+ private val mutableIsExpanded = MutableSharedFlow<Boolean>()
+
+ val isExpanded: StateFlow<Boolean> =
+ merge(
+ mutableIsExpanded.onStart { emit(false) },
+ mediaOutputInteractor.mediaDeviceSession.map { !it.isPlaying() },
+ )
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ fun onExpandedChanged(isExpanded: Boolean) {
+ scope.launch { mutableIsExpanded.emit(isExpanded) }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 10b86ea..2b4e9ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -20,7 +20,6 @@
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricManager
-import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.PromptVerticalListContentView
import android.hardware.face.FaceSensorPropertiesInternal
@@ -386,7 +385,6 @@
@Test
fun testShowCredentialUI_withDescription() {
- mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val container = initializeFingerprintContainer(
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
@@ -397,6 +395,7 @@
}
@Test
+ @Ignore("b/302735104")
fun testShowCredentialUI_withCustomBp() {
val container = initializeFingerprintContainer(
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index 7b972d3..81d4e83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -17,9 +17,11 @@
package com.android.systemui.biometrics.data.repository
import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.PromptVerticalListContentView
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.PromptKind
@@ -135,6 +137,8 @@
@Test
fun showBpWithoutIconForCredential_withCustomBp() =
testScope.runTest {
+ mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP)
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
for (case in
listOf(
PromptKind.Biometric(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 140849b..1aab9e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -35,6 +35,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.Flags.FLAG_BP_TALKBACK
+import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.UdfpsUtils
@@ -1256,6 +1257,7 @@
fun descriptionOverriddenByContentView() =
runGenericTest(contentView = promptContentView, description = "test description") {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1267,6 +1269,7 @@
fun descriptionWithoutContentView() =
runGenericTest(description = "test description") {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1278,6 +1281,7 @@
fun logoIsNullIfPackageNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isNull()
}
@@ -1285,6 +1289,7 @@
@Test
fun defaultLogoIfNoLogoSet() = runGenericTest {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(defaultLogoIcon)
}
@@ -1293,6 +1298,7 @@
fun logoResSetByApp() =
runGenericTest(logoRes = logoResFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(logoFromApp)
}
@@ -1301,6 +1307,7 @@
fun logoBitmapSetByApp() =
runGenericTest(logoBitmap = logoBitmapFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logo by collectLastValue(viewModel.logo)
assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
}
@@ -1309,6 +1316,7 @@
fun logoDescriptionIsEmptyIfPackageNameNotFound() =
runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo("")
}
@@ -1316,6 +1324,7 @@
@Test
fun defaultLogoDescriptionIfNoLogoDescriptionSet() = runGenericTest {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo(defaultLogoDescription)
}
@@ -1324,6 +1333,7 @@
fun logoDescriptionSetByApp() =
runGenericTest(logoDescription = logoDescriptionFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index e796303..701b703 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -401,7 +401,7 @@
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair("Enter PIN", "PIN is required after lockdown"),
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- Pair("Enter PIN", "Update will install when device not in use"),
+ Pair("Enter PIN", "PIN required for additional security"),
LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(
@@ -439,7 +439,7 @@
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair("Enter PIN", "PIN is required after lockdown"),
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- Pair("Enter PIN", "Update will install when device not in use"),
+ Pair("Enter PIN", "PIN required for additional security"),
LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(
@@ -481,7 +481,7 @@
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair("Enter PIN", "PIN is required after lockdown"),
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
- Pair("Enter PIN", "Update will install when device not in use"),
+ Pair("Enter PIN", "PIN required for additional security"),
LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index e1e9fcb..dac88a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -532,6 +532,18 @@
assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
}
+ @Test
+ fun lockedOut_providesSameValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.lockedOut).isSameInstanceAs(faceAuthRepository.isLockedOut)
+ }
+
+ @Test
+ fun authenticated_providesSameValueFromRepository() =
+ testScope.runTest {
+ assertThat(underTest.authenticated).isSameInstanceAs(faceAuthRepository.isAuthenticated)
+ }
+
companion object {
private const val primaryUserId = 1
private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt
new file mode 100644
index 0000000..decbdaf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryFingerprintAuthInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.deviceEntryFingerprintAuthInteractor
+ private val fingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
+ private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+
+ @Test
+ fun isFingerprintAuthCurrentlyAllowed_allowedOnlyWhenItIsNotLockedOutAndAllowedBySettings() =
+ testScope.runTest {
+ val currentlyAllowed by collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed)
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true)
+ fingerprintAuthRepository.setLockedOut(true)
+
+ assertThat(currentlyAllowed).isFalse()
+
+ fingerprintAuthRepository.setLockedOut(false)
+ assertThat(currentlyAllowed).isTrue()
+
+ biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
+ assertThat(currentlyAllowed).isFalse()
+ }
+
+ @Test
+ fun isSensorUnderDisplay_trueForUdfpsSensorTypes() =
+ testScope.runTest {
+ val isSensorUnderDisplay by collectLastValue(underTest.isSensorUnderDisplay)
+
+ fingerprintPropertyRepository.supportsUdfps()
+ assertThat(isSensorUnderDisplay).isTrue()
+
+ fingerprintPropertyRepository.supportsRearFps()
+ assertThat(isSensorUnderDisplay).isFalse()
+
+ fingerprintPropertyRepository.supportsSideFps()
+ assertThat(isSensorUnderDisplay).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 65ede89..0101741 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -51,6 +51,7 @@
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.util.animation.DisappearParameters;
@@ -100,6 +101,8 @@
Configuration mConfiguration;
@Mock
Runnable mHorizontalLayoutListener;
+ @Mock
+ VibratorHelper mVibratorHelper;
private QSPanelControllerBase<QSPanel> mController;
@@ -110,7 +113,8 @@
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
DumpManager dumpManager) {
super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
- qsLogger, dumpManager, new ResourcesSplitShadeStateController());
+ qsLogger, dumpManager, new ResourcesSplitShadeStateController(),
+ mVibratorHelper);
}
@Override
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 85d7d98..916e8dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -19,6 +19,7 @@
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.settings.brightness.BrightnessController
import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.tuner.TunerService
@@ -61,6 +62,7 @@
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var configuration: Configuration
@Mock private lateinit var pagedTileLayout: PagedTileLayout
+ @Mock private lateinit var vibratorHelper: VibratorHelper
private val sceneContainerFlags = FakeSceneContainerFlags()
@@ -101,6 +103,7 @@
statusBarKeyguardViewManager,
ResourcesSplitShadeStateController(),
sceneContainerFlags,
+ vibratorHelper,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 2c14308..71a9a8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.customize.QSCustomizerController
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.util.leak.RotationUtils
import org.junit.After
@@ -59,6 +60,7 @@
@Mock private lateinit var tile: QSTile
@Mock private lateinit var tileLayout: TileLayout
@Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+ @Mock private lateinit var vibratorHelper: VibratorHelper
private val uiEventLogger = UiEventLoggerFake()
private val dumpManager = DumpManager()
@@ -89,7 +91,8 @@
metricsLogger,
uiEventLogger,
qsLogger,
- dumpManager
+ dumpManager,
+ vibratorHelper,
)
controller.init()
@@ -157,7 +160,8 @@
metricsLogger: MetricsLogger,
uiEventLogger: UiEventLoggerFake,
qsLogger: QSLogger,
- dumpManager: DumpManager
+ dumpManager: DumpManager,
+ vibratorHelper: VibratorHelper,
) :
QuickQSPanelController(
view,
@@ -170,7 +174,8 @@
uiEventLogger,
qsLogger,
dumpManager,
- ResourcesSplitShadeStateController()
+ ResourcesSplitShadeStateController(),
+ vibratorHelper,
) {
private var rotation = RotationUtils.ROTATION_NONE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index ab90b9b..25ba09a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.BrightnessMirrorController
import com.android.systemui.util.mockito.any
@@ -66,6 +67,8 @@
private lateinit var listener: ToggleSlider.Listener
@Mock
private lateinit var vibratorHelper: VibratorHelper
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
@Captor
private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
@@ -91,6 +94,7 @@
mFalsingManager,
uiEventLogger,
SeekableSliderHapticPlugin(vibratorHelper, systemClock),
+ activityStarter,
)
mController.init()
mController.setOnChangedListener(listener)
@@ -120,7 +124,7 @@
@Test
fun testEnforceAdminRelayed() {
mController.setEnforcedAdmin(enforcedAdmin)
- verify(brightnessSliderView).setEnforcedAdmin(enforcedAdmin)
+ verify(brightnessSliderView).setAdminBlocker(notNull())
}
@Test
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 cdbbc93..f947640 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
@@ -747,14 +747,16 @@
// Open the bouncer.
mScrimController.setRawPanelExpansionFraction(0f);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_VISIBLE);
finishAnimationsImmediately();
- // Only behind widget is visible.
+ // Only behind scrim is visible.
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mNotificationsScrim, TRANSPARENT,
mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, mSurfaceColor);
// Bouncer is closed.
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -773,8 +775,9 @@
mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
// Open the shade.
- mScrimController.transitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
+ mScrimController.setRawPanelExpansionFraction(1);
+ mScrimController.setTransitionToFullShadeProgress(1, 0);
finishAnimationsImmediately();
// Shade scrims are visible.
@@ -782,8 +785,10 @@
mNotificationsScrim, OPAQUE,
mScrimInFront, TRANSPARENT,
mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, Color.BLACK);
+ assertScrimTint(mNotificationsScrim, Color.TRANSPARENT);
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB);
+ mScrimController.setTransitionToFullShadeProgress(0, 0);
finishAnimationsImmediately();
// All scrims are transparent.
@@ -813,14 +818,16 @@
// Open the bouncer.
mScrimController.setRawPanelExpansionFraction(0f);
+ when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_VISIBLE);
finishAnimationsImmediately();
- // Only behind widget is visible.
+ // Only behind scrim is visible.
assertScrimAlpha(Map.of(
mScrimInFront, TRANSPARENT,
mNotificationsScrim, TRANSPARENT,
mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, mSurfaceColor);
// Bouncer is closed.
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
@@ -839,7 +846,6 @@
mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
// Open the shade.
- mScrimController.transitionTo(SHADE_LOCKED);
mScrimController.setQsPosition(1f, 0);
mScrimController.setRawPanelExpansionFraction(1f);
finishAnimationsImmediately();
@@ -849,8 +855,11 @@
mNotificationsScrim, OPAQUE,
mScrimInFront, TRANSPARENT,
mScrimBehind, OPAQUE));
+ assertScrimTint(mScrimBehind, Color.BLACK);
+ assertScrimTint(mNotificationsScrim, Color.TRANSPARENT);
- mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM);
+ mScrimController.setQsPosition(0f, 0);
+ mScrimController.setRawPanelExpansionFraction(0f);
finishAnimationsImmediately();
// All scrims are transparent.
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 974e396..5206db4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -95,6 +95,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -292,6 +293,7 @@
assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_DISABLED, type);
}
+ @Ignore("Causing breakages so ignoring to resolve, b/329099861")
@Test
@EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER)
public void testVolumeChange_withSliderHaptics_deliversOnProgressChangedHapticsEagerly() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
index 6dd8d07..0660d00 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
@@ -18,6 +18,7 @@
import com.android.systemui.classifier.FakeClassifierModule
import com.android.systemui.data.FakeSystemUiDataLayerModule
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.flags.FakeSystemPropertiesHelperModule
import com.android.systemui.log.FakeUiEventLoggerModule
import com.android.systemui.settings.FakeSettingsModule
import com.android.systemui.statusbar.policy.FakeConfigurationControllerModule
@@ -33,6 +34,7 @@
FakeConfigurationControllerModule::class,
FakeExecutorModule::class,
FakeFeatureFlagsClassicModule::class,
+ FakeSystemPropertiesHelperModule::class,
FakeSettingsModule::class,
FakeSplitShadeStateControllerModule::class,
FakeSystemClockModule::class,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index 69b769e..bc0bf9d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -27,6 +27,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
import com.android.systemui.scene.SceneContainerFrameworkModule
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneDataSource
@@ -56,6 +59,7 @@
CoroutineTestScopeModule::class,
FakeSystemUiModule::class,
SceneContainerFrameworkModule::class,
+ FaceWakeUpTriggersConfigModule::class,
]
)
interface SysUITestModule {
@@ -69,6 +73,11 @@
@Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor
@Binds fun bindSceneDataSource(delegator: SceneDataSourceDelegator): SceneDataSource
+ @Binds
+ fun provideFaceAuthInteractor(
+ sysUIFaceAuthInteractor: SystemUIDeviceEntryFaceAuthInteractor
+ ): DeviceEntryFaceAuthInteractor
+
companion object {
@Provides
fun provideSysuiTestableContext(test: SysuiTestCase): SysuiTestableContext = test.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 62a1aa9..3d84291 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -17,6 +17,7 @@
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
+import android.app.trust.TrustManager
import android.os.UserManager
import android.service.notification.NotificationListenerService
import android.util.DisplayMetrics
@@ -27,6 +28,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardViewController
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
@@ -36,6 +38,7 @@
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.BiometricLog
import com.android.systemui.log.dagger.BroadcastDispatcherLog
+import com.android.systemui.log.dagger.FaceAuthLog
import com.android.systemui.log.dagger.SceneFrameworkLog
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.model.SysUiState
@@ -65,10 +68,12 @@
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.ZenModeController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.GlobalSettings
import com.android.wm.shell.bubbles.Bubbles
import dagger.Binds
import dagger.Module
@@ -123,6 +128,10 @@
@get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(),
@get:Provides val communalInteractor: CommunalInteractor = mock(),
@get:Provides val sceneLogger: SceneLogger = mock(),
+ @get:Provides val trustManager: TrustManager = mock(),
+ @get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
+ @get:Provides val keyguardStateController: KeyguardStateController = mock(),
+ @get:Provides val globalSettings: GlobalSettings = mock(),
// log buffers
@get:[Provides BroadcastDispatcherLog]
@@ -131,6 +140,8 @@
val sceneLogBuffer: LogBuffer = mock(),
@get:[Provides BiometricLog]
val biometricLogger: LogBuffer = mock(),
+ @get:[Provides FaceAuthLog]
+ val faceAuthLogger: LogBuffer = mock(),
@get:Provides val lsShadeTransitionLogger: LSShadeTransitionLogger = mock(),
// framework mocks
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
index 68ef555..8a95136 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
@@ -19,10 +19,15 @@
import android.graphics.Point
import com.android.systemui.biometrics.shared.model.LockoutMode
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-class FakeFacePropertyRepository : FacePropertyRepository {
+@SysUISingleton
+class FakeFacePropertyRepository @Inject constructor() : FacePropertyRepository {
private val faceSensorInfo = MutableStateFlow<FaceSensorInfo?>(null)
override val sensorInfo: StateFlow<FaceSensorInfo?>
get() = faceSensorInfo
@@ -56,3 +61,8 @@
currentCameraInfo.value = value
}
}
+
+@Module
+interface FakeFacePropertyRepositoryModule {
+ @Binds fun bindFake(fake: FakeFacePropertyRepository): FacePropertyRepository
+}
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 bd30fb4..60d61ac 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
@@ -68,6 +68,16 @@
)
}
+ /** setProperties as if the device supports POWER_BUTTON fingerprint sensor. */
+ fun supportsSideFps() {
+ setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.POWER_BUTTON,
+ sensorLocations = emptyMap(),
+ )
+ }
+
/** setProperties as if the device supports the rear fingerprint sensor. */
fun supportsRearFps() {
setProperties(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index c3af437..2e2cf9a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -78,6 +78,7 @@
val hasCredentialViewShown = kind.value !is PromptKind.Biometric
val showBpForCredential =
Flags.customBiometricPrompt() &&
+ com.android.systemui.Flags.constraintBp() &&
!Utils.isBiometricAllowed(promptInfo) &&
Utils.isDeviceCredentialAllowed(promptInfo) &&
promptInfo.contentView != null
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
index 1c8190e..fbb8ea2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.deviceentry.data
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepositoryModule
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepositoryModule
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule
import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule
import com.android.systemui.display.data.repository.FakeDisplayRepositoryModule
@@ -35,6 +36,7 @@
FakeDisplayRepositoryModule::class,
FakeDisplayStateRepositoryModule::class,
FakeFingerprintPropertyRepositoryModule::class,
+ FakeFacePropertyRepositoryModule::class,
FakeTrustRepositoryModule::class,
]
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
index 66c6f86..ebed922 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt
@@ -18,6 +18,7 @@
package com.android.systemui.deviceentry.domain.interactor
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.kosmos.Kosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -25,6 +26,8 @@
val Kosmos.deviceEntryFingerprintAuthInteractor by
Kosmos.Fixture {
DeviceEntryFingerprintAuthInteractor(
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
repository = deviceEntryFingerprintAuthRepository,
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index 0d1a31f..e73e295 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -18,8 +18,8 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
-import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.flags.fakeSystemPropertiesHelper
+import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -34,9 +34,12 @@
repository = deviceEntryRepository,
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
- deviceEntryFaceAuthRepository = deviceEntryFaceAuthRepository,
- trustRepository = trustRepository,
+ faceAuthInteractor = deviceEntryFaceAuthInteractor,
+ trustInteractor = trustInteractor,
flags = sceneContainerFlags,
deviceUnlockedInteractor = deviceUnlockedInteractor,
+ fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ systemPropertiesHelper = fakeSystemPropertiesHelper,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt
new file mode 100644
index 0000000..2f30d34
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+
+@SysUISingleton
+class FakeSystemPropertiesHelper @Inject constructor() : SystemPropertiesHelper() {
+ private val fakeProperties = mutableMapOf<String, Any>()
+
+ override fun get(name: String): String {
+ return fakeProperties[name] as String
+ }
+
+ override fun get(name: String, def: String?): String {
+ return checkNotNull(fakeProperties[name] as String? ?: def)
+ }
+
+ override fun getBoolean(name: String, default: Boolean): Boolean {
+ return fakeProperties[name] as Boolean? ?: default
+ }
+
+ override fun setBoolean(name: String, value: Boolean) {
+ fakeProperties[name] = value
+ }
+
+ override fun set(name: String, value: String) {
+ fakeProperties[name] = value
+ }
+
+ override fun set(name: String, value: Int) {
+ fakeProperties[name] = value
+ }
+
+ override fun erase(name: String) {
+ fakeProperties.remove(name)
+ }
+}
+
+@Module
+interface FakeSystemPropertiesHelperModule {
+ @Binds fun bindFake(fake: FakeSystemPropertiesHelper): SystemPropertiesHelper
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
index 365d97f..d6f2f77 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt
@@ -62,6 +62,7 @@
}
val Kosmos.systemPropertiesHelper by Kosmos.Fixture { SystemPropertiesHelper() }
+val Kosmos.fakeSystemPropertiesHelper by Kosmos.Fixture { FakeSystemPropertiesHelper() }
var Kosmos.serverFlagReader: ServerFlagReader by Kosmos.Fixture { serverFlagReaderFake }
val Kosmos.serverFlagReaderFake by Kosmos.Fixture { ServerFlagReaderFake() }
var Kosmos.restarter: Restarter by Kosmos.Fixture { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt
new file mode 100644
index 0000000..0ebf164
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.trustRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.trustInteractor by Fixture { TrustInteractor(repository = trustRepository) }
diff --git a/packages/Tethering/OWNERS b/packages/Tethering/OWNERS
deleted file mode 100644
index aa87958..0000000
--- a/packages/Tethering/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/net/OWNERS
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 10e6ed4..3239175 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -59,6 +59,8 @@
import android.hardware.camera2.extension.SizeList;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
+import android.hardware.camera2.params.ColorSpaceProfiles;
+import android.hardware.camera2.params.DynamicRangeProfiles;
import android.hardware.camera2.utils.SurfaceUtils;
import android.media.Image;
import android.media.ImageReader;
@@ -1228,7 +1230,6 @@
return null;
}
-
}
private class CaptureCallbackStub implements SessionProcessorImpl.CaptureCallback {
@@ -1585,11 +1586,13 @@
Camera2SessionConfigImpl sessionConfig;
if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+ int outputsColorSpace = getColorSpaceFromOutputSurfaces(previewSurface,
+ imageCaptureSurface, postviewSurface);
OutputSurfaceConfigurationImplStub outputSurfaceConfigs =
new OutputSurfaceConfigurationImplStub(mOutputPreviewSurfaceImpl,
// Image Analysis Output is currently only supported in CameraX
mOutputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/,
- mOutputPostviewSurfaceImpl);
+ mOutputPostviewSurfaceImpl, outputsColorSpace);
sessionConfig = mSessionProcessor.initSession(cameraId,
getCharacteristicsMap(charsMapNative),
@@ -1616,6 +1619,11 @@
}
ret.outputConfigs.add(entry);
}
+ if (Flags.extension10Bit() && EFV_SUPPORTED) {
+ ret.colorSpace = sessionConfig.getColorSpace();
+ } else {
+ ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+ }
ret.sessionTemplateId = sessionConfig.getSessionTemplateId();
ret.sessionType = -1;
if (LATENCY_IMPROVEMENTS_SUPPORTED) {
@@ -1720,6 +1728,24 @@
public void binderDied() {
mSessionProcessor.deInitSession();
}
+
+ // Get the color space of the output configurations. All of the OutputSurfaces
+ // can be assumed to have the same color space so return the color space
+ // of any non-null OutputSurface
+ private int getColorSpaceFromOutputSurfaces(OutputSurface previewSurface,
+ OutputSurface imageCaptureSurface, OutputSurface postviewSurface) {
+ int colorSpace = ColorSpaceProfiles.UNSPECIFIED;
+
+ if (previewSurface.surface != null) {
+ colorSpace = previewSurface.colorSpace;
+ } else if (imageCaptureSurface.surface != null) {
+ colorSpace = imageCaptureSurface.colorSpace;
+ } else if (postviewSurface.surface != null) {
+ colorSpace = postviewSurface.colorSpace;
+ }
+
+ return colorSpace;
+ }
}
private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl {
@@ -1727,6 +1753,17 @@
private OutputSurfaceImpl mOutputImageCaptureSurfaceImpl;
private OutputSurfaceImpl mOutputImageAnalysisSurfaceImpl;
private OutputSurfaceImpl mOutputPostviewSurfaceImpl;
+ private int mColorSpace;
+
+ public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput,
+ OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput,
+ OutputSurfaceImpl postviewOutput, int colorSpace) {
+ mOutputPreviewSurfaceImpl = previewOutput;
+ mOutputImageCaptureSurfaceImpl = imageCaptureOutput;
+ mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput;
+ mOutputPostviewSurfaceImpl = postviewOutput;
+ mColorSpace = colorSpace;
+ }
public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput,
OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput,
@@ -1735,6 +1772,7 @@
mOutputImageCaptureSurfaceImpl = imageCaptureOutput;
mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput;
mOutputPostviewSurfaceImpl = postviewOutput;
+ mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
}
@Override
@@ -1756,6 +1794,11 @@
public OutputSurfaceImpl getPostviewOutputSurface() {
return mOutputPostviewSurfaceImpl;
}
+
+ @Override
+ public int getColorSpace() {
+ return mColorSpace;
+ }
}
private class OutputSurfaceImplStub implements OutputSurfaceImpl {
@@ -1764,11 +1807,10 @@
private final int mImageFormat;
private final int mDataspace;
private final long mUsage;
+ private final long mDynamicRangeProfile;
public OutputSurfaceImplStub(OutputSurface outputSurface) {
mSurface = outputSurface.surface;
- mSize = new Size(outputSurface.size.width, outputSurface.size.height);
- mImageFormat = outputSurface.imageFormat;
if (mSurface != null) {
mDataspace = SurfaceUtils.getSurfaceDataspace(mSurface);
mUsage = SurfaceUtils.getSurfaceUsage(mSurface);
@@ -1776,6 +1818,9 @@
mDataspace = -1;
mUsage = 0;
}
+ mDynamicRangeProfile = outputSurface.dynamicRangeProfile;
+ mSize = new Size(outputSurface.size.width, outputSurface.size.height);
+ mImageFormat = outputSurface.imageFormat;
}
@Override
@@ -1802,6 +1847,12 @@
public long getUsage() {
return mUsage;
}
+
+ @Override
+ public long getDynamicRangeProfile() {
+ return mDynamicRangeProfile;
+ }
+
}
private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements
@@ -2531,6 +2582,11 @@
private static CameraOutputConfig getCameraOutputConfig(Camera2OutputConfigImpl output) {
CameraOutputConfig ret = new CameraOutputConfig();
+ if (Flags.extension10Bit() && EFV_SUPPORTED) {
+ ret.dynamicRangeProfile = output.getDynamicRangeProfile();
+ } else {
+ ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+ }
ret.outputId = new OutputConfigId();
ret.outputId.id = output.getId();
ret.physicalCameraId = output.getPhysicalCameraId();
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index d9e25ef..e13994e 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -525,8 +525,9 @@
mReceivedPointersDown |= pointerFlag;
mReceivedPointers[pointerId].set(
event.getX(pointerIndex), event.getY(pointerIndex), event.getEventTime());
-
- mPrimaryPointerId = pointerId;
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mPrimaryPointerId = pointerId;
+ }
}
/**
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index ca2a3dd..a55b8d0 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -171,6 +171,7 @@
import android.util.TimeUtils;
import android.view.KeyEvent;
import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillFeatureFlags;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillManager.AutofillCommitReason;
import android.view.autofill.AutofillManager.SmartSuggestionMode;
@@ -1498,7 +1499,7 @@
mSessionCommittedEventLogger.maybeSetComponentPackageUid(uid);
mSaveEventLogger = SaveEventLogger.forSessionId(sessionId);
mIsPrimaryCredential = isPrimaryCredential;
- mIgnoreViewStateResetToEmpty = Flags.ignoreViewStateResetToEmpty();
+ mIgnoreViewStateResetToEmpty = AutofillFeatureFlags.shouldIgnoreViewStateResetToEmpty();
synchronized (mLock) {
mSessionFlags = new SessionFlags();
diff --git a/services/backup/BACKUP_OWNERS b/services/backup/BACKUP_OWNERS
index f8f4f4f..29ae202 100644
--- a/services/backup/BACKUP_OWNERS
+++ b/services/backup/BACKUP_OWNERS
@@ -2,9 +2,10 @@
jstemmer@google.com
martinoh@google.com
-millmore@google.com
niamhfw@google.com
piee@google.com
philippov@google.com
rthakohov@google.com
-sarpm@google.com
\ No newline at end of file
+sarpm@google.com
+beatricemarch@google.com
+azilio@google.com
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index 82ab098..340bc32 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -38,7 +38,8 @@
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
- ]
+ ],
+ "keywords": ["primary-device"]
},
{
"name": "CtsHardwareTestCases",
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 7d8aad7..ecd14ce 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -24,6 +24,7 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
@@ -79,7 +80,7 @@
Trace.beginSection(
"SensitiveContentProtectionManagerService.onProjectionStart");
try {
- onProjectionStart(info);
+ onProjectionStart(info.getPackageName());
} finally {
Trace.endSection();
}
@@ -124,14 +125,6 @@
}
}
- // These packages are exempted from screen share protection.
- private ArraySet<String> getExemptedPackages() {
- final ArraySet<String> exemptedPackages =
- SystemConfig.getInstance().getBugreportWhitelistedPackages();
- // TODO(b/323361046) - Add sys ui recorder package.
- return exemptedPackages;
- }
-
@VisibleForTesting
void init(MediaProjectionManager projectionManager, WindowManagerInternal windowManager,
ArraySet<String> exemptedPackages) {
@@ -179,9 +172,22 @@
}
}
- private void onProjectionStart(MediaProjectionInfo info) {
- if (mExemptedPackages != null && mExemptedPackages.contains(info.getPackageName())) {
- Log.w(TAG, info.getPackageName() + " is exempted from screen share protection.");
+ private boolean canRecordSensitiveContent(@NonNull String packageName) {
+ return getContext().getPackageManager()
+ .checkPermission(android.Manifest.permission.RECORD_SENSITIVE_CONTENT,
+ packageName) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ // These packages are exempted from screen share protection.
+ private ArraySet<String> getExemptedPackages() {
+ return SystemConfig.getInstance().getBugreportWhitelistedPackages();
+ }
+
+ private void onProjectionStart(String packageName) {
+ // exempt on device screen recorder as well.
+ if ((mExemptedPackages != null && mExemptedPackages.contains(packageName))
+ || canRecordSensitiveContent(packageName)) {
+ Log.w(TAG, packageName + " is exempted from screen share protection.");
return;
}
// TODO(b/324447419): move GlobalSettings lookup to background thread
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 130a733..1334a95 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -113,6 +113,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import com.android.modules.expresslog.Histogram;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
@@ -284,6 +285,11 @@
private static AtomicReference<AccountManagerService> sThis = new AtomicReference<>();
private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
+ private static Histogram sResponseLatency = new Histogram(
+ "app.value_high_authenticator_response_latency",
+ new Histogram.ScaledRangeOptions(20, 10000, 10000, 1.5f)
+ );
+
/**
* This should only be called by system code. One should only call this after the service
* has started.
@@ -4937,6 +4943,9 @@
protected boolean mCanStartAccountManagerActivity = false;
protected final UserAccounts mAccounts;
+ private int mAuthenticatorUid;
+ private long mBindingStartTime;
+
public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName,
boolean authDetailsRequired) {
@@ -4974,6 +4983,10 @@
}
IAccountManagerResponse getResponseAndClose() {
+ if (mAuthenticatorUid != 0 && mBindingStartTime > 0) {
+ sResponseLatency.logSampleWithUid(mAuthenticatorUid,
+ SystemClock.uptimeMillis() - mBindingStartTime);
+ }
if (mResponse == null) {
close();
return null;
@@ -5353,7 +5366,8 @@
mContext.unbindService(this);
return false;
}
-
+ mAuthenticatorUid = authenticatorInfo.uid;
+ mBindingStartTime = SystemClock.uptimeMillis();
return true;
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e0c2425..14428c4 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -157,7 +157,7 @@
* corresponding peers in case of BLE
*/
void addAudioDeviceInInventoryIfNeeded(int deviceType, String address, String peerAddress,
- @AudioDeviceCategory int category) {
+ @AudioDeviceCategory int category, boolean userDefined) {
if (!isBluetoothOutDevice(deviceType)) {
return;
}
@@ -167,7 +167,11 @@
ads = findBtDeviceStateForAddress(peerAddress, deviceType);
}
if (ads != null) {
- if (ads.getAudioDeviceCategory() != category) {
+ // if category is user defined allow to change back to unknown otherwise
+ // do not reset the category back to unknown since it might have been set
+ // before by the user
+ if (ads.getAudioDeviceCategory() != category && (userDefined
+ || category != AUDIO_DEVICE_CATEGORY_UNKNOWN)) {
ads.setAudioDeviceCategory(category);
mDeviceBroker.postUpdatedAdiDeviceState(ads);
mDeviceBroker.postPersistAudioDeviceSettings();
@@ -220,9 +224,9 @@
void addAudioDeviceWithCategoryInInventoryIfNeeded(@NonNull String address,
@AudioDeviceCategory int btAudioDeviceCategory) {
addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLE_HEADSET,
- address, "", btAudioDeviceCategory);
+ address, "", btAudioDeviceCategory, /*userDefined=*/true);
addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLUETOOTH_A2DP,
- address, "", btAudioDeviceCategory);
+ address, "", btAudioDeviceCategory, /*userDefined=*/true);
}
@AudioDeviceCategory
@@ -1733,7 +1737,7 @@
purgeDevicesRoles_l();
} else {
addAudioDeviceInInventoryIfNeeded(device, address, "",
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
}
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"SCO " + (AudioSystem.isInputDevice(device) ? "source" : "sink")
@@ -2023,7 +2027,7 @@
updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
addAudioDeviceInInventoryIfNeeded(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, "",
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
}
static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
@@ -2357,7 +2361,7 @@
DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/);
addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_HEARING_AID, address, "",
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
.set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
.set(MediaMetrics.Property.DEVICE,
@@ -2488,7 +2492,7 @@
mDeviceBroker.postAccessoryPlugMediaUnmute(device);
setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
addAudioDeviceInInventoryIfNeeded(device, address, peerAddress,
- BtHelper.getBtDeviceCategory(address));
+ BtHelper.getBtDeviceCategory(address), /*userDefined=*/false);
}
if (streamType == AudioSystem.STREAM_DEFAULT) {
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 48bf9f4..e915688 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -791,14 +791,12 @@
private void registerAuthenticators() {
BiometricHandlerProvider handlerProvider = mInjector.getBiometricHandlerProvider();
- handlerProvider.getFingerprintHandler().post(() ->
- registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
- mInjector.getFingerprintConfiguration(getContext()), getContext(),
- mInjector.getFingerprintService()));
- handlerProvider.getFaceHandler().post(() ->
- registerFaceSensors(mInjector.getFaceAidlInstances(),
- mInjector.getFaceConfiguration(getContext()), getContext(),
- mInjector.getFaceService()));
+ registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
+ mInjector.getFingerprintConfiguration(getContext()), getContext(),
+ mInjector.getFingerprintService(), handlerProvider);
+ registerFaceSensors(mInjector.getFaceAidlInstances(),
+ mInjector.getFaceConfiguration(getContext()), getContext(),
+ mInjector.getFaceService(), handlerProvider);
registerIrisSensors(mInjector.getIrisConfiguration(getContext()));
}
@@ -854,30 +852,38 @@
*/
private static void registerFaceSensors(final String[] faceAidlInstances,
final String[] hidlConfigStrings, final Context context,
- final IFaceService faceService) {
- final FaceSensorConfigurations mFaceSensorConfigurations =
- new FaceSensorConfigurations(hidlConfigStrings != null
- && hidlConfigStrings.length > 0);
-
- if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
- mFaceSensorConfigurations.addHidlConfigs(hidlConfigStrings, context);
+ final IFaceService faceService, final BiometricHandlerProvider handlerProvider) {
+ if ((hidlConfigStrings == null || hidlConfigStrings.length == 0)
+ && (faceAidlInstances == null || faceAidlInstances.length == 0)) {
+ Slog.d(TAG, "No face sensors.");
+ return;
}
- if (faceAidlInstances != null && faceAidlInstances.length > 0) {
- mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
- name -> IFace.Stub.asInterface(Binder.allowBlocking(
- ServiceManager.waitForDeclaredService(name))));
- }
+ handlerProvider.getFaceHandler().post(() -> {
+ final FaceSensorConfigurations mFaceSensorConfigurations =
+ new FaceSensorConfigurations(hidlConfigStrings != null
+ && hidlConfigStrings.length > 0);
- if (faceService != null) {
- try {
- faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException when registering face authenticators", e);
+ if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+ mFaceSensorConfigurations.addHidlConfigs(hidlConfigStrings, context);
}
- } else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
- Slog.e(TAG, "Face configuration exists, but FaceService is null.");
- }
+
+ if (faceAidlInstances != null && faceAidlInstances.length > 0) {
+ mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
+ name -> IFace.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(name))));
+ }
+
+ if (faceService != null) {
+ try {
+ faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering face authenticators", e);
+ }
+ } else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
+ Slog.e(TAG, "Face configuration exists, but FaceService is null.");
+ }
+ });
}
/**
@@ -885,30 +891,40 @@
*/
private static void registerFingerprintSensors(final String[] fingerprintAidlInstances,
final String[] hidlConfigStrings, final Context context,
- final IFingerprintService fingerprintService) {
- final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
- new FingerprintSensorConfigurations(!(hidlConfigStrings != null
- && hidlConfigStrings.length > 0));
-
- if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
- mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, context);
+ final IFingerprintService fingerprintService,
+ final BiometricHandlerProvider handlerProvider) {
+ if ((hidlConfigStrings == null || hidlConfigStrings.length == 0)
+ && (fingerprintAidlInstances == null || fingerprintAidlInstances.length == 0)) {
+ Slog.d(TAG, "No fingerprint sensors.");
+ return;
}
- if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
- mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
- name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
- ServiceManager.waitForDeclaredService(name))));
- }
+ handlerProvider.getFingerprintHandler().post(() -> {
+ final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
+ new FingerprintSensorConfigurations(!(hidlConfigStrings != null
+ && hidlConfigStrings.length > 0));
- if (fingerprintService != null) {
- try {
- fingerprintService.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+ if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+ mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, context);
}
- } else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
- Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
- }
+
+ if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
+ mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
+ name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(name))));
+ }
+
+ if (fingerprintService != null) {
+ try {
+ fingerprintService.registerAuthenticatorsLegacy(
+ mFingerprintSensorConfigurations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+ }
+ } else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
+ Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
+ }
+ });
}
/**
diff --git a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
index a923daa..e578861 100644
--- a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
+++ b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
@@ -16,6 +16,9 @@
package com.android.server.biometrics;
+import static android.os.Process.THREAD_PRIORITY_DEFAULT;
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -27,9 +30,9 @@
private static final BiometricHandlerProvider sBiometricHandlerProvider =
new BiometricHandlerProvider();
- private final Handler mBiometricsCallbackHandler;
- private final Handler mFingerprintHandler;
- private final Handler mFaceHandler;
+ private Handler mBiometricsCallbackHandler;
+ private Handler mFingerprintHandler;
+ private Handler mFaceHandler;
/**
* @return an instance of {@link BiometricHandlerProvider} which contains the three
@@ -39,16 +42,16 @@
return sBiometricHandlerProvider;
}
- private BiometricHandlerProvider() {
- mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler");
- mFingerprintHandler = getNewHandler("FingerprintHandler");
- mFaceHandler = getNewHandler("FaceHandler");
- }
+ private BiometricHandlerProvider() {}
/**
* @return the handler to process all biometric callback operations
*/
public synchronized Handler getBiometricCallbackHandler() {
+ if (mBiometricsCallbackHandler == null) {
+ mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler",
+ THREAD_PRIORITY_DISPLAY);
+ }
return mBiometricsCallbackHandler;
}
@@ -56,6 +59,9 @@
* @return the handler to process all face related biometric operations
*/
public synchronized Handler getFaceHandler() {
+ if (mFaceHandler == null) {
+ mFaceHandler = getNewHandler("FaceHandler", THREAD_PRIORITY_DEFAULT);
+ }
return mFaceHandler;
}
@@ -63,12 +69,15 @@
* @return the handler to process all fingerprint related biometric operations
*/
public synchronized Handler getFingerprintHandler() {
+ if (mFingerprintHandler == null) {
+ mFingerprintHandler = getNewHandler("FingerprintHandler", THREAD_PRIORITY_DEFAULT);
+ }
return mFingerprintHandler;
}
- private Handler getNewHandler(String tag) {
+ private Handler getNewHandler(String tag, int priority) {
if (Flags.deHidl()) {
- HandlerThread handlerThread = new HandlerThread(tag);
+ HandlerThread handlerThread = new HandlerThread(tag, priority);
handlerThread.start();
return new Handler(handlerThread.getLooper());
}
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 3b05b47..a7748f4 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -44,6 +44,15 @@
* </p>
*/
abstract class DisplayDevice {
+ /**
+ * Maximum acceptable anisotropy for the output image.
+ *
+ * Necessary to avoid unnecessary scaling when pixels are almost square, as they are non ideal
+ * anyway. For external displays, we expect an anisotropy of about 2% even if the pixels
+ * are, in fact, square due to the imprecision of the display's actual size (parsed from edid
+ * and rounded to the nearest cm).
+ */
+ static final float MAX_ANISOTROPY = 1.025f;
private static final String TAG = "DisplayDevice";
private static final Display.Mode EMPTY_DISPLAY_MODE = new Display.Mode.Builder().build();
@@ -69,13 +78,21 @@
// Do not use for any other purpose.
DisplayDeviceInfo mDebugLastLoggedDeviceInfo;
- public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
+ private final boolean mIsAnisotropyCorrectionEnabled;
+
+ DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
Context context) {
+ this(displayAdapter, displayToken, uniqueId, context, false);
+ }
+
+ DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
+ Context context, boolean isAnisotropyCorrectionEnabled) {
mDisplayAdapter = displayAdapter;
mDisplayToken = displayToken;
mUniqueId = uniqueId;
mDisplayDeviceConfig = null;
mContext = context;
+ mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
}
/**
@@ -143,8 +160,17 @@
DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked();
final boolean isRotated = mCurrentOrientation == ROTATION_90
|| mCurrentOrientation == ROTATION_270;
- return isRotated ? new Point(displayDeviceInfo.height, displayDeviceInfo.width)
- : new Point(displayDeviceInfo.width, displayDeviceInfo.height);
+ var width = displayDeviceInfo.width;
+ var height = displayDeviceInfo.height;
+ if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.yDpi > 0
+ && displayDeviceInfo.xDpi > 0) {
+ if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * MAX_ANISOTROPY) {
+ height = (int) (height * displayDeviceInfo.xDpi / displayDeviceInfo.yDpi + 0.5);
+ } else if (displayDeviceInfo.xDpi * MAX_ANISOTROPY < displayDeviceInfo.yDpi) {
+ width = (int) (width * displayDeviceInfo.yDpi / displayDeviceInfo.xDpi + 0.5);
+ }
+ }
+ return isRotated ? new Point(height, width) : new Point(width, height);
}
/**
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 88c24e0..b2fd9ed 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -257,7 +257,8 @@
SurfaceControl.DynamicDisplayInfo dynamicInfo,
SurfaceControl.DesiredDisplayModeSpecs modeSpecs, boolean isFirstDisplay) {
super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId,
- getContext());
+ getContext(),
+ getFeatureFlags().isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
mPhysicalDisplayId = physicalDisplayId;
mIsFirstDisplay = isFirstDisplay;
updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index db636d6..5eaaf35 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -35,7 +35,6 @@
import com.android.server.display.layout.Layout;
import com.android.server.display.mode.DisplayModeDirector;
-import com.android.server.wm.utils.DisplayInfoOverrides;
import com.android.server.wm.utils.InsetUtils;
import java.io.PrintWriter;
@@ -204,7 +203,28 @@
private SparseArray<SurfaceControl.RefreshRateRange> mThermalRefreshRateThrottling =
new SparseArray<>();
+ /**
+ * If the aspect ratio of the resolution of the display does not match the physical aspect
+ * ratio of the display, then without this feature enabled, picture would appear stretched to
+ * the user. This is because applications assume that they are rendered on square pixels
+ * (meaning density of pixels in x and y directions are equal). This would result into circles
+ * appearing as ellipses to the user.
+ * To compensate for non-square (anisotropic) pixels, if this feature is enabled:
+ * 1. LogicalDisplay will add more pixels for the applications to render on, as if the pixels
+ * were square and occupied the full display.
+ * 2. SurfaceFlinger will squeeze this taller/wider surface into the available number of
+ * physical pixels in the current display resolution.
+ * 3. If a setting on the display itself is set to "fill the entire display panel" then the
+ * display will stretch the pixels to fill the display fully.
+ */
+ private final boolean mIsAnisotropyCorrectionEnabled;
+
LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
+ this(displayId, layerStack, primaryDisplayDevice, false);
+ }
+
+ LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice,
+ boolean isAnisotropyCorrectionEnabled) {
mDisplayId = displayId;
mLayerStack = layerStack;
mPrimaryDisplayDevice = primaryDisplayDevice;
@@ -215,6 +235,7 @@
mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
+ mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
}
public void setDevicePositionLocked(int position) {
@@ -453,6 +474,14 @@
int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
+ if (mIsAnisotropyCorrectionEnabled && deviceInfo.xDpi > 0 && deviceInfo.yDpi > 0) {
+ if (deviceInfo.xDpi > deviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
+ maskedHeight = (int) (maskedHeight * deviceInfo.xDpi / deviceInfo.yDpi + 0.5);
+ } else if (deviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY < deviceInfo.yDpi) {
+ maskedWidth = (int) (maskedWidth * deviceInfo.yDpi / deviceInfo.xDpi + 0.5);
+ }
+ }
+
mBaseDisplayInfo.type = deviceInfo.type;
mBaseDisplayInfo.address = deviceInfo.address;
mBaseDisplayInfo.deviceProductInfo = deviceInfo.deviceProductInfo;
@@ -666,6 +695,31 @@
physWidth -= maskingInsets.left + maskingInsets.right;
physHeight -= maskingInsets.top + maskingInsets.bottom;
+ var displayLogicalWidth = displayInfo.logicalWidth;
+ var displayLogicalHeight = displayInfo.logicalHeight;
+
+ if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.xDpi > 0
+ && displayDeviceInfo.yDpi > 0) {
+ if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
+ var scalingFactor = displayDeviceInfo.yDpi / displayDeviceInfo.xDpi;
+ if (rotated) {
+ displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5);
+ } else {
+ displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor
+ + 0.5);
+ }
+ } else if (displayDeviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY
+ < displayDeviceInfo.yDpi) {
+ var scalingFactor = displayDeviceInfo.xDpi / displayDeviceInfo.yDpi;
+ if (rotated) {
+ displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor
+ + 0.5);
+ } else {
+ displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5);
+ }
+ }
+ }
+
// Determine whether the width or height is more constrained to be scaled.
// physWidth / displayInfo.logicalWidth => letter box
// or physHeight / displayInfo.logicalHeight => pillar box
@@ -675,16 +729,16 @@
// comparing them.
int displayRectWidth, displayRectHeight;
if ((displayInfo.flags & Display.FLAG_SCALING_DISABLED) != 0 || mDisplayScalingDisabled) {
- displayRectWidth = displayInfo.logicalWidth;
- displayRectHeight = displayInfo.logicalHeight;
- } else if (physWidth * displayInfo.logicalHeight
- < physHeight * displayInfo.logicalWidth) {
+ displayRectWidth = displayLogicalWidth;
+ displayRectHeight = displayLogicalHeight;
+ } else if (physWidth * displayLogicalHeight
+ < physHeight * displayLogicalWidth) {
// Letter box.
displayRectWidth = physWidth;
- displayRectHeight = displayInfo.logicalHeight * physWidth / displayInfo.logicalWidth;
+ displayRectHeight = displayLogicalHeight * physWidth / displayLogicalWidth;
} else {
// Pillar box.
- displayRectWidth = displayInfo.logicalWidth * physHeight / displayInfo.logicalHeight;
+ displayRectWidth = displayLogicalWidth * physHeight / displayLogicalHeight;
displayRectHeight = physHeight;
}
int displayRectTop = (physHeight - displayRectHeight) / 2;
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 3452e0f..e092fda 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -1151,7 +1151,8 @@
*/
private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {
final int layerStack = assignLayerStackLocked(displayId);
- final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
+ final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device,
+ mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
display.updateLocked(mDisplayDeviceRepo);
final DisplayInfo info = display.getDisplayInfoLocked();
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 3c98ee4..15ee937 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -121,6 +121,11 @@
Flags::refreshRateVotingTelemetry
);
+ private final FlagState mPixelAnisotropyCorrectionEnabled = new FlagState(
+ Flags.FLAG_ENABLE_PIXEL_ANISOTROPY_CORRECTION,
+ Flags::enablePixelAnisotropyCorrection
+ );
+
private final FlagState mSensorBasedBrightnessThrottling = new FlagState(
Flags.FLAG_SENSOR_BASED_BRIGHTNESS_THROTTLING,
Flags::sensorBasedBrightnessThrottling
@@ -259,6 +264,10 @@
return mRefreshRateVotingTelemetry.isEnabled();
}
+ public boolean isPixelAnisotropyCorrectionInLogicalDisplayEnabled() {
+ return mPixelAnisotropyCorrectionEnabled.isEnabled();
+ }
+
public boolean isSensorBasedBrightnessThrottlingEnabled() {
return mSensorBasedBrightnessThrottling.isEnabled();
}
@@ -290,6 +299,7 @@
pw.println(" " + mAutoBrightnessModesFlagState);
pw.println(" " + mFastHdrTransitions);
pw.println(" " + mRefreshRateVotingTelemetry);
+ pw.println(" " + mPixelAnisotropyCorrectionEnabled);
pw.println(" " + mSensorBasedBrightnessThrottling);
pw.println(" " + mRefactorDisplayPowerController);
}
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 3404527..9bf36e4 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -186,6 +186,14 @@
}
flag {
+ name: "enable_pixel_anisotropy_correction"
+ namespace: "display_manager"
+ description: "Feature flag for enabling display anisotropy correction through LogicalDisplay upscaling"
+ bug: "317363416"
+ is_fixed_read_only: true
+}
+
+flag {
name: "sensor_based_brightness_throttling"
namespace: "display_manager"
description: "Feature flag for enabling brightness throttling using sensor from config."
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 0ef23e9..e6bf2c9 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -61,7 +61,6 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
@@ -133,7 +132,11 @@
@Override
public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
- return canGetSystemGrammaticalGender(attributionSource)
+ if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) {
+ throw new SecurityException("AttributionSource: " + attributionSource
+ + " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission.");
+ }
+ return checkSystemTermsOfAddressIsEnabled()
? GrammaticalInflectionService.this.getSystemGrammaticalGender(
attributionSource, userId)
: GRAMMATICAL_GENDER_NOT_SPECIFIED;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 996477d..fef5661 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -67,7 +67,6 @@
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.annotation.UserIdInt;
-import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -196,21 +195,16 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.security.InvalidParameterException;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Locale;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -730,344 +724,9 @@
private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners =
new CopyOnWriteArrayList<>();
- /**
- * Internal state snapshot when
- * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
- *
- * <p>Calling that IPC endpoint basically means that
- * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
- * back in the current IME process shortly, which will also affect what the current IME starts
- * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
- * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
- * logical input session between the client application and the current IME.</p>
- *
- * <p>Be careful to not keep strong references to this object forever, which can prevent
- * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
- * </p>
- */
- private static class StartInputInfo {
- private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
- final int mSequenceNumber;
- final long mTimestamp;
- final long mWallTime;
- @UserIdInt
- final int mImeUserId;
- @NonNull
- final IBinder mImeToken;
- final int mImeDisplayId;
- @NonNull
- final String mImeId;
- @StartInputReason
- final int mStartInputReason;
- final boolean mRestarting;
- @UserIdInt
- final int mTargetUserId;
- final int mTargetDisplayId;
- @Nullable
- final IBinder mTargetWindow;
- @NonNull
- final EditorInfo mEditorInfo;
- @SoftInputModeFlags
- final int mTargetWindowSoftInputMode;
- final int mClientBindSequenceNumber;
-
- StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
- @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
- @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
- @NonNull EditorInfo editorInfo, @SoftInputModeFlags int targetWindowSoftInputMode,
- int clientBindSequenceNumber) {
- mSequenceNumber = sSequenceNumber.getAndIncrement();
- mTimestamp = SystemClock.uptimeMillis();
- mWallTime = System.currentTimeMillis();
- mImeUserId = imeUserId;
- mImeToken = imeToken;
- mImeDisplayId = imeDisplayId;
- mImeId = imeId;
- mStartInputReason = startInputReason;
- mRestarting = restarting;
- mTargetUserId = targetUserId;
- mTargetDisplayId = targetDisplayId;
- mTargetWindow = targetWindow;
- mEditorInfo = editorInfo;
- mTargetWindowSoftInputMode = targetWindowSoftInputMode;
- mClientBindSequenceNumber = clientBindSequenceNumber;
- }
- }
-
@GuardedBy("ImfLock.class")
private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>();
- @VisibleForTesting
- static final class SoftInputShowHideHistory {
- private final Entry[] mEntries = new Entry[16];
- private int mNextIndex = 0;
- private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
-
- static final class Entry {
- final int mSequenceNumber = sSequenceNumber.getAndIncrement();
- @Nullable
- final ClientState mClientState;
- @SoftInputModeFlags
- final int mFocusedWindowSoftInputMode;
- @SoftInputShowHideReason
- final int mReason;
- // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
- final long mTimestamp;
- final long mWallTime;
- final boolean mInFullscreenMode;
- @NonNull
- final String mFocusedWindowName;
- @Nullable
- final EditorInfo mEditorInfo;
- @NonNull
- final String mRequestWindowName;
- @Nullable
- final String mImeControlTargetName;
- @Nullable
- final String mImeTargetNameFromWm;
- @Nullable
- final String mImeSurfaceParentName;
-
- Entry(ClientState client, EditorInfo editorInfo,
- String focusedWindowName, @SoftInputModeFlags int softInputMode,
- @SoftInputShowHideReason int reason,
- boolean inFullscreenMode, String requestWindowName,
- @Nullable String imeControlTargetName, @Nullable String imeTargetName,
- @Nullable String imeSurfaceParentName) {
- mClientState = client;
- mEditorInfo = editorInfo;
- mFocusedWindowName = focusedWindowName;
- mFocusedWindowSoftInputMode = softInputMode;
- mReason = reason;
- mTimestamp = SystemClock.uptimeMillis();
- mWallTime = System.currentTimeMillis();
- mInFullscreenMode = inFullscreenMode;
- mRequestWindowName = requestWindowName;
- mImeControlTargetName = imeControlTargetName;
- mImeTargetNameFromWm = imeTargetName;
- mImeSurfaceParentName = imeSurfaceParentName;
- }
- }
-
- void addEntry(@NonNull Entry entry) {
- final int index = mNextIndex;
- mEntries[index] = entry;
- mNextIndex = (mNextIndex + 1) % mEntries.length;
- }
-
- void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- final DateTimeFormatter formatter =
- DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
- .withZone(ZoneId.systemDefault());
-
- for (int i = 0; i < mEntries.length; ++i) {
- final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
- if (entry == null) {
- continue;
- }
- pw.print(prefix);
- pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
-
- pw.print(prefix);
- pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
- + " (timestamp=" + entry.mTimestamp + ")");
-
- pw.print(prefix);
- pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString(
- entry.mReason));
- pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
-
- pw.print(prefix);
- pw.println(" requestClient=" + entry.mClientState);
-
- pw.print(prefix);
- pw.println(" focusedWindowName=" + entry.mFocusedWindowName);
-
- pw.print(prefix);
- pw.println(" requestWindowName=" + entry.mRequestWindowName);
-
- pw.print(prefix);
- pw.println(" imeControlTargetName=" + entry.mImeControlTargetName);
-
- pw.print(prefix);
- pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
-
- pw.print(prefix);
- pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName);
-
- pw.print(prefix);
- pw.print(" editorInfo:");
- if (entry.mEditorInfo != null) {
- pw.print(" inputType=" + entry.mEditorInfo.inputType);
- pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
- pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
- } else {
- pw.println(" null");
- }
-
- pw.print(prefix);
- pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
- entry.mFocusedWindowSoftInputMode));
- }
- }
- }
-
- /**
- * A ring buffer to store the history of {@link StartInputInfo}.
- */
- private static final class StartInputHistory {
- /**
- * Entry size for non low-RAM devices.
- *
- * <p>TODO: Consider to follow what other system services have been doing to manage
- * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
- */
- private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
-
- /**
- * Entry size for low-RAM devices.
- *
- * <p>TODO: Consider to follow what other system services have been doing to manage
- * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
- */
- private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
-
- private static int getEntrySize() {
- if (ActivityManager.isLowRamDeviceStatic()) {
- return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
- } else {
- return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
- }
- }
-
- /**
- * Backing store for the ring buffer.
- */
- private final Entry[] mEntries = new Entry[getEntrySize()];
-
- /**
- * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should
- * write.
- */
- private int mNextIndex = 0;
-
- /**
- * Recyclable entry to store the information in {@link StartInputInfo}.
- */
- private static final class Entry {
- int mSequenceNumber;
- long mTimestamp;
- long mWallTime;
- @UserIdInt
- int mImeUserId;
- @NonNull
- String mImeTokenString;
- int mImeDisplayId;
- @NonNull
- String mImeId;
- @StartInputReason
- int mStartInputReason;
- boolean mRestarting;
- @UserIdInt
- int mTargetUserId;
- int mTargetDisplayId;
- @NonNull
- String mTargetWindowString;
- @NonNull
- EditorInfo mEditorInfo;
- @SoftInputModeFlags
- int mTargetWindowSoftInputMode;
- int mClientBindSequenceNumber;
-
- Entry(@NonNull StartInputInfo original) {
- set(original);
- }
-
- void set(@NonNull StartInputInfo original) {
- mSequenceNumber = original.mSequenceNumber;
- mTimestamp = original.mTimestamp;
- mWallTime = original.mWallTime;
- mImeUserId = original.mImeUserId;
- // Intentionally convert to String so as not to keep a strong reference to a Binder
- // object.
- mImeTokenString = String.valueOf(original.mImeToken);
- mImeDisplayId = original.mImeDisplayId;
- mImeId = original.mImeId;
- mStartInputReason = original.mStartInputReason;
- mRestarting = original.mRestarting;
- mTargetUserId = original.mTargetUserId;
- mTargetDisplayId = original.mTargetDisplayId;
- // Intentionally convert to String so as not to keep a strong reference to a Binder
- // object.
- mTargetWindowString = String.valueOf(original.mTargetWindow);
- mEditorInfo = original.mEditorInfo;
- mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
- mClientBindSequenceNumber = original.mClientBindSequenceNumber;
- }
- }
-
- /**
- * Add a new entry and discard the oldest entry as needed.
- * @param info {@link StartInputInfo} to be added.
- */
- void addEntry(@NonNull StartInputInfo info) {
- final int index = mNextIndex;
- if (mEntries[index] == null) {
- mEntries[index] = new Entry(info);
- } else {
- mEntries[index].set(info);
- }
- mNextIndex = (mNextIndex + 1) % mEntries.length;
- }
-
- void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
- final DateTimeFormatter formatter =
- DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
- .withZone(ZoneId.systemDefault());
-
- for (int i = 0; i < mEntries.length; ++i) {
- final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
- if (entry == null) {
- continue;
- }
- pw.print(prefix);
- pw.println("StartInput #" + entry.mSequenceNumber + ":");
-
- pw.print(prefix);
- pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
- + " (timestamp=" + entry.mTimestamp + ")"
- + " reason="
- + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
- + " restarting=" + entry.mRestarting);
-
- pw.print(prefix);
- pw.print(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
- pw.print(" imeUserId=" + entry.mImeUserId);
- pw.println(" imeDisplayId=" + entry.mImeDisplayId);
-
- pw.print(prefix);
- pw.println(" targetWin=" + entry.mTargetWindowString
- + " [" + entry.mEditorInfo.packageName + "]"
- + " targetUserId=" + entry.mTargetUserId
- + " targetDisplayId=" + entry.mTargetDisplayId
- + " clientBindSeq=" + entry.mClientBindSequenceNumber);
-
- pw.print(prefix);
- pw.println(" softInputMode=" + InputMethodDebug.softInputModeToString(
- entry.mTargetWindowSoftInputMode));
-
- pw.print(prefix);
- pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
- + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
- + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
- + " fieldName=" + entry.mEditorInfo.fieldName
- + " actionId=" + entry.mEditorInfo.actionId
- + " actionLabel=" + entry.mEditorInfo.actionLabel);
- }
- }
- }
-
@GuardedBy("ImfLock.class")
@NonNull
private final StartInputHistory mStartInputHistory = new StartInputHistory();
diff --git a/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
new file mode 100644
index 0000000..3023603
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/SoftInputShowHideHistory.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
+
+final class SoftInputShowHideHistory {
+ private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+ private final Entry[] mEntries = new Entry[16];
+ private int mNextIndex = 0;
+
+ static final class Entry {
+ final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+ @Nullable
+ final ClientState mClientState;
+ @WindowManager.LayoutParams.SoftInputModeFlags
+ final int mFocusedWindowSoftInputMode;
+ @SoftInputShowHideReason
+ final int mReason;
+ // The timing of handling showCurrentInputLocked() or hideCurrentInputLocked().
+ final long mTimestamp;
+ final long mWallTime;
+ final boolean mInFullscreenMode;
+ @NonNull
+ final String mFocusedWindowName;
+ @Nullable
+ final EditorInfo mEditorInfo;
+ @NonNull
+ final String mRequestWindowName;
+ @Nullable
+ final String mImeControlTargetName;
+ @Nullable
+ final String mImeTargetNameFromWm;
+ @Nullable
+ final String mImeSurfaceParentName;
+
+ Entry(ClientState client, EditorInfo editorInfo,
+ String focusedWindowName,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ @SoftInputShowHideReason int reason,
+ boolean inFullscreenMode, String requestWindowName,
+ @Nullable String imeControlTargetName, @Nullable String imeTargetName,
+ @Nullable String imeSurfaceParentName) {
+ mClientState = client;
+ mEditorInfo = editorInfo;
+ mFocusedWindowName = focusedWindowName;
+ mFocusedWindowSoftInputMode = softInputMode;
+ mReason = reason;
+ mTimestamp = SystemClock.uptimeMillis();
+ mWallTime = System.currentTimeMillis();
+ mInFullscreenMode = inFullscreenMode;
+ mRequestWindowName = requestWindowName;
+ mImeControlTargetName = imeControlTargetName;
+ mImeTargetNameFromWm = imeTargetName;
+ mImeSurfaceParentName = imeSurfaceParentName;
+ }
+ }
+
+ void addEntry(@NonNull Entry entry) {
+ final int index = mNextIndex;
+ mEntries[index] = entry;
+ mNextIndex = (mNextIndex + 1) % mEntries.length;
+ }
+
+ void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+ .withZone(ZoneId.systemDefault());
+
+ for (int i = 0; i < mEntries.length; ++i) {
+ final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+ if (entry == null) {
+ continue;
+ }
+ pw.print(prefix);
+ pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
+
+ pw.print(prefix);
+ pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+ + " (timestamp=" + entry.mTimestamp + ")");
+
+ pw.print(prefix);
+ pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString(
+ entry.mReason));
+ pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
+
+ pw.print(prefix);
+ pw.println(" requestClient=" + entry.mClientState);
+
+ pw.print(prefix);
+ pw.println(" focusedWindowName=" + entry.mFocusedWindowName);
+
+ pw.print(prefix);
+ pw.println(" requestWindowName=" + entry.mRequestWindowName);
+
+ pw.print(prefix);
+ pw.println(" imeControlTargetName=" + entry.mImeControlTargetName);
+
+ pw.print(prefix);
+ pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
+
+ pw.print(prefix);
+ pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName);
+
+ pw.print(prefix);
+ pw.print(" editorInfo:");
+ if (entry.mEditorInfo != null) {
+ pw.print(" inputType=" + entry.mEditorInfo.inputType);
+ pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
+ pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
+ } else {
+ pw.println(" null");
+ }
+
+ pw.print(prefix);
+ pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
+ entry.mFocusedWindowSoftInputMode));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/StartInputHistory.java b/services/core/java/com/android/server/inputmethod/StartInputHistory.java
new file mode 100644
index 0000000..3a39434
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/StartInputHistory.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * A ring buffer to store the history of {@link StartInputInfo}.
+ */
+final class StartInputHistory {
+ /**
+ * Entry size for non low-RAM devices.
+ *
+ * <p>TODO: Consider to follow what other system services have been doing to manage
+ * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+ */
+ private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
+
+ /**
+ * Entry size for low-RAM devices.
+ *
+ * <p>TODO: Consider to follow what other system services have been doing to manage
+ * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
+ */
+ private static final int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
+
+ private static int getEntrySize() {
+ if (ActivityManager.isLowRamDeviceStatic()) {
+ return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
+ } else {
+ return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
+ }
+ }
+
+ /**
+ * Backing store for the ring buffer.
+ */
+ private final Entry[] mEntries = new Entry[getEntrySize()];
+
+ /**
+ * An index of {@link #mEntries}, to which next
+ * {@link #addEntry(StartInputInfo)} should
+ * write.
+ */
+ private int mNextIndex = 0;
+
+ /**
+ * Recyclable entry to store the information in {@link StartInputInfo}.
+ */
+ private static final class Entry {
+ int mSequenceNumber;
+ long mTimestamp;
+ long mWallTime;
+ @UserIdInt
+ int mImeUserId;
+ @NonNull
+ String mImeTokenString;
+ int mImeDisplayId;
+ @NonNull
+ String mImeId;
+ @StartInputReason
+ int mStartInputReason;
+ boolean mRestarting;
+ @UserIdInt
+ int mTargetUserId;
+ int mTargetDisplayId;
+ @NonNull
+ String mTargetWindowString;
+ @NonNull
+ EditorInfo mEditorInfo;
+ @WindowManager.LayoutParams.SoftInputModeFlags
+ int mTargetWindowSoftInputMode;
+ int mClientBindSequenceNumber;
+
+ Entry(@NonNull StartInputInfo original) {
+ set(original);
+ }
+
+ void set(@NonNull StartInputInfo original) {
+ mSequenceNumber = original.mSequenceNumber;
+ mTimestamp = original.mTimestamp;
+ mWallTime = original.mWallTime;
+ mImeUserId = original.mImeUserId;
+ // Intentionally convert to String so as not to keep a strong reference to a Binder
+ // object.
+ mImeTokenString = String.valueOf(original.mImeToken);
+ mImeDisplayId = original.mImeDisplayId;
+ mImeId = original.mImeId;
+ mStartInputReason = original.mStartInputReason;
+ mRestarting = original.mRestarting;
+ mTargetUserId = original.mTargetUserId;
+ mTargetDisplayId = original.mTargetDisplayId;
+ // Intentionally convert to String so as not to keep a strong reference to a Binder
+ // object.
+ mTargetWindowString = String.valueOf(original.mTargetWindow);
+ mEditorInfo = original.mEditorInfo;
+ mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
+ mClientBindSequenceNumber = original.mClientBindSequenceNumber;
+ }
+ }
+
+ /**
+ * Add a new entry and discard the oldest entry as needed.
+ *
+ * @param info {@link StartInputInfo} to be added.
+ */
+ void addEntry(@NonNull StartInputInfo info) {
+ final int index = mNextIndex;
+ if (mEntries[index] == null) {
+ mEntries[index] = new Entry(info);
+ } else {
+ mEntries[index].set(info);
+ }
+ mNextIndex = (mNextIndex + 1) % mEntries.length;
+ }
+
+ void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+ .withZone(ZoneId.systemDefault());
+
+ for (int i = 0; i < mEntries.length; ++i) {
+ final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
+ if (entry == null) {
+ continue;
+ }
+ pw.print(prefix);
+ pw.println("StartInput #" + entry.mSequenceNumber + ":");
+
+ pw.print(prefix);
+ pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+ + " (timestamp=" + entry.mTimestamp + ")"
+ + " reason="
+ + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
+ + " restarting=" + entry.mRestarting);
+
+ pw.print(prefix);
+ pw.print(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
+ pw.print(" imeUserId=" + entry.mImeUserId);
+ pw.println(" imeDisplayId=" + entry.mImeDisplayId);
+
+ pw.print(prefix);
+ pw.println(" targetWin=" + entry.mTargetWindowString
+ + " [" + entry.mEditorInfo.packageName + "]"
+ + " targetUserId=" + entry.mTargetUserId
+ + " targetDisplayId=" + entry.mTargetDisplayId
+ + " clientBindSeq=" + entry.mClientBindSequenceNumber);
+
+ pw.print(prefix);
+ pw.println(" softInputMode=" + InputMethodDebug.softInputModeToString(
+ entry.mTargetWindowSoftInputMode));
+
+ pw.print(prefix);
+ pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
+ + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
+ + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
+ + " fieldName=" + entry.mEditorInfo.fieldName
+ + " actionId=" + entry.mEditorInfo.actionId
+ + " actionLabel=" + entry.mEditorInfo.actionLabel);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/StartInputInfo.java b/services/core/java/com/android/server/inputmethod/StartInputInfo.java
new file mode 100644
index 0000000..1cff737
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/StartInputInfo.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.inputmethodservice.InputMethodService;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+import com.android.internal.inputmethod.IInputMethod;
+import com.android.internal.inputmethod.StartInputReason;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Internal state snapshot when
+ * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called.
+ *
+ * <p>Calling that IPC endpoint basically means that
+ * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
+ * back in the current IME process shortly, which will also affect what the current IME starts
+ * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
+ * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
+ * logical input session between the client application and the current IME.</p>
+ *
+ * <p>Be careful to not keep strong references to this object forever, which can prevent
+ * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
+ * </p>
+ */
+final class StartInputInfo {
+ private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+ final int mSequenceNumber;
+ final long mTimestamp;
+ final long mWallTime;
+ @UserIdInt
+ final int mImeUserId;
+ @NonNull
+ final IBinder mImeToken;
+ final int mImeDisplayId;
+ @NonNull
+ final String mImeId;
+ @StartInputReason
+ final int mStartInputReason;
+ final boolean mRestarting;
+ @UserIdInt
+ final int mTargetUserId;
+ final int mTargetDisplayId;
+ @Nullable
+ final IBinder mTargetWindow;
+ @NonNull
+ final EditorInfo mEditorInfo;
+ @WindowManager.LayoutParams.SoftInputModeFlags
+ final int mTargetWindowSoftInputMode;
+ final int mClientBindSequenceNumber;
+
+ StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId,
+ @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting,
+ @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow,
+ @NonNull EditorInfo editorInfo,
+ @WindowManager.LayoutParams.SoftInputModeFlags int targetWindowSoftInputMode,
+ int clientBindSequenceNumber) {
+ mSequenceNumber = sSequenceNumber.getAndIncrement();
+ mTimestamp = SystemClock.uptimeMillis();
+ mWallTime = System.currentTimeMillis();
+ mImeUserId = imeUserId;
+ mImeToken = imeToken;
+ mImeDisplayId = imeDisplayId;
+ mImeId = imeId;
+ mStartInputReason = startInputReason;
+ mRestarting = restarting;
+ mTargetUserId = targetUserId;
+ mTargetDisplayId = targetDisplayId;
+ mTargetWindow = targetWindow;
+ mEditorInfo = editorInfo;
+ mTargetWindowSoftInputMode = targetWindowSoftInputMode;
+ mClientBindSequenceNumber = clientBindSequenceNumber;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index c7ebb3c..c6bb99e 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -2116,6 +2116,18 @@
@RequiresPermission(READ_FRAME_BUFFER)
@Override
+ public void saveViewCaptureData() {
+ int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
+ if (PERMISSION_GRANTED == status) {
+ forEachViewCaptureWindow(this::dumpViewCaptureDataToWmTrace);
+ } else {
+ Log.w(TAG, "caller lacks permissions to save view capture data");
+ }
+ }
+
+
+ @RequiresPermission(READ_FRAME_BUFFER)
+ @Override
public void registerDumpCallback(@NonNull IDumpCallback cb) {
int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
if (PERMISSION_GRANTED == status) {
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index c2f74a8..c9fd261 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -155,7 +155,8 @@
UserManager.DISALLOW_CONFIG_DEFAULT_APPS,
UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
UserManager.DISALLOW_SIM_GLOBALLY,
- UserManager.DISALLOW_ASSIST_CONTENT
+ UserManager.DISALLOW_ASSIST_CONTENT,
+ UserManager.DISALLOW_THREAD_NETWORK
});
public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
@@ -206,7 +207,8 @@
UserManager.DISALLOW_ADD_WIFI_CONFIG,
UserManager.DISALLOW_CELLULAR_2G,
UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
- UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO
+ UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+ UserManager.DISALLOW_THREAD_NETWORK
);
/**
@@ -252,7 +254,8 @@
UserManager.DISALLOW_ADD_WIFI_CONFIG,
UserManager.DISALLOW_CELLULAR_2G,
UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
- UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO
+ UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+ UserManager.DISALLOW_THREAD_NETWORK
);
/**
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index c3efcb1..885baf6 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2691,6 +2691,7 @@
}
float maxDimAmount = getHighestDimAmountFromMap(wallpaper.mUidToDimAmount);
+ if (wallpaper.mWallpaperDimAmount == maxDimAmount) return;
wallpaper.mWallpaperDimAmount = maxDimAmount;
// Also set the dim amount to the lock screen wallpaper if the lock and home screen
// do not share the same wallpaper
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 071f403..f7baa79 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -18,10 +18,10 @@
import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
-import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -37,12 +37,11 @@
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
+import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
import static com.android.window.flags.Flags.balRequireOptInSameUid;
-import static com.android.window.flags.Flags.balShowToasts;
import static com.android.window.flags.Flags.balShowToastsBlocked;
-import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import static java.util.Objects.requireNonNull;
@@ -752,7 +751,6 @@
Slog.wtf(TAG, "With Android 15 BAL hardening this activity start may be blocked"
+ " if the PI creator upgrades target_sdk to 35+! "
+ " (missing opt in by PI creator)!" + state.dump());
- showBalRiskToast();
return allowBasedOnCaller(state);
}
}
@@ -762,7 +760,6 @@
Slog.wtf(TAG, "With Android 14 BAL hardening this activity start will be blocked"
+ " if the PI sender upgrades target_sdk to 34+! "
+ " (missing opt in by PI sender)!" + state.dump());
- showBalRiskToast();
return allowBasedOnRealCaller(state);
}
}
@@ -793,7 +790,11 @@
private BalVerdict abortLaunch(BalState state) {
Slog.wtf(TAG, "Background activity launch blocked! "
+ state.dump());
- showBalBlockedToast();
+ if (balShowToastsBlocked()
+ && (state.mResultForCaller.allows() || state.mResultForRealCaller.allows())) {
+ // only show a toast if either caller or real caller could launch if they opted in
+ showToast("BAL blocked. go/debug-bal");
+ }
return statsLog(BalVerdict.BLOCK, state);
}
@@ -1192,18 +1193,6 @@
return true;
}
- private void showBalBlockedToast() {
- if (balShowToastsBlocked()) {
- showToast("BAL blocked. go/debug-bal");
- }
- }
-
- private void showBalRiskToast() {
- if (balShowToasts()) {
- showToast("BAL allowed in compat mode. go/debug-bal");
- }
- }
-
@VisibleForTesting void showToast(String toastText) {
UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
toastText, Toast.LENGTH_LONG).show());
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index a914c07..b616d24 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -107,7 +107,9 @@
ContentRecorder(@NonNull DisplayContent displayContent) {
this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId),
- new DisplayManagerFlags().isConnectedDisplayManagementEnabled());
+ new DisplayManagerFlags().isConnectedDisplayManagementEnabled()
+ && !new DisplayManagerFlags()
+ .isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 877378c..a29cb60 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -173,18 +173,25 @@
mDisplayContent.mInitialDisplayHeight);
final int fromRotation = mDisplayContent.getRotation();
- onStartCollect.run();
+ mDisplayContent.mAtmService.deferWindowLayout();
+ try {
+ onStartCollect.run();
- ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
- "DeferredDisplayUpdater: applied DisplayInfo after deferring");
+ ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS,
+ "DeferredDisplayUpdater: applied DisplayInfo after deferring");
- if (physicalDisplayUpdated) {
- onDisplayUpdated(transition, fromRotation, startBounds);
- } else {
- final TransitionRequestInfo.DisplayChange displayChange =
- getCurrentDisplayChange(fromRotation, startBounds);
- mDisplayContent.mTransitionController.requestStartTransition(transition,
- /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+ if (physicalDisplayUpdated) {
+ onDisplayUpdated(transition, fromRotation, startBounds);
+ } else {
+ final TransitionRequestInfo.DisplayChange displayChange =
+ getCurrentDisplayChange(fromRotation, startBounds);
+ mDisplayContent.mTransitionController.requestStartTransition(transition,
+ /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+ }
+ } finally {
+ // Run surface placement after requestStartTransition, so shell side can receive
+ // the transition request before handling task info changes.
+ mDisplayContent.mAtmService.continueWindowLayout();
}
});
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 282ecc7..837d08b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6210,7 +6210,12 @@
* @param onDisplayChangeApplied callback that is called when the changes are applied
*/
void requestDisplayUpdate(@NonNull Runnable onDisplayChangeApplied) {
- mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied);
+ mAtmService.deferWindowLayout();
+ try {
+ mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied);
+ } finally {
+ mAtmService.continueWindowLayout();
+ }
}
void onDisplayInfoUpdated(@NonNull DisplayInfo newDisplayInfo) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c37946b..ee72db0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3395,7 +3395,9 @@
if (shouldMigrateV1ToDevicePolicyEngine()) {
migrateV1PoliciesToDevicePolicyEngine();
}
+ maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked();
migratePoliciesToPolicyEngineLocked();
+
}
maybeStartSecurityLogMonitorOnActivityManagerReady();
break;
@@ -16877,6 +16879,8 @@
private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int callingUserId) {
synchronized (getLockObject()) {
final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode()
+ && (!Flags.headlessDeviceOwnerProvisioningFixEnabled()
+ || getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED)
? UserHandle.USER_SYSTEM
: callingUserId;
Slogf.i(LOG_TAG, "Calling user %d, device owner will be set on user %d",
@@ -21549,10 +21553,21 @@
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
+
+
+ boolean isSingleUserMode;
+ if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) {
+ DeviceAdminInfo adminInfo = findAdmin(
+ deviceAdmin, caller.getUserId(), /* throwForMissingPermission= */ false);
+ isSingleUserMode = (adminInfo != null && adminInfo.getHeadlessDeviceOwnerMode()
+ == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER);
+ } else {
+ isSingleUserMode =
+ (getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER);
+ }
int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
- && getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER
- ? mUserManagerInternal.getMainUserId()
- : UserHandle.USER_SYSTEM;
+ && isSingleUserMode
+ ? mUserManagerInternal.getMainUserId() : UserHandle.USER_SYSTEM;
if (!removeNonRequiredAppsForManagedDevice(
deviceOwnerUserId,
@@ -23736,7 +23751,9 @@
if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) {
return false;
}
- return migrateV1PoliciesToDevicePolicyEngine();
+ boolean migrated = migrateV1PoliciesToDevicePolicyEngine();
+ migrated &= migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+ return migrated;
});
}
@@ -23765,6 +23782,30 @@
/**
* Migrates the initial set of policies to use policy engine.
+ * [b/318497672] Migrate policies that weren't migrated properly in the initial migration on
+ * update from Android T to Android U
+ */
+ private void maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked() {
+ if (!mOwners.isMigratedToPolicyEngine() || mOwners.isMigratedPostUpdate()) {
+ return;
+ }
+ migratePoliciesPostUpgradeToDevicePolicyEngineLocked();
+ mOwners.markPostUpgradeMigration();
+ }
+
+ private boolean migratePoliciesPostUpgradeToDevicePolicyEngineLocked() {
+ try {
+ migrateScreenCapturePolicyLocked();
+ migrateLockTaskPolicyLocked();
+ return true;
+ } catch (Exception e) {
+ Slogf.e(LOG_TAG, e, "Error occurred during post upgrade migration to the device "
+ + "policy engine.");
+ return false;
+ }
+ }
+
+ /**
* @return {@code true} if policies were migrated successfully, {@code false} otherwise.
*/
private boolean migrateV1PoliciesToDevicePolicyEngine() {
@@ -23777,7 +23818,6 @@
migrateAutoTimezonePolicy();
migratePermissionGrantStatePolicies();
}
- migrateScreenCapturePolicyLocked();
migratePermittedInputMethodsPolicyLocked();
migrateAccountManagementDisabledPolicyLocked();
migrateUserControlDisabledPackagesLocked();
@@ -23858,14 +23898,12 @@
private void migrateScreenCapturePolicyLocked() {
Binder.withCleanCallingIdentity(() -> {
- if (mPolicyCache.getScreenCaptureDisallowedUser() == UserHandle.USER_NULL) {
- return;
- }
ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
if (admin != null
&& ((isDeviceOwner(admin) && admin.disableScreenCapture)
|| (admin.getParentActiveAdmin() != null
&& admin.getParentActiveAdmin().disableScreenCapture))) {
+
EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
admin.info.getComponent(),
admin.getUserHandle().getIdentifier(),
@@ -23894,6 +23932,48 @@
});
}
+ private void migrateLockTaskPolicyLocked() {
+ Binder.withCleanCallingIdentity(() -> {
+ ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ if (deviceOwner != null) {
+ int doUserId = deviceOwner.getUserHandle().getIdentifier();
+ DevicePolicyData policies = getUserData(doUserId);
+ List<String> packages = policies.mLockTaskPackages;
+ int features = policies.mLockTaskFeatures;
+ // TODO: find out about persistent preferred activities
+ if (!packages.isEmpty()) {
+ setLockTaskPolicyInPolicyEngine(deviceOwner, doUserId, packages, features);
+ }
+ }
+
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ ActiveAdmin profileOwner = getProfileOwnerLocked(userId);
+ if (profileOwner != null && canDPCManagedUserUseLockTaskLocked(userId)) {
+ DevicePolicyData policies = getUserData(userId);
+ List<String> packages = policies.mLockTaskPackages;
+ int features = policies.mLockTaskFeatures;
+ if (!packages.isEmpty()) {
+ setLockTaskPolicyInPolicyEngine(profileOwner, userId, packages, features);
+ }
+ }
+ }
+ });
+ }
+
+ private void setLockTaskPolicyInPolicyEngine(
+ ActiveAdmin admin, int userId, List<String> packages, int features) {
+ EnforcingAdmin enforcingAdmin =
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ admin.info.getComponent(),
+ userId,
+ admin);
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.LOCK_TASK,
+ enforcingAdmin,
+ new LockTaskPolicy(new HashSet<>(packages), features),
+ userId);
+ }
+
private void migratePermittedInputMethodsPolicyLocked() {
Binder.withCleanCallingIdentity(() -> {
List<UserInfo> users = mUserManager.getUsers();
@@ -24256,4 +24336,13 @@
return mDevicePolicyEngine.getMaxPolicyStorageLimit();
}
+
+ @Override
+ public int getHeadlessDeviceOwnerMode(String callerPackageName) {
+ final CallerIdentity caller = getCallerIdentity(callerPackageName);
+ enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(),
+ caller.getUserId());
+
+ return Binder.withCleanCallingIdentity(() -> getHeadlessDeviceOwnerMode());
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index c5a9888..7912cbc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -623,12 +623,25 @@
}
}
+ void markPostUpgradeMigration() {
+ synchronized (mData) {
+ mData.mPoliciesMigratedPostUpdate = true;
+ mData.writeDeviceOwner();
+ }
+ }
+
boolean isSecurityLoggingMigrated() {
synchronized (mData) {
return mData.mSecurityLoggingMigrated;
}
}
+ boolean isMigratedPostUpdate() {
+ synchronized (mData) {
+ return mData.mPoliciesMigratedPostUpdate;
+ }
+ }
+
@GuardedBy("mData")
void pushToAppOpsLocked() {
if (!mSystemReady) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 9d73ed0..42ac998 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -89,6 +89,8 @@
private static final String ATTR_MIGRATED_TO_POLICY_ENGINE = "migratedToPolicyEngine";
private static final String ATTR_SECURITY_LOG_MIGRATED = "securityLogMigrated";
+ private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade";
+
// Internal state for the device owner package.
OwnerInfo mDeviceOwner;
int mDeviceOwnerUserId = UserHandle.USER_NULL;
@@ -117,6 +119,8 @@
boolean mMigratedToPolicyEngine = false;
boolean mSecurityLoggingMigrated = false;
+ boolean mPoliciesMigratedPostUpdate = false;
+
OwnersData(PolicyPathProvider pathProvider) {
mPathProvider = pathProvider;
}
@@ -400,6 +404,7 @@
out.startTag(null, TAG_POLICY_ENGINE_MIGRATION);
out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine);
+ out.attributeBoolean(null, ATTR_MIGRATED_POST_UPGRADE, mPoliciesMigratedPostUpdate);
if (Flags.securityLogV2Enabled()) {
out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
}
@@ -463,8 +468,11 @@
case TAG_POLICY_ENGINE_MIGRATION:
mMigratedToPolicyEngine = parser.getAttributeBoolean(
null, ATTR_MIGRATED_TO_POLICY_ENGINE, false);
+ mPoliciesMigratedPostUpdate = parser.getAttributeBoolean(
+ null, ATTR_MIGRATED_POST_UPGRADE, false);
mSecurityLoggingMigrated = Flags.securityLogV2Enabled()
&& parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
+
break;
default:
Slog.e(TAG, "Unexpected tag: " + tag);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 71facab..e713a82 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -506,6 +506,10 @@
UserManager.DISALLOW_SIM_GLOBALLY,
POLICY_FLAG_GLOBAL_ONLY_POLICY);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ASSIST_CONTENT, /* flags= */ 0);
+ if (com.android.net.thread.platform.flags.Flags.threadUserRestrictionEnabled()) {
+ USER_RESTRICTION_FLAGS.put(
+ UserManager.DISALLOW_THREAD_NETWORK, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ }
for (String key : USER_RESTRICTION_FLAGS.keySet()) {
createAndAddUserRestrictionPolicyDefinition(key, USER_RESTRICTION_FLAGS.get(key));
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 73d830d..7c669f1 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2166,14 +2166,16 @@
}
t.traceEnd();
- t.traceBegin("StartVcnManagementService");
- try {
- vcnManagement = VcnManagementService.create(context);
- ServiceManager.addService(Context.VCN_MANAGEMENT_SERVICE, vcnManagement);
- } catch (Throwable e) {
- reportWtf("starting VCN Management Service", e);
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) {
+ t.traceBegin("StartVcnManagementService");
+ try {
+ vcnManagement = VcnManagementService.create(context);
+ ServiceManager.addService(Context.VCN_MANAGEMENT_SERVICE, vcnManagement);
+ } catch (Throwable e) {
+ reportWtf("starting VCN Management Service", e);
+ }
+ t.traceEnd();
}
- t.traceEnd();
t.traceBegin("StartSystemUpdateManagerService");
try {
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
index a33e52f..e5d3153 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java
@@ -91,8 +91,8 @@
@Test
public void testSoftInputShowHideHistoryDump_withNulls_doesntThrow() {
var writer = new StringWriter();
- var history = new InputMethodManagerService.SoftInputShowHideHistory();
- history.addEntry(new InputMethodManagerService.SoftInputShowHideHistory.Entry(
+ var history = new SoftInputShowHideHistory();
+ history.addEntry(new SoftInputShowHideHistory.Entry(
null,
null,
null,
diff --git a/services/tests/VpnTests/Android.bp b/services/tests/VpnTests/Android.bp
index 64a9a3b..a5011a8 100644
--- a/services/tests/VpnTests/Android.bp
+++ b/services/tests/VpnTests/Android.bp
@@ -17,8 +17,7 @@
"java/**/*.java",
"java/**/*.kt",
],
-
- defaults: ["framework-connectivity-test-defaults"],
+ sdk_version: "core_platform", // tests can use @CorePlatformApi's
test_suites: ["device-tests"],
static_libs: [
"androidx.test.rules",
@@ -32,6 +31,13 @@
"service-connectivity-tiramisu-pre-jarjar",
],
libs: [
+ // order matters: classes in framework-connectivity are resolved before framework,
+ // meaning @hide APIs in framework-connectivity are resolved before @SystemApi
+ // stubs in framework
+ "framework-connectivity.impl",
+ "framework-connectivity-t.impl",
+ "framework",
+ "framework-res",
"android.test.runner",
"android.test.base",
"android.test.mock",
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
index dc6abf1..1c71abc 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
@@ -52,7 +52,9 @@
private static final int WIDTH = 500;
private static final int HEIGHT = 900;
private static final Point PORTRAIT_SIZE = new Point(WIDTH, HEIGHT);
+ private static final Point PORTRAIT_DOUBLE_WIDTH = new Point(2 * WIDTH, HEIGHT);
private static final Point LANDSCAPE_SIZE = new Point(HEIGHT, WIDTH);
+ private static final Point LANDSCAPE_DOUBLE_HEIGHT = new Point(HEIGHT, 2 * WIDTH);
@Mock
private SurfaceControl.Transaction mMockTransaction;
@@ -69,6 +71,16 @@
}
@Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_notRotated_anisotropyCorrection() {
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+ mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(
+ PORTRAIT_DOUBLE_WIDTH);
+ }
+
+ @Test
public void testGetDisplaySurfaceDefaultSizeLocked_notRotated() {
DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
mMockDisplayAdapter);
@@ -84,6 +96,17 @@
}
@Test
+ public void testGetDisplaySurfaceDefaultSizeLocked_rotation90_anisotropyCorrection() {
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+ DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+ mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+ displayDevice.setProjectionLocked(mMockTransaction, ROTATION_90, new Rect(), new Rect());
+ assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(
+ LANDSCAPE_DOUBLE_HEIGHT);
+ }
+
+ @Test
public void testGetDisplaySurfaceDefaultSizeLocked_rotation90() {
DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
mMockDisplayAdapter);
@@ -111,8 +134,14 @@
private final DisplayDeviceInfo mDisplayDeviceInfo;
FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter) {
+ this(displayDeviceInfo, displayAdapter, /*isAnisotropyCorrectionEnabled=*/ false);
+ }
+
+ FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter,
+ boolean isAnisotropyCorrectionEnabled) {
super(displayAdapter, /* displayToken= */ null, /* uniqueId= */ "",
- InstrumentationRegistry.getInstrumentation().getContext());
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ isAnisotropyCorrectionEnabled);
mDisplayDeviceInfo = displayDeviceInfo;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index 1c43418..549f0d7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -106,8 +106,184 @@
}
@Test
+ public void testLetterbox() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ false);
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+
+ /*
+ * Content is too wide, should become letterboxed
+ * ______DISPLAY_WIDTH________
+ * | |
+ * |________________________|
+ * | |
+ * | CONTENT |
+ * | |
+ * |________________________|
+ * | |
+ * |________________________|
+ */
+ // Make a wide application content, by reducing its height.
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT / 2;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ assertEquals(new Point(0, DISPLAY_HEIGHT / 4), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testNoLetterbox_anisotropyCorrection() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+ // to using the whole screen. This is because display will rescale it back to fill the
+ // screen (in case the display menu setting is set to stretch the pixels across the display)
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ // Content width re-scaled
+ assertEquals(DISPLAY_WIDTH * 2, originalDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ // Applications need to think that they are shown on a display with square pixels.
+ // as applications can be displayed on multiple displays simultaneously (mirrored).
+ // Content is too wide, should have become letterboxed - but it won't because of anisotropy
+ // correction
+ assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testLetterbox_anisotropyCorrectionYDpi() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT / 2;
+ mDisplayDeviceInfo.xDpi = 1.0f;
+ mDisplayDeviceInfo.yDpi = 0.5f;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ assertEquals(new Point(0, 75), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testPillarbox() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ false);
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.rotation = Surface.ROTATION_90;
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT;
+ mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+
+ var updatedDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ assertEquals(Surface.ROTATION_90, updatedDisplayInfo.rotation);
+ assertEquals(DISPLAY_WIDTH, updatedDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT, updatedDisplayInfo.logicalHeight);
+
+ /*
+ * Content is too tall, should become pillarboxed
+ * ______DISPLAY_WIDTH________
+ * | | | |
+ * | | | |
+ * | | | |
+ * | | CONTENT | |
+ * | | | |
+ * | | | |
+ * |____|________________|____|
+ */
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ assertEquals(new Point(75, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testPillarbox_anisotropyCorrection() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_WIDTH;
+ displayInfo.logicalHeight = DISPLAY_HEIGHT;
+ displayInfo.rotation = Surface.ROTATION_90;
+ mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+ // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+ // to using the whole screen. This is because display will rescale it back to fill the
+ // screen (in case the display menu setting is set to stretch the pixels across the display)
+ mDisplayDeviceInfo.xDpi = 0.5f;
+ mDisplayDeviceInfo.yDpi = 1.0f;
+ mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ // Applications need to think that they are shown on a display with square pixels.
+ // as applications can be displayed on multiple displays simultaneously (mirrored).
+ // Content is a bit wider than in #testPillarbox, due to content added stretching
+ assertEquals(new Point(50, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
+ public void testNoPillarbox_anisotropyCorrectionYDpi() {
+ mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+ /*isAnisotropyCorrectionEnabled=*/ true);
+
+ // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+ // to using the whole screen. This is because display will rescale it back to fill the
+ // screen (in case the display menu setting is set to stretch the pixels across the display)
+ mDisplayDeviceInfo.xDpi = 1.0f;
+ mDisplayDeviceInfo.yDpi = 0.5f;
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+ // Content width re-scaled
+ assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth);
+ assertEquals(DISPLAY_HEIGHT * 2, originalDisplayInfo.logicalHeight);
+
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+ // Applications need to think that they are shown on a display with square pixels.
+ // as applications can be displayed on multiple displays simultaneously (mirrored).
+ // Content is too tall, should have occupy the whole screen - but it won't because of
+ // anisotropy correction
+ assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+ }
+
+ @Test
public void testGetDisplayPosition() {
- Point expectedPosition = new Point();
+ Point expectedPosition = new Point(0, 0);
SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 1dd64ff..5582e13 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -145,6 +145,7 @@
@SmallTest
@Test
+ @Ignore("b/277916462")
public void testCompMigrationUnAffiliated_skipped() throws Exception {
prepareAdmin1AsDo();
prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID);
@@ -216,6 +217,7 @@
@SmallTest
@Test
+ @Ignore("b/277916462")
public void testCompMigration_keepSuspendedAppsWhenDpcIsRPlus() throws Exception {
prepareAdmin1AsDo();
prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R);
@@ -249,6 +251,7 @@
@SmallTest
@Test
+ @Ignore("b/277916462")
public void testCompMigration_unsuspendAppsWhenDpcNotRPlus() throws Exception {
prepareAdmin1AsDo();
prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index dc504ca..a35a35a 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -153,7 +153,8 @@
= SystemProperties.getBoolean("persist.debug.time_correction", true);
private static final boolean USE_DEDICATED_HANDLER_THREAD =
- SystemProperties.getBoolean("persist.debug.use_dedicated_handler_thread", false);
+ SystemProperties.getBoolean("persist.debug.use_dedicated_handler_thread",
+ Flags.useDedicatedHandlerThread());
static final boolean DEBUG = false; // Never submit with true
static final boolean DEBUG_RESPONSE_STATS = DEBUG || Log.isLoggable(TAG, Log.DEBUG);
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
deleted file mode 100644
index 8faf224..0000000
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.input.screenshot
-
-import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
-import platform.test.screenshot.PathConfig
-
-/** A [GoldenImagePathManager] that should be used for all Input screenshot tests. */
-class InputGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String
-) :
- GoldenImagePathManager(
- appContext = InstrumentationRegistry.getInstrumentation().context,
- assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
- deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
- .targetContext
- .filesDir
- .absolutePath
- .toString() + "/input_screenshots",
- pathConfig = pathConfig,
- ) {
- override fun toString(): String {
- // This string is appended to all actual/expected screenshots on the device, so make sure
- // it is a static value.
- return "InputGoldenImagePathManager"
- }
-}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
similarity index 62%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
copy to tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
index f5fba7f..9f14b136 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,31 +14,28 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot.util
+package com.android.input.screenshot
import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.GoldenPathManager
import platform.test.screenshot.PathConfig
-/** A [GoldenImagePathManager] that should be used for all Settings screenshot tests. */
-class SettingsGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String
-) :
- GoldenImagePathManager(
+/** A [GoldenPathManager] that should be used for all Input screenshot tests. */
+class InputGoldenPathManager(pathConfig: PathConfig, assetsPathRelativeToBuildRoot: String) :
+ GoldenPathManager(
appContext = InstrumentationRegistry.getInstrumentation().context,
assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
deviceLocalPath =
- InstrumentationRegistry.getInstrumentation()
- .targetContext
- .filesDir
- .absolutePath
- .toString() + "/settings_screenshots",
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/input_screenshots",
pathConfig = pathConfig,
) {
override fun toString(): String {
// This string is appended to all actual/expected screenshots on the device, so make sure
// it is a static value.
- return "SettingsGoldenImagePathManager"
+ return "InputGoldenPathManager"
}
}
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
index 75dab41..2f40896 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
@@ -44,7 +44,7 @@
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- InputGoldenImagePathManager(
+ InputGoldenPathManager(
getEmulatedDevicePathConfig(emulationSpec),
assetsPathRelativeToBuildRoot
)