diff options
545 files changed, 14135 insertions, 3866 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index a5178cfaeec8..2623702f1dc9 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -35,6 +35,7 @@ java_defaults { ":android.widget.flags-aconfig-java{.generated_srcjars}", ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}", ":sdk_sandbox_flags_lib{.generated_srcjars}", + ":android.permission.flags-aconfig-java{.generated_srcjars}", ], // Add aconfig-annotations-lib as a dependency for the optimization libs: ["aconfig-annotations-lib"], @@ -130,7 +131,6 @@ java_aconfig_library { name: "android.security.flags-aconfig-java-host", aconfig_declarations: "android.security.flags-aconfig", host_supported: true, - test: true, defaults: ["framework-minus-apex-aconfig-java-defaults"], } @@ -250,3 +250,16 @@ java_aconfig_library { aconfig_declarations: "com.android.media.flags.bettertogether-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Permissions +aconfig_declarations { + name: "android.permission.flags-aconfig", + package: "android.permission.flags", + srcs: ["core/java/android/permission/flags.aconfig"], +} + +java_aconfig_library { + name: "android.permission.flags-aconfig-java", + aconfig_declarations: "android.permission.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/api/gen_combined_removed_dex.sh b/api/gen_combined_removed_dex.sh index 9225fe8dfe85..71f366a6aae2 100755 --- a/api/gen_combined_removed_dex.sh +++ b/api/gen_combined_removed_dex.sh @@ -6,6 +6,6 @@ shift 2 # Convert each removed.txt to the "dex format" equivalent, and print all output. for f in "$@"; do - "$metalava_path" --no-banner "$f" --dex-api "${tmp_dir}/tmp" + "$metalava_path" "$f" --dex-api "${tmp_dir}/tmp" cat "${tmp_dir}/tmp" done diff --git a/core/api/current.txt b/core/api/current.txt index a6b77ea1be06..a5784a048274 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -32285,6 +32285,7 @@ package android.os { method public static long getNativeHeapFreeSize(); method public static long getNativeHeapSize(); method public static long getPss(); + method @FlaggedApi(Flags.FLAG_REMOVE_APP_PROFILER_PSS_COLLECTION) public static long getRss(); method public static String getRuntimeStat(String); method public static java.util.Map<java.lang.String,java.lang.String> getRuntimeStats(); method @Deprecated public static int getThreadAllocCount(); @@ -38658,7 +38659,7 @@ package android.security { public final class FileIntegrityManager { method @FlaggedApi(Flags.FLAG_FSVERITY_API) @Nullable public byte[] getFsVerityDigest(@NonNull java.io.File) throws java.io.IOException; method public boolean isApkVeritySupported(); - method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public boolean isAppSourceCertificateTrusted(@NonNull java.security.cert.X509Certificate) throws java.security.cert.CertificateEncodingException; + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public boolean isAppSourceCertificateTrusted(@NonNull java.security.cert.X509Certificate) throws java.security.cert.CertificateEncodingException; method @FlaggedApi(Flags.FLAG_FSVERITY_API) public void setupFsVerity(@NonNull java.io.File) throws java.io.IOException; } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index fcfa41a6efff..5c48b21ad35e 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -95,6 +95,7 @@ package android { field public static final String BYPASS_ROLE_QUALIFICATION = "android.permission.BYPASS_ROLE_QUALIFICATION"; field public static final String CALL_AUDIO_INTERCEPTION = "android.permission.CALL_AUDIO_INTERCEPTION"; field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED"; + field public static final String CAMERA_HEADLESS_SYSTEM_USER = "android.permission.CAMERA_HEADLESS_SYSTEM_USER"; field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER"; field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD"; field public static final String CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD"; @@ -3483,6 +3484,7 @@ package android.content { field public static final String SYSTEM_CONFIG_SERVICE = "system_config"; field public static final String SYSTEM_UPDATE_SERVICE = "system_update"; field public static final String TETHERING_SERVICE = "tethering"; + field public static final String THREAD_NETWORK_SERVICE = "thread_network"; field public static final String TIME_MANAGER_SERVICE = "time_manager"; field public static final String TRANSLATION_MANAGER_SERVICE = "translation"; field public static final String UI_TRANSLATION_SERVICE = "ui_translation"; @@ -3798,10 +3800,6 @@ package android.content.pm { field @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800 } - public class PackageInfo implements android.os.Parcelable { - field public boolean isArchived; - } - public class PackageInstaller { method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException; method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException; @@ -3879,6 +3877,7 @@ package android.content.pm { method public static void forceSafeLabels(); method @Deprecated @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager); method @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager, @FloatRange(from=0) float, int); + field @FlaggedApi(Flags.FLAG_ARCHIVING) public boolean isArchived; } public abstract class PackageManager { @@ -16707,10 +16706,12 @@ package android.telephony.satellite { field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3 field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2 field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff + field @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7 field public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3 field public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2 field public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0 field public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1 + field @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6 field public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4 field public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5 field public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 642813f36a58..a4cc44646341 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -50,6 +50,7 @@ package android { field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS"; field public static final String SET_GAME_SERVICE = "android.permission.SET_GAME_SERVICE"; field public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT"; + field public static final String START_ACTIVITIES_FROM_SDK_SANDBOX = "android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC"; field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD"; @@ -2115,6 +2116,7 @@ package android.net.wifi.sharedconnectivity.app { public class SharedConnectivityManager { method @Nullable public static android.net.wifi.sharedconnectivity.app.SharedConnectivityManager create(@NonNull android.content.Context, @NonNull String, @NonNull String); + method @NonNull public android.content.BroadcastReceiver getBroadcastReceiver(); method @Nullable public android.content.ServiceConnection getServiceConnection(); method public void setService(@Nullable android.os.IInterface); } @@ -3504,6 +3506,10 @@ package android.view { field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800 } + public static final class MotionEvent.PointerCoords { + method public boolean isResampled(); + } + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface RemotableViewMethod { method public abstract String asyncImpl() default ""; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 00e546ad25b0..01912012f04a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3720,7 +3720,19 @@ public final class ActivityThread extends ClientTransactionHandler /** Core implementation of activity launch. */ private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ActivityInfo aInfo = r.activityInfo; - if (r.packageInfo == null) { + + if (getInstrumentation() != null + && getInstrumentation().getContext() != null + && getInstrumentation().getContext().getApplicationInfo() != null + && getInstrumentation().isSdkSandboxAllowedToStartActivities()) { + // Activities launched from CTS-in-sandbox tests use a customized ApplicationInfo. See + // also {@link SdkSandboxManagerLocal#getSdkSandboxApplicationInfoForInstrumentation}. + r.packageInfo = + getPackageInfo( + getInstrumentation().getContext().getApplicationInfo(), + mCompatibilityInfo, + Context.CONTEXT_INCLUDE_CODE); + } else if (r.packageInfo == null) { r.packageInfo = getPackageInfo(aInfo.applicationInfo, mCompatibilityInfo, Context.CONTEXT_INCLUDE_CODE); } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 02558602acd3..04d04b9adbdd 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -3355,7 +3355,11 @@ public class ApplicationPackageManager extends PackageManager { } Drawable dr = null; if (itemInfo.packageName != null) { - dr = getDrawable(itemInfo.packageName, itemInfo.icon, appInfo); + if (itemInfo.isArchived) { + dr = getArchivedAppIcon(itemInfo.packageName); + } else { + dr = getDrawable(itemInfo.packageName, itemInfo.icon, appInfo); + } } if (dr == null && itemInfo != appInfo && appInfo != null) { dr = loadUnbadgedItemIcon(appInfo, appInfo); @@ -3964,4 +3968,14 @@ public class ApplicationPackageManager extends PackageManager { throw e.rethrowFromSystemServer(); } } + + @Nullable + private Drawable getArchivedAppIcon(String packageName) { + try { + return new BitmapDrawable(null, + mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId()))); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index e31486f18dbf..10747bb0e57e 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.hardware.input.InputManager; import android.hardware.input.InputManagerGlobal; @@ -474,6 +475,56 @@ public class Instrumentation { sr.waitForComplete(); } + boolean isSdkSandboxAllowedToStartActivities() { + return Process.isSdkSandbox() + && mThread != null + && mThread.mBoundApplication != null + && mThread.mBoundApplication.isSdkInSandbox + && getContext() != null + && (getContext() + .checkSelfPermission( + android.Manifest.permission + .START_ACTIVITIES_FROM_SDK_SANDBOX) + == PackageManager.PERMISSION_GRANTED); + } + + /** + * Activity name resolution for CTS-in-SdkSandbox tests requires some adjustments. Intents + * generated using {@link Context#getPackageName()} use the SDK sandbox package name in the + * component field instead of the test package name. An SDK-in-sandbox test attempting to launch + * an activity in the test package will encounter name resolution errors when resolving the + * activity name in the SDK sandbox package. + * + * <p>This function replaces the package name of the input intent component to allow activities + * belonging to a CTS-in-sandbox test to resolve correctly. + * + * @param intent the intent to modify to allow CTS-in-sandbox activity resolution. + */ + private void adjustIntentForCtsInSdkSandboxInstrumentation(@NonNull Intent intent) { + if (mComponent != null + && intent.getComponent() != null + && getContext() + .getPackageManager() + .getSdkSandboxPackageName() + .equals(intent.getComponent().getPackageName())) { + // Resolve the intent target for the test package, not for the sandbox package. + intent.setComponent( + new ComponentName( + mComponent.getPackageName(), intent.getComponent().getClassName())); + } + // We match the intent identifier against the running instrumentations for the sandbox. + intent.setIdentifier(mComponent.getPackageName()); + } + + private ActivityInfo resolveActivityInfoForCtsInSandbox(@NonNull Intent intent) { + adjustIntentForCtsInSdkSandboxInstrumentation(intent); + ActivityInfo ai = intent.resolveActivityInfo(getTargetContext().getPackageManager(), 0); + if (ai != null) { + ai.processName = mThread.getProcessName(); + } + return ai; + } + /** * Start a new activity and wait for it to begin running before returning. * In addition to being synchronous, this method as some semantic @@ -531,8 +582,10 @@ public class Instrumentation { synchronized (mSync) { intent = new Intent(intent); - ActivityInfo ai = intent.resolveActivityInfo( - getTargetContext().getPackageManager(), 0); + ActivityInfo ai = + isSdkSandboxAllowedToStartActivities() + ? resolveActivityInfoForCtsInSandbox(intent) + : intent.resolveActivityInfo(getTargetContext().getPackageManager(), 0); if (ai == null) { throw new RuntimeException("Unable to resolve activity for: " + intent); } @@ -1842,6 +1895,9 @@ public class Instrumentation { if (referrer != null) { intent.putExtra(Intent.EXTRA_REFERRER, referrer); } + if (isSdkSandboxAllowedToStartActivities()) { + adjustIntentForCtsInSdkSandboxInstrumentation(intent); + } if (mActivityMonitors != null) { synchronized (mSync) { final int N = mActivityMonitors.size(); @@ -1914,6 +1970,11 @@ public class Instrumentation { IBinder token, Activity target, Intent[] intents, Bundle options, int userId) { IApplicationThread whoThread = (IApplicationThread) contextThread; + if (isSdkSandboxAllowedToStartActivities()) { + for (Intent intent : intents) { + adjustIntentForCtsInSdkSandboxInstrumentation(intent); + } + } if (mActivityMonitors != null) { synchronized (mSync) { final int N = mActivityMonitors.size(); @@ -1989,6 +2050,9 @@ public class Instrumentation { Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; + if (isSdkSandboxAllowedToStartActivities()) { + adjustIntentForCtsInSdkSandboxInstrumentation(intent); + } if (mActivityMonitors != null) { synchronized (mSync) { final int N = mActivityMonitors.size(); @@ -2060,6 +2124,9 @@ public class Instrumentation { Context who, IBinder contextThread, IBinder token, String resultWho, Intent intent, int requestCode, Bundle options, UserHandle user) { IApplicationThread whoThread = (IApplicationThread) contextThread; + if (isSdkSandboxAllowedToStartActivities()) { + adjustIntentForCtsInSdkSandboxInstrumentation(intent); + } if (mActivityMonitors != null) { synchronized (mSync) { final int N = mActivityMonitors.size(); @@ -2110,6 +2177,9 @@ public class Instrumentation { Intent intent, int requestCode, Bundle options, boolean ignoreTargetSecurity, int userId) { IApplicationThread whoThread = (IApplicationThread) contextThread; + if (isSdkSandboxAllowedToStartActivities()) { + adjustIntentForCtsInSdkSandboxInstrumentation(intent); + } if (mActivityMonitors != null) { synchronized (mSync) { final int N = mActivityMonitors.size(); @@ -2161,6 +2231,9 @@ public class Instrumentation { Context who, IBinder contextThread, IAppTask appTask, Intent intent, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; + if (isSdkSandboxAllowedToStartActivities()) { + adjustIntentForCtsInSdkSandboxInstrumentation(intent); + } if (mActivityMonitors != null) { synchronized (mSync) { final int N = mActivityMonitors.size(); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index e447dc511ced..2d554031ab48 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -49,7 +49,6 @@ import android.util.ArrayMap; import android.util.Log; import android.view.IOnKeyguardExitResult; import android.view.IWindowManager; -import android.view.WindowManager.LayoutParams; import android.view.WindowManagerGlobal; import com.android.internal.annotations.VisibleForTesting; @@ -71,9 +70,7 @@ import java.util.Objects; import java.util.concurrent.Executor; /** - * Class that can be used to lock and unlock the keyguard. The - * actual class to control the keyguard locking is - * {@link android.app.KeyguardManager.KeyguardLock}. + * Class to manage and query the state of the lock screen (also known as Keyguard). */ @SystemService(Context.KEYGUARD_SERVICE) public class KeyguardManager { @@ -259,7 +256,9 @@ public class KeyguardManager { * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge. * * @return the intent for launching the activity or null if no password is required. - * @deprecated see BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean) + * + * @deprecated see {@link + * android.hardware.biometrics.BiometricPrompt.Builder#setAllowedAuthenticators(int)} */ @Deprecated @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @@ -477,13 +476,12 @@ public class KeyguardManager { /** * Handle returned by {@link KeyguardManager#newKeyguardLock} that allows - * you to disable / reenable the keyguard. + * you to temporarily disable / reenable the keyguard (lock screen). * - * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD} - * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} - * instead; this allows you to seamlessly hide the keyguard as your application - * moves in and out of the foreground and does not require that any special - * permissions be requested. + * @deprecated Use {@link android.R.attr#showWhenLocked} or {@link + * android.app.Activity#setShowWhenLocked(boolean)} instead. This allows you to seamlessly + * occlude and unocclude the keyguard as your application moves in and out of the foreground + * and does not require that any special permissions be requested. */ @Deprecated public class KeyguardLock { @@ -498,12 +496,12 @@ public class KeyguardManager { * Disable the keyguard from showing. If the keyguard is currently * showing, hide it. The keyguard will be prevented from showing again * until {@link #reenableKeyguard()} is called. - * + * <p> + * This only works if the keyguard is not secure. + * <p> * A good place to call this is from {@link android.app.Activity#onResume()} * - * Note: This call has no effect while any {@link android.app.admin.DevicePolicyManager} - * is enabled that requires a password. - * + * @see KeyguardManager#isKeyguardSecure() * @see #reenableKeyguard() */ @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD) @@ -520,9 +518,6 @@ public class KeyguardManager { * * A good place to call this is from {@link android.app.Activity#onPause()} * - * Note: This call has no effect while any {@link android.app.admin.DevicePolicyManager} - * is enabled that requires a password. - * * @see #disableKeyguard() */ @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD) @@ -621,20 +616,18 @@ public class KeyguardManager { } /** - * Enables you to lock or unlock the keyguard. Get an instance of this class by - * calling {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. - * This class is wrapped by {@link android.app.KeyguardManager KeyguardManager}. + * Enables you to temporarily disable / reenable the keyguard (lock screen). + * * @param tag A tag that informally identifies who you are (for debugging who * is disabling the keyguard). * * @return A {@link KeyguardLock} handle to use to disable and reenable the * keyguard. * - * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD} - * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} - * instead; this allows you to seamlessly hide the keyguard as your application - * moves in and out of the foreground and does not require that any special - * permissions be requested. + * @deprecated Use {@link android.R.attr#showWhenLocked} or {@link + * android.app.Activity#setShowWhenLocked(boolean)} instead. This allows you to seamlessly + * occlude and unocclude the keyguard as your application moves in and out of the foreground + * and does not require that any special permissions be requested. */ @Deprecated public KeyguardLock newKeyguardLock(String tag) { @@ -642,9 +635,36 @@ public class KeyguardManager { } /** - * Return whether the keyguard is currently locked. + * Returns whether the lock screen (also known as Keyguard) is showing. + * <p> + * Specifically, this returns {@code true} in the following cases: + * <ul> + * <li>The lock screen is showing in the foreground.</li> + * <li>The lock screen is showing, but it is occluded by an activity that is showing on top of + * it. A common example is the phone app receiving a call or making an emergency call.</li> + * <li>The lock screen was showing but is temporarily disabled as a result of <a + * href="https://developer.android.com/work/dpc/dedicated-devices/lock-task-mode">lock task + * mode</a> or an app using the deprecated {@link KeyguardLock} API.</li> + * </ul> + * <p> + * "Showing" refers to a logical state of the UI, regardless of whether the screen happens to be + * on. When the power button is pressed on an unlocked device, the lock screen starts "showing" + * immediately when the screen turns off. + * <p> + * This method does not distinguish a lock screen that is requiring authentication (e.g. with + * PIN, pattern, password, or biometric) from a lock screen that is trivially dismissible (e.g. + * with swipe). It also does not distinguish a lock screen requesting a SIM card PIN from a + * normal device lock screen. Finally, it always returns the global lock screen state and does + * not consider the {@link Context}'s user specifically. + * <p> + * Note that {@code isKeyguardLocked()} is confusingly named and probably should be called + * {@code isKeyguardShowing()}. On many devices, the lock screen displays an <i>unlocked</i> + * padlock icon when it is trivially dismissible. As mentioned above, {@code isKeyguardLocked()} + * actually returns {@code true} in this case, not {@code false} as might be expected. {@link + * #isDeviceLocked()} is an alternative API that has slightly different semantics. * - * @return {@code true} if the keyguard is locked. + * @return {@code true} if the lock screen is showing + * @see #isDeviceLocked() */ public boolean isKeyguardLocked() { try { @@ -655,12 +675,23 @@ public class KeyguardManager { } /** - * Return whether the keyguard is secured by a PIN, pattern or password or a SIM card - * is currently locked. - * - * <p>See also {@link #isDeviceSecure()} which ignores SIM locked states. + * Returns whether the user has a secure lock screen or there is a locked SIM card. + * <p> + * Specifically, this returns {@code true} if at least one of the following is true: + * <ul> + * <li>The {@link Context}'s user has a secure lock screen. A full user has a secure lock + * screen if its lock screen is set to PIN, pattern, or password, as opposed to swipe or none. + * A profile that uses a unified challenge is considered to have a secure lock screen if and + * only if its parent user has a secure lock screen.</li> + * <li>At least one SIM card is currently locked and requires a PIN.</li> + * </ul> + * <p> + * This method does not consider whether the lock screen is currently showing or not. + * <p> + * See also {@link #isDeviceSecure()} which excludes locked SIM cards. * - * @return {@code true} if a PIN, pattern or password is set or a SIM card is locked. + * @return {@code true} if the user has a secure lock screen or there is a locked SIM card + * @see #isDeviceSecure() */ public boolean isKeyguardSecure() { try { @@ -671,11 +702,11 @@ public class KeyguardManager { } /** - * If keyguard screen is showing or in restricted key input mode (i.e. in - * keyguard password emergency screen). When in such mode, certain keys, - * such as the Home key and the right soft keys, don't work. + * Returns whether the lock screen is showing. + * <p> + * This is exactly the same as {@link #isKeyguardLocked()}. * - * @return {@code true} if in keyguard restricted input mode. + * @return the value of {@link #isKeyguardLocked()} * @deprecated Use {@link #isKeyguardLocked()} instead. */ public boolean inKeyguardRestrictedInputMode() { @@ -683,11 +714,26 @@ public class KeyguardManager { } /** - * Returns whether the device is currently locked and requires a PIN, pattern or - * password to unlock. + * Returns whether the device is currently locked for the user. + * <p> + * This returns the device locked state for the {@link Context}'s user. If this user is the + * current user, then the device is considered "locked" when the lock screen is showing (i.e. + * {@link #isKeyguardLocked()} returns {@code true}) and is not trivially dismissible (e.g. with + * swipe), and the user has a PIN, pattern, or password. + * <p> + * Note: the above definition implies that a user with no PIN, pattern, or password is never + * considered locked, even if the lock screen is showing and requesting a SIM card PIN. The + * device PIN and SIM PIN are separate. Also, the user is not considered locked if face + * authentication has just completed or a trust agent is keeping the device unlocked, since in + * these cases the lock screen is dismissible with swipe. + * <p> + * For a user that is not the current user but can be switched to (usually this means "another + * full user"), and that has a PIN, pattern, or password, the device is always considered + * locked. For a profile with a unified challenge, the device is considered locked if and only + * if the device is locked for the parent user. * - * @return {@code true} if unlocking the device currently requires a PIN, pattern or - * password. + * @return {@code true} if the device is currently locked for the user + * @see #isKeyguardLocked() */ public boolean isDeviceLocked() { return isDeviceLocked(mContext.getUserId()); @@ -708,12 +754,19 @@ public class KeyguardManager { } /** - * Returns whether the device is secured with a PIN, pattern or - * password. - * - * <p>See also {@link #isKeyguardSecure} which treats SIM locked states as secure. + * Returns whether the user has a secure lock screen. + * <p> + * This returns {@code true} if the {@link Context}'s user has a secure lock screen. A full user + * has a secure lock screen if its lock screen is set to PIN, pattern, or password, as opposed + * to swipe or none. A profile that uses a unified challenge is considered to have a secure lock + * screen if and only if its parent user has a secure lock screen. + * <p> + * This method does not consider whether the lock screen is currently showing or not. + * <p> + * See also {@link #isKeyguardSecure()} which includes locked SIM cards. * - * @return {@code true} if a PIN, pattern or password was set. + * @return {@code true} if the user has a secure lock screen + * @see #isKeyguardSecure() */ public boolean isDeviceSecure() { return isDeviceSecure(mContext.getUserId()); @@ -734,8 +787,7 @@ public class KeyguardManager { } /** - * If the device is currently locked (see {@link #isKeyguardLocked()}, requests the Keyguard to - * be dismissed. + * Requests that the Keyguard (lock screen) be dismissed if it is currently showing. * <p> * If the Keyguard is not secure or the device is currently in a trusted state, calling this * method will immediately dismiss the Keyguard without any user interaction. @@ -746,8 +798,9 @@ public class KeyguardManager { * If the value set for the {@link Activity} attr {@link android.R.attr#turnScreenOn} is true, * the screen will turn on when the keyguard is dismissed. * - * @param activity The activity requesting the dismissal. The activity must be either visible - * by using {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} or must be in a state in + * @param activity The activity requesting the dismissal. The activity must either be visible + * by using {@link android.R.attr#showWhenLocked} or {@link + * android.app.Activity#setShowWhenLocked(boolean)}, or must be in a state in * which it would be visible if Keyguard would not be hiding it. If that's not * the case, the request will fail immediately and * {@link KeyguardDismissCallback#onDismissError} will be invoked. @@ -762,8 +815,7 @@ public class KeyguardManager { } /** - * If the device is currently locked (see {@link #isKeyguardLocked()}, requests the Keyguard to - * be dismissed. + * Requests that the Keyguard (lock screen) be dismissed if it is currently showing. * <p> * If the Keyguard is not secure or the device is currently in a trusted state, calling this * method will immediately dismiss the Keyguard without any user interaction. @@ -774,8 +826,9 @@ public class KeyguardManager { * If the value set for the {@link Activity} attr {@link android.R.attr#turnScreenOn} is true, * the screen will turn on when the keyguard is dismissed. * - * @param activity The activity requesting the dismissal. The activity must be either visible - * by using {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} or must be in a state in + * @param activity The activity requesting the dismissal. The activity must either be visible + * by using {@link android.R.attr#showWhenLocked} or {@link + * android.app.Activity#setShowWhenLocked(boolean)}, or must be in a state in * which it would be visible if Keyguard would not be hiding it. If that's not * the case, the request will fail immediately and * {@link KeyguardDismissCallback#onDismissError} will be invoked. @@ -829,12 +882,12 @@ public class KeyguardManager { * @param callback Lets you know whether the operation was successful and * it is safe to launch anything that would normally be considered safe * once the user has gotten past the keyguard. - - * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD} - * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} - * instead; this allows you to seamlessly hide the keyguard as your application - * moves in and out of the foreground and does not require that any special - * permissions be requested. + * + * @deprecated Use {@link android.R.attr#showWhenLocked} or {@link + * android.app.Activity#setShowWhenLocked(boolean)} to seamlessly occlude and unocclude the + * keyguard as your application moves in and out of the foreground, without requiring any + * special permissions. Use {@link #requestDismissKeyguard(android.app.Activity, + * KeyguardDismissCallback)} to request dismissal of the keyguard. */ @Deprecated @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD) diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 715edc5161b7..213e5cb4ad64 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -11806,6 +11806,34 @@ public class DevicePolicyManager { } /** + * Returns the list of {@link EnforcingAdmin}s who have set this restriction. + * + * <p>Note that for {@link #POLICY_SUSPEND_PACKAGES} it returns the PO or DO to keep the + * behavior the same as before the bug fix for b/192245204. + * + * <p>This API is only callable by the system UID + * + * @param userId The user for whom to retrieve the information. + * @param restriction The restriction enforced by admins. It could be any user restriction or + * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and + * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}. + * + * @hide + */ + public @NonNull Set<EnforcingAdmin> getEnforcingAdminsForRestriction(int userId, + @NonNull String restriction) { + if (mService != null) { + try { + return new HashSet<>(mService.getEnforcingAdminsForRestriction( + userId, restriction)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return null; + } + + /** * Hide or unhide packages. When a package is hidden it is unavailable for use, but the data and * actual package file remain. This function can be called by a device owner, profile owner, or * by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via diff --git a/core/java/android/app/admin/EnforcingAdmin.aidl b/core/java/android/app/admin/EnforcingAdmin.aidl new file mode 100644 index 000000000000..bfbfdbeaf9aa --- /dev/null +++ b/core/java/android/app/admin/EnforcingAdmin.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +parcelable EnforcingAdmin;
\ No newline at end of file diff --git a/core/java/android/app/admin/EnforcingAdmin.java b/core/java/android/app/admin/EnforcingAdmin.java index 771794dbe0fb..7c718f6651a2 100644 --- a/core/java/android/app/admin/EnforcingAdmin.java +++ b/core/java/android/app/admin/EnforcingAdmin.java @@ -19,6 +19,7 @@ package android.app.admin; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; @@ -38,6 +39,11 @@ public final class EnforcingAdmin implements Parcelable { private final UserHandle mUserHandle; /** + * @hide + */ + private final ComponentName mComponentName; + + /** * Creates an enforcing admin with the given params. */ public EnforcingAdmin( @@ -46,6 +52,21 @@ public final class EnforcingAdmin implements Parcelable { mPackageName = Objects.requireNonNull(packageName); mAuthority = Objects.requireNonNull(authority); mUserHandle = Objects.requireNonNull(userHandle); + mComponentName = null; + } + + /** + * Creates an enforcing admin with the given params. + * + * @hide + */ + public EnforcingAdmin( + @NonNull String packageName, @NonNull Authority authority, + @NonNull UserHandle userHandle, @Nullable ComponentName componentName) { + mPackageName = Objects.requireNonNull(packageName); + mAuthority = Objects.requireNonNull(authority); + mUserHandle = Objects.requireNonNull(userHandle); + mComponentName = componentName; } private EnforcingAdmin(Parcel source) { @@ -53,6 +74,7 @@ public final class EnforcingAdmin implements Parcelable { mUserHandle = new UserHandle(source.readInt()); mAuthority = Objects.requireNonNull( source.readParcelable(Authority.class.getClassLoader())); + mComponentName = source.readParcelable(ComponentName.class.getClassLoader()); } /** @@ -86,7 +108,8 @@ public final class EnforcingAdmin implements Parcelable { EnforcingAdmin other = (EnforcingAdmin) o; return Objects.equals(mPackageName, other.mPackageName) && Objects.equals(mAuthority, other.mAuthority) - && Objects.equals(mUserHandle, other.mUserHandle); + && Objects.equals(mUserHandle, other.mUserHandle) + && Objects.equals(mComponentName, other.mComponentName); } @Override @@ -97,7 +120,7 @@ public final class EnforcingAdmin implements Parcelable { @Override public String toString() { return "EnforcingAdmin { mPackageName= " + mPackageName + ", mAuthority= " + mAuthority - + ", mUserHandle= " + mUserHandle + " }"; + + ", mUserHandle= " + mUserHandle + ", mComponentName= " + mComponentName + " }"; } @Override @@ -110,6 +133,7 @@ public final class EnforcingAdmin implements Parcelable { dest.writeString(mPackageName); dest.writeInt(mUserHandle.getIdentifier()); dest.writeParcelable(mAuthority, flags); + dest.writeParcelable(mComponentName, flags); } @NonNull diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 95ec89e5f444..c49b820b9e37 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -54,6 +54,7 @@ import android.security.keystore.ParcelableKeyGenParameterSpec; import android.telephony.data.ApnSetting; import com.android.internal.infra.AndroidFuture; import android.app.admin.DevicePolicyState; +import android.app.admin.EnforcingAdmin; import java.util.List; @@ -274,6 +275,7 @@ interface IDevicePolicyManager { Intent createAdminSupportIntent(in String restriction); Bundle getEnforcingAdminAndUserDetails(int userId,String restriction); + List<EnforcingAdmin> getEnforcingAdminsForRestriction(int userId,String restriction); boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden, boolean parent); boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean parent); diff --git a/core/java/android/app/admin/Provisioning_OWNERS b/core/java/android/app/admin/Provisioning_OWNERS index fa0a1f09bfc8..8f71fc0c4c05 100644 --- a/core/java/android/app/admin/Provisioning_OWNERS +++ b/core/java/android/app/admin/Provisioning_OWNERS @@ -1,4 +1,4 @@ # Assign bugs to android-enterprise-triage@google.com -mdb.ae-provisioning-reviews@google.com +ae-provisioning-reviews@google.com +petuska@google.com #{LAST_RESORT_SUGGESTION} file:EnterprisePlatform_OWNERS -petuska@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index d6dee9389c8e..b6a98a5b8f83 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4089,6 +4089,7 @@ public abstract class Context { VIBRATOR_MANAGER_SERVICE, VIBRATOR_SERVICE, //@hide: STATUS_BAR_SERVICE, + THREAD_NETWORK_SERVICE, CONNECTIVITY_SERVICE, PAC_PROXY_SERVICE, VCN_MANAGEMENT_SERVICE, @@ -4764,6 +4765,20 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.net.thread.ThreadNetworkManager}. + * + * <p>On devices without {@link PackageManager#FEATURE_THREAD_NETWORK} system feature + * the {@link #getSystemService(String)} will return {@code null}. + * + * @see #getSystemService(String) + * @see android.net.thread.ThreadNetworkManager + * @hide + */ + @SystemApi + public static final String THREAD_NETWORK_SERVICE = "thread_network"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a * {@link android.net.IpSecManager} for encrypting Sockets or Networks with * IPSec. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index e9bbed340800..7579d99f4927 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -12516,6 +12516,7 @@ public class Intent implements Parcelable, Cloneable { return (mFlags & FLAG_ACTIVITY_NEW_DOCUMENT) == FLAG_ACTIVITY_NEW_DOCUMENT; } + // TODO(b/299109198): Refactor into the {@link SdkSandboxManagerLocal} /** @hide */ public boolean isSandboxActivity(@NonNull Context context) { if (mAction != null && mAction.equals(ACTION_START_SANDBOXED_ACTIVITY)) { diff --git a/core/java/android/content/om/OWNERS b/core/java/android/content/om/OWNERS index 3669817e9844..72aed2d3fdaf 100644 --- a/core/java/android/content/om/OWNERS +++ b/core/java/android/content/om/OWNERS @@ -1,6 +1,5 @@ # Bug component: 568631 -toddke@android.com -toddke@google.com patb@google.com zyy@google.com +jakmcbane@google.com
\ No newline at end of file diff --git a/core/java/android/content/pm/ArchivedActivityParcel.aidl b/core/java/android/content/pm/ArchivedActivityParcel.aidl new file mode 100644 index 000000000000..7ab7ed1cc5df --- /dev/null +++ b/core/java/android/content/pm/ArchivedActivityParcel.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +/** @hide */ +parcelable ArchivedActivityParcel { + String title; + // PNG compressed bitmaps. + byte[] iconBitmap; + byte[] monochromeIconBitmap; +} diff --git a/core/java/android/content/pm/ArchivedPackageParcel.aidl b/core/java/android/content/pm/ArchivedPackageParcel.aidl index 573e69094e35..d3cd79efc3b5 100644 --- a/core/java/android/content/pm/ArchivedPackageParcel.aidl +++ b/core/java/android/content/pm/ArchivedPackageParcel.aidl @@ -16,6 +16,7 @@ package android.content.pm; +import android.content.pm.ArchivedActivityParcel; import android.content.pm.SigningDetails; /** @@ -29,9 +30,8 @@ parcelable ArchivedPackageParcel { int versionCode; int versionCodeMajor; int targetSdkVersion; - String backupAllowed; String defaultToDeviceProtectedStorage; String requestLegacyExternalStorage; String userDataFragile; - String clearUserDataOnFailedRestoreAllowed; + ArchivedActivityParcel[] archivedActivities; } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 556c794b6139..aca88d6af033 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -20,6 +20,7 @@ package android.content.pm; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; +import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ArchivedPackageParcel; @@ -59,7 +60,7 @@ import android.os.Bundle; import android.os.IRemoteCallback; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; -import android.content.IntentSender; +import android.os.UserHandle; import java.util.Map; @@ -832,5 +833,7 @@ interface IPackageManager { void unregisterPackageMonitorCallback(IRemoteCallback callback); - ArchivedPackageParcel getArchivedPackage(in String apkPath); + ArchivedPackageParcel getArchivedPackage(in String packageName, int userId); + + Bitmap getArchivedAppIcon(String packageName, in UserHandle user); } diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 1fe19231e4b4..63c11b779641 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -18,9 +18,7 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; -import android.content.IntentSender; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -490,18 +488,6 @@ public class PackageInfo implements Parcelable { */ public boolean isActiveApex; - /** - * Whether the package is currently in an archived state. - * - * <p>Packages can be archived through - * {@link PackageInstaller#requestArchive(String, IntentSender)} and do not have any APKs stored - * on the device, but do keep the data directory. - * @hide - */ - // TODO(b/278553670) Unhide and update @links before launch. - @SystemApi - public boolean isArchived; - public PackageInfo() { } @@ -589,7 +575,6 @@ public class PackageInfo implements Parcelable { } dest.writeBoolean(isApex); dest.writeBoolean(isActiveApex); - dest.writeBoolean(isArchived); dest.restoreAllowSquashing(prevAllowSquashing); } @@ -655,6 +640,5 @@ public class PackageInfo implements Parcelable { } isApex = source.readBoolean(); isActiveApex = source.readBoolean(); - isArchived = source.readBoolean(); } } diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java index bb978e05dd16..c7091ad99199 100644 --- a/core/java/android/content/pm/PackageItemInfo.java +++ b/core/java/android/content/pm/PackageItemInfo.java @@ -21,6 +21,7 @@ import static android.text.TextUtils.SAFE_STRING_FLAG_SINGLE_LINE; import static android.text.TextUtils.SAFE_STRING_FLAG_TRIM; import static android.text.TextUtils.makeSafeForPresentation; +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.SystemApi; @@ -173,6 +174,18 @@ public class PackageItemInfo { */ public int showUserIcon; + /** + * Whether the package is currently in an archived state. + * + * <p>Packages can be archived through {@link PackageArchiver} and do not have any APKs stored + * on the device, but do keep the data directory. + * @hide + */ + // TODO(b/278553670) Unhide and update @links before launch. + @SystemApi + @FlaggedApi(Flags.FLAG_ARCHIVING) + public boolean isArchived; + public PackageItemInfo() { showUserIcon = UserHandle.USER_NULL; } @@ -189,6 +202,7 @@ public class PackageItemInfo { logo = orig.logo; metaData = orig.metaData; showUserIcon = orig.showUserIcon; + isArchived = orig.isArchived; } /** @@ -442,6 +456,7 @@ public class PackageItemInfo { dest.writeBundle(metaData); dest.writeInt(banner); dest.writeInt(showUserIcon); + dest.writeBoolean(isArchived); } /** @@ -459,6 +474,7 @@ public class PackageItemInfo { } proto.write(PackageItemInfoProto.ICON, icon); proto.write(PackageItemInfoProto.BANNER, banner); + proto.write(PackageItemInfoProto.IS_ARCHIVED, isArchived); proto.end(token); } @@ -473,6 +489,7 @@ public class PackageItemInfo { metaData = source.readBundle(); banner = source.readInt(); showUserIcon = source.readInt(); + isArchived = source.readBoolean(); } /** diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java index 149de7efc861..0333942b7f3e 100644 --- a/core/java/android/content/pm/RegisteredServicesCache.java +++ b/core/java/android/content/pm/RegisteredServicesCache.java @@ -834,4 +834,11 @@ public abstract class RegisteredServicesCache<V> { public abstract V parseServiceAttributes(Resources res, String packageName, AttributeSet attrs); + + @VisibleForTesting + public void unregisterReceivers() { + mContext.unregisterReceiver(mPackageReceiver); + mContext.unregisterReceiver(mExternalReceiver); + mContext.unregisterReceiver(mUserRemovedReceiver); + } } diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java index 159b789fc4a0..f3194be81b0d 100644 --- a/core/java/android/content/pm/parsing/ApkLite.java +++ b/core/java/android/content/pm/parsing/ApkLite.java @@ -23,11 +23,9 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.SigningDetails; import android.content.pm.VerifierInfo; -import android.os.Build; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; -import com.android.internal.util.XmlUtils; import java.util.List; import java.util.Set; @@ -142,32 +140,9 @@ public class ApkLite { private final boolean mIsSdkLibrary; /** - * Set to <code>false</code> if the application does not wish to permit any OS-driven - * backups of its data; <code>true</code> otherwise. + * Archival install info. */ - private final boolean mBackupAllowed; - - /** - * When set, the default data storage directory for this app is pointed at - * the device-protected location. - */ - private final boolean mDefaultToDeviceProtectedStorage; - - /** - * If {@code true} this app requests full external storage access. - */ - private final boolean mRequestLegacyExternalStorage; - - /** - * Indicates whether this application has declared its user data as fragile, causing the - * system to prompt the user on whether to keep the user data on uninstall. - */ - private final boolean mUserDataFragile; - - /** - * Indicates whether this application's data will be cleared on a failed restore. - */ - private final boolean mClearUserDataOnFailedRestoreAllowed; + private final @Nullable ArchivedPackageParcel mArchivedPackage; public ApkLite(String path, String packageName, String splitName, boolean isFeatureSplit, String configForSplit, String usesSplitName, boolean isSplitRequired, int versionCode, @@ -179,10 +154,7 @@ public class ApkLite { String requiredSystemPropertyName, String requiredSystemPropertyValue, int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy, Set<String> requiredSplitTypes, Set<String> splitTypes, - boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean clearUserDataAllowed, - boolean backupAllowed, boolean defaultToDeviceProtectedStorage, - boolean requestLegacyExternalStorage, boolean userDataFragile, - boolean clearUserDataOnFailedRestoreAllowed) { + boolean hasDeviceAdminReceiver, boolean isSdkLibrary) { mPath = path; mPackageName = packageName; mSplitName = splitName; @@ -216,11 +188,7 @@ public class ApkLite { mRollbackDataPolicy = rollbackDataPolicy; mHasDeviceAdminReceiver = hasDeviceAdminReceiver; mIsSdkLibrary = isSdkLibrary; - mBackupAllowed = backupAllowed; - mDefaultToDeviceProtectedStorage = defaultToDeviceProtectedStorage; - mRequestLegacyExternalStorage = requestLegacyExternalStorage; - mUserDataFragile = userDataFragile; - mClearUserDataOnFailedRestoreAllowed = clearUserDataOnFailedRestoreAllowed; + mArchivedPackage = null; } public ApkLite(String path, ArchivedPackageParcel archivedPackage) { @@ -257,16 +225,7 @@ public class ApkLite { mRollbackDataPolicy = 0; mHasDeviceAdminReceiver = false; mIsSdkLibrary = false; - // @see ParsingPackageUtils#parseBaseAppBasicFlags - mBackupAllowed = XmlUtils.convertValueToBoolean(archivedPackage.backupAllowed, true); - mDefaultToDeviceProtectedStorage = XmlUtils.convertValueToBoolean( - archivedPackage.defaultToDeviceProtectedStorage, false); - mRequestLegacyExternalStorage = XmlUtils.convertValueToBoolean( - archivedPackage.requestLegacyExternalStorage, - mTargetSdkVersion < Build.VERSION_CODES.Q); - mUserDataFragile = XmlUtils.convertValueToBoolean(archivedPackage.userDataFragile, false); - mClearUserDataOnFailedRestoreAllowed = XmlUtils.convertValueToBoolean( - archivedPackage.clearUserDataOnFailedRestoreAllowed, true); + mArchivedPackage = archivedPackage; } /** @@ -576,53 +535,18 @@ public class ApkLite { } /** - * Set to <code>false</code> if the application does not wish to permit any OS-driven - * backups of its data; <code>true</code> otherwise. - */ - @DataClass.Generated.Member - public boolean isBackupAllowed() { - return mBackupAllowed; - } - - /** - * When set, the default data storage directory for this app is pointed at - * the device-protected location. - */ - @DataClass.Generated.Member - public boolean isDefaultToDeviceProtectedStorage() { - return mDefaultToDeviceProtectedStorage; - } - - /** - * If {@code true} this app requests full external storage access. - */ - @DataClass.Generated.Member - public boolean isRequestLegacyExternalStorage() { - return mRequestLegacyExternalStorage; - } - - /** - * Indicates whether this application has declared its user data as fragile, causing the - * system to prompt the user on whether to keep the user data on uninstall. - */ - @DataClass.Generated.Member - public boolean isUserDataFragile() { - return mUserDataFragile; - } - - /** - * Indicates whether this application's data will be cleared on a failed restore. + * Archival install info. */ @DataClass.Generated.Member - public boolean isClearUserDataOnFailedRestoreAllowed() { - return mClearUserDataOnFailedRestoreAllowed; + public @Nullable ArchivedPackageParcel getArchivedPackage() { + return mArchivedPackage; } @DataClass.Generated( - time = 1693513509013L, + time = 1694792109463L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mBackupAllowed\nprivate final boolean mDefaultToDeviceProtectedStorage\nprivate final boolean mRequestLegacyExternalStorage\nprivate final boolean mUserDataFragile\nprivate final boolean mClearUserDataOnFailedRestoreAllowed\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 066ff6896ac8..5f86742fc562 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -40,7 +40,6 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; @@ -447,13 +446,6 @@ public class ApkLiteParseUtils { int overlayPriority = 0; int rollbackDataPolicy = 0; - boolean clearUserDataAllowed = true; - boolean backupAllowed = true; - boolean defaultToDeviceProtectedStorage = false; - String requestLegacyExternalStorage = null; - boolean userDataFragile = false; - boolean clearUserDataOnFailedRestoreAllowed = true; - String requiredSystemPropertyName = null; String requiredSystemPropertyValue = null; @@ -493,22 +485,6 @@ public class ApkLiteParseUtils { useEmbeddedDex = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "useEmbeddedDex", false); - clearUserDataAllowed = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, - "allowClearUserDataOnFailedRestore", true); - backupAllowed = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, - "allowBackup", true); - defaultToDeviceProtectedStorage = parser.getAttributeBooleanValue( - ANDROID_RES_NAMESPACE, - "defaultToDeviceProtectedStorage", false); - userDataFragile = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, - "hasFragileUserData", false); - clearUserDataOnFailedRestoreAllowed = parser.getAttributeBooleanValue( - ANDROID_RES_NAMESPACE, - "allowClearUserDataOnFailedRestore", true); - - requestLegacyExternalStorage = parser.getAttributeValue(ANDROID_RES_NAMESPACE, - "requestLegacyExternalStorage"); - rollbackDataPolicy = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "rollbackDataPolicy", 0); String permission = parser.getAttributeValue(ANDROID_RES_NAMESPACE, @@ -629,9 +605,6 @@ public class ApkLiteParseUtils { return input.skip(message); } - boolean isRequestLegacyExternalStorage = XmlUtils.convertValueToBoolean( - requestLegacyExternalStorage, targetSdkVersion < Build.VERSION_CODES.Q); - return input.success( new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, versionCode, @@ -641,9 +614,7 @@ public class ApkLiteParseUtils { overlayIsStatic, overlayPriority, requiredSystemPropertyName, requiredSystemPropertyValue, minSdkVersion, targetSdkVersion, rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second, - hasDeviceAdminReceiver, isSdkLibrary, clearUserDataAllowed, backupAllowed, - defaultToDeviceProtectedStorage, isRequestLegacyExternalStorage, - userDataFragile, clearUserDataOnFailedRestoreAllowed)); + hasDeviceAdminReceiver, isSdkLibrary)); } private static boolean isDeviceAdminReceiver( diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java index ccef9def609e..116dd1fc9a42 100644 --- a/core/java/android/content/pm/parsing/PackageLite.java +++ b/core/java/android/content/pm/parsing/PackageLite.java @@ -18,6 +18,7 @@ package android.content.pm.parsing; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.pm.ArchivedPackageParcel; import android.content.pm.PackageInfo; import android.content.pm.SigningDetails; import android.content.pm.VerifierInfo; @@ -112,29 +113,11 @@ public class PackageLite { * Indicates if this package is a sdk. */ private final boolean mIsSdkLibrary; + /** - * Set to <code>false</code> if the application does not wish to permit any OS-driven - * backups of its data; <code>true</code> otherwise. - */ - private final boolean mBackupAllowed; - /** - * When set, the default data storage directory for this app is pointed at - * the device-protected location. - */ - private final boolean mDefaultToDeviceProtectedStorage; - /** - * If {@code true} this app requests full external storage access. - */ - private final boolean mRequestLegacyExternalStorage; - /** - * Indicates whether this application has declared its user data as fragile, causing the - * system to prompt the user on whether to keep the user data on uninstall. - */ - private final boolean mUserDataFragile; - /** - * Indicates whether this application's data will be cleared on a failed restore. + * Archival install info. */ - private final boolean mClearUserDataOnFailedRestoreAllowed; + private final @Nullable ArchivedPackageParcel mArchivedPackage; public PackageLite(String path, String baseApkPath, ApkLite baseApk, String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames, @@ -171,11 +154,7 @@ public class PackageLite { mSplitApkPaths = splitApkPaths; mSplitRevisionCodes = splitRevisionCodes; mTargetSdk = targetSdk; - mBackupAllowed = baseApk.isBackupAllowed(); - mDefaultToDeviceProtectedStorage = baseApk.isDefaultToDeviceProtectedStorage(); - mRequestLegacyExternalStorage = baseApk.isRequestLegacyExternalStorage(); - mUserDataFragile = baseApk.isUserDataFragile(); - mClearUserDataOnFailedRestoreAllowed = baseApk.isClearUserDataOnFailedRestoreAllowed(); + mArchivedPackage = baseApk.getArchivedPackage(); } /** @@ -455,53 +434,18 @@ public class PackageLite { } /** - * Set to <code>false</code> if the application does not wish to permit any OS-driven - * backups of its data; <code>true</code> otherwise. - */ - @DataClass.Generated.Member - public boolean isBackupAllowed() { - return mBackupAllowed; - } - - /** - * When set, the default data storage directory for this app is pointed at - * the device-protected location. - */ - @DataClass.Generated.Member - public boolean isDefaultToDeviceProtectedStorage() { - return mDefaultToDeviceProtectedStorage; - } - - /** - * If {@code true} this app requests full external storage access. - */ - @DataClass.Generated.Member - public boolean isRequestLegacyExternalStorage() { - return mRequestLegacyExternalStorage; - } - - /** - * Indicates whether this application has declared its user data as fragile, causing the - * system to prompt the user on whether to keep the user data on uninstall. - */ - @DataClass.Generated.Member - public boolean isUserDataFragile() { - return mUserDataFragile; - } - - /** - * Indicates whether this application's data will be cleared on a failed restore. + * Archival install info. */ @DataClass.Generated.Member - public boolean isClearUserDataOnFailedRestoreAllowed() { - return mClearUserDataOnFailedRestoreAllowed; + public @Nullable ArchivedPackageParcel getArchivedPackage() { + return mArchivedPackage; } @DataClass.Generated( - time = 1693513525097L, + time = 1694792176268L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final boolean mBackupAllowed\nprivate final boolean mDefaultToDeviceProtectedStorage\nprivate final boolean mRequestLegacyExternalStorage\nprivate final boolean mUserDataFragile\nprivate final boolean mClearUserDataOnFailedRestoreAllowed\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/res/OWNERS b/core/java/android/content/res/OWNERS index a7bce122eb35..141d58d51353 100644 --- a/core/java/android/content/res/OWNERS +++ b/core/java/android/content/res/OWNERS @@ -1,8 +1,7 @@ # Bug component: 568761 -toddke@android.com -toddke@google.com patb@google.com zyy@google.com +branliu@google.com per-file FontScaleConverter*=fuego@google.com
\ No newline at end of file diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java index eedb25b1aa8f..408869ec76bc 100644 --- a/core/java/android/credentials/CredentialManager.java +++ b/core/java/android/credentials/CredentialManager.java @@ -19,6 +19,7 @@ package android.credentials; import static java.util.Objects.requireNonNull; import android.annotation.CallbackExecutor; +import android.annotation.Hide; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -117,6 +118,32 @@ public final class CredentialManager { } /** + * Returns a list of candidate credentials returned from credential manager providers + * + * @param request the request specifying type(s) of credentials to get from the + * credential providers + * @param cancellationSignal an optional signal that allows for cancelling this call + * @param executor the callback will take place on this {@link Executor} + * @param callback the callback invoked when the request succeeds or fails + * + * @hide + */ + @Hide + public void getCandidateCredentials( + @NonNull GetCredentialRequest request, + @Nullable CancellationSignal cancellationSignal, + @CallbackExecutor @NonNull Executor executor, + @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) { + requireNonNull(request, "request must not be null"); + requireNonNull(executor, "executor must not be null"); + requireNonNull(callback, "callback must not be null"); + + if (cancellationSignal != null && cancellationSignal.isCanceled()) { + Log.w(TAG, "getCredential already canceled"); + } + } + + /** * Launches the necessary flows to retrieve an app credential from the user. * * <p>The execution can potentially launch UI flows to collect user consent to using a @@ -641,6 +668,44 @@ public final class CredentialManager { } } + private static class GetCandidateCredentialsTransport + extends IGetCandidateCredentialsCallback.Stub { + + private final Executor mExecutor; + private final OutcomeReceiver<GetCandidateCredentialsResponse, + GetCandidateCredentialsException> mCallback; + + private GetCandidateCredentialsTransport( + Executor executor, + OutcomeReceiver<GetCandidateCredentialsResponse, + GetCandidateCredentialsException> callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onResponse(GetCandidateCredentialsResponse response) { + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onResult(response)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void onError(String errorType, String message) { + final long identity = Binder.clearCallingIdentity(); + try { + mExecutor.execute( + () -> mCallback.onError(new GetCandidateCredentialsException( + errorType, message))); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + private static class GetCredentialTransport extends IGetCredentialCallback.Stub { // TODO: listen for cancellation to release callback. diff --git a/core/java/android/credentials/GetCandidateCredentialsException.java b/core/java/android/credentials/GetCandidateCredentialsException.java new file mode 100644 index 000000000000..40650d02a93e --- /dev/null +++ b/core/java/android/credentials/GetCandidateCredentialsException.java @@ -0,0 +1,95 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.credentials; + +import android.annotation.Hide; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.util.Preconditions; + +/** + * Represents an error encountered during the + * {@link CredentialManager#getCandidateCredentials} operation. + * + * @hide + */ +@Hide +public class GetCandidateCredentialsException extends Exception { + /** + * The error type value for when the given operation failed due to an unknown reason. + */ + @NonNull + public static final String TYPE_UNKNOWN = + "android.credentials.GetCandidateCredentialsException.TYPE_UNKNOWN"; + + /** + * The error type value for when no credential is found available for the given {@link + * CredentialManager#getCandidateCredentials} request. + */ + @NonNull + public static final String TYPE_NO_CREDENTIAL = + "android.credentials.GetCandidateCredentialsException.TYPE_NO_CREDENTIAL"; + + @NonNull + private final String mType; + + /** Returns the specific exception type. */ + @NonNull + public String getType() { + return mType; + } + + /** + * Constructs a {@link GetCandidateCredentialsException}. + * + * @throws IllegalArgumentException If type is empty. + */ + public GetCandidateCredentialsException(@NonNull String type, @Nullable String message) { + this(type, message, null); + } + + /** + * Constructs a {@link GetCandidateCredentialsException}. + * + * @throws IllegalArgumentException If type is empty. + */ + public GetCandidateCredentialsException( + @NonNull String type, @Nullable String message, @Nullable Throwable cause) { + super(message, cause); + this.mType = Preconditions.checkStringNotEmpty(type, + "type must not be empty"); + } + + /** + * Constructs a {@link GetCandidateCredentialsException}. + * + * @throws IllegalArgumentException If type is empty. + */ + public GetCandidateCredentialsException(@NonNull String type, @Nullable Throwable cause) { + this(type, null, cause); + } + + /** + * Constructs a {@link GetCandidateCredentialsException}. + * + * @throws IllegalArgumentException If type is empty. + */ + public GetCandidateCredentialsException(@NonNull String type) { + this(type, null, null); + } +} diff --git a/core/java/android/credentials/GetCandidateCredentialsRequest.aidl b/core/java/android/credentials/GetCandidateCredentialsRequest.aidl new file mode 100644 index 000000000000..d3610894b418 --- /dev/null +++ b/core/java/android/credentials/GetCandidateCredentialsRequest.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.credentials; + +parcelable GetCandidateCredentialsRequest;
\ No newline at end of file diff --git a/core/java/android/credentials/GetCandidateCredentialsRequest.java b/core/java/android/credentials/GetCandidateCredentialsRequest.java new file mode 100644 index 000000000000..7f0dcaf060b8 --- /dev/null +++ b/core/java/android/credentials/GetCandidateCredentialsRequest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.credentials; + +import static java.util.Objects.requireNonNull; + +import android.annotation.Hide; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.AnnotationValidations; +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.List; + +/** + * A request to retrieve a list of candidate credentials against the list of credential + * options + * + * @hide + */ +@Hide +public final class GetCandidateCredentialsRequest implements Parcelable { + + /** + * The list of credential requests. + */ + @NonNull + private final List<CredentialOption> mCredentialOptions; + + /** + * The top request level data. + */ + @NonNull + private final Bundle mData; + + /** + * The origin of the calling app. Callers of this special API (e.g. browsers) + * can set this origin for an app different from their own, to be able to get credentials + * on behalf of that app. + */ + @Nullable + private String mOrigin; + + /** + * Returns the list of credential options to be requested. + */ + @NonNull + public List<CredentialOption> getCredentialOptions() { + return mCredentialOptions; + } + + /** + * Returns the top request level data. + */ + @NonNull + public Bundle getData() { + return mData; + } + + /** + * Returns the origin of the calling app if set otherwise returns null. + */ + @Nullable + public String getOrigin() { + return mOrigin; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedList(mCredentialOptions, flags); + dest.writeBundle(mData); + dest.writeString8(mOrigin); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "GetCandidateCredentialsRequest {credentialOption=" + mCredentialOptions + + ", data=" + mData + + ", origin=" + mOrigin + + "}"; + } + + private GetCandidateCredentialsRequest(@NonNull List<CredentialOption> credentialOptions, + @NonNull Bundle data, String origin) { + Preconditions.checkCollectionNotEmpty( + credentialOptions, + /*valueName=*/ "credentialOptions"); + Preconditions.checkCollectionElementsNotNull( + credentialOptions, + /*valueName=*/ "credentialOptions"); + mCredentialOptions = credentialOptions; + mData = requireNonNull(data, + "data must not be null"); + mOrigin = origin; + } + + private GetCandidateCredentialsRequest(@NonNull Parcel in) { + List<CredentialOption> credentialOptions = new ArrayList<CredentialOption>(); + in.readTypedList(credentialOptions, CredentialOption.CREATOR); + mCredentialOptions = credentialOptions; + AnnotationValidations.validate(NonNull.class, null, mCredentialOptions); + + Bundle data = in.readBundle(); + mData = data; + AnnotationValidations.validate(NonNull.class, null, mData); + + mOrigin = in.readString8(); + } + + @NonNull + public static final Creator<GetCandidateCredentialsRequest> CREATOR = + new Creator<>() { + @Override + public GetCandidateCredentialsRequest[] newArray(int size) { + return new GetCandidateCredentialsRequest[size]; + } + + @Override + public GetCandidateCredentialsRequest createFromParcel(@NonNull Parcel in) { + return new GetCandidateCredentialsRequest(in); + } + }; +} diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.aidl b/core/java/android/credentials/GetCandidateCredentialsResponse.aidl new file mode 100644 index 000000000000..ffcd3e7078e8 --- /dev/null +++ b/core/java/android/credentials/GetCandidateCredentialsResponse.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.credentials; + +parcelable GetCandidateCredentialsResponse;
\ No newline at end of file diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java new file mode 100644 index 000000000000..1d649eb92fde --- /dev/null +++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java @@ -0,0 +1,55 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.credentials; + +import android.annotation.Hide; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A list of candidate credentials. + * + * @hide + */ +@Hide +public final class GetCandidateCredentialsResponse implements Parcelable { + // TODO(b/299321990): Add members + protected GetCandidateCredentialsResponse(Parcel in) { + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<GetCandidateCredentialsResponse> CREATOR = + new Creator<GetCandidateCredentialsResponse>() { + @Override + public GetCandidateCredentialsResponse createFromParcel(Parcel in) { + return new GetCandidateCredentialsResponse(in); + } + + @Override + public GetCandidateCredentialsResponse[] newArray(int size) { + return new GetCandidateCredentialsResponse[size]; + } + }; +} diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl index dec729f4a19f..42323d66e533 100644 --- a/core/java/android/credentials/ICredentialManager.aidl +++ b/core/java/android/credentials/ICredentialManager.aidl @@ -21,12 +21,14 @@ import java.util.List; import android.credentials.CredentialProviderInfo; import android.credentials.ClearCredentialStateRequest; import android.credentials.CreateCredentialRequest; +import android.credentials.GetCandidateCredentialsRequest; import android.credentials.GetCredentialRequest; import android.credentials.RegisterCredentialDescriptionRequest; import android.credentials.UnregisterCredentialDescriptionRequest; import android.credentials.IClearCredentialStateCallback; import android.credentials.ICreateCredentialCallback; import android.credentials.IGetCredentialCallback; +import android.credentials.IGetCandidateCredentialsCallback; import android.credentials.IPrepareGetCredentialCallback; import android.credentials.ISetEnabledProvidersCallback; import android.content.ComponentName; @@ -45,6 +47,8 @@ interface ICredentialManager { @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage); + @nullable ICancellationSignal getCandidateCredentials(in GetCandidateCredentialsRequest request, in IGetCandidateCredentialsCallback callback, String callingPackage); + @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage); void setEnabledProviders(in List<String> primaryProviders, in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback); diff --git a/core/java/android/credentials/IGetCandidateCredentialsCallback.aidl b/core/java/android/credentials/IGetCandidateCredentialsCallback.aidl new file mode 100644 index 000000000000..729176a9919d --- /dev/null +++ b/core/java/android/credentials/IGetCandidateCredentialsCallback.aidl @@ -0,0 +1,30 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.credentials; + +import android.app.PendingIntent; +import android.credentials.GetCandidateCredentialsResponse; + +/** + * Listener for a getCandidateCredentials request. + * + * @hide + */ +interface IGetCandidateCredentialsCallback { + oneway void onResponse(in GetCandidateCredentialsResponse response); + oneway void onError(String errorType, String message); +}
\ No newline at end of file diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl index addd622eef35..17cd18cc4182 100644 --- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl +++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl @@ -48,7 +48,8 @@ interface IBiometricAuthenticator { // startPreparedClient(). void prepareForAuthentication(boolean requireConfirmation, IBinder token, long operationId, int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName, - long requestId, int cookie, boolean allowBackgroundAuthentication); + long requestId, int cookie, boolean allowBackgroundAuthentication, + boolean isForLegacyFingerprintManager); // Starts authentication with the previously prepared client. void startPreparedClient(int cookie); diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 4700720736b5..b1aa7deeb4ef 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -32,7 +32,6 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.RefreshRateRange; import android.view.SurfaceControl.Transaction; import android.window.DisplayWindowPolicyController; -import android.window.ScreenCapture; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -112,25 +111,6 @@ public abstract class DisplayManagerInternal { public abstract void unregisterDisplayGroupListener(DisplayGroupListener listener); /** - * Screenshot for internal system-only use such as rotation, etc. This method includes - * secure layers and the result should never be exposed to non-system applications. - * This method does not apply any rotation and provides the output in natural orientation. - * - * @param displayId The display id to take the screenshot of. - * @return The buffer or null if we have failed. - */ - public abstract ScreenCapture.ScreenshotHardwareBuffer systemScreenshot(int displayId); - - /** - * General screenshot functionality that excludes secure layers and applies appropriate - * rotation that the device is currently in. - * - * @param displayId The display id to take the screenshot of. - * @return The buffer or null if we have failed. - */ - public abstract ScreenCapture.ScreenshotHardwareBuffer userScreenshot(int displayId); - - /** * Returns information about the specified logical display. * * @param displayId The logical display id. diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index e2840ec20ff9..0100660669e9 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -74,7 +74,8 @@ interface IFingerprintService { @EnforcePermission("MANAGE_BIOMETRIC") void prepareForAuthentication(IBinder token, long operationId, IBiometricSensorReceiver sensorReceiver, in FingerprintAuthenticateOptions options, long requestId, - int cookie, boolean allowBackgroundAuthentication); + int cookie, boolean allowBackgroundAuthentication, + boolean isForLegacyFingerprintManager); // Starts authentication with the previously prepared client. @EnforcePermission("MANAGE_BIOMETRIC") diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index c3fae55fd00c..88d7231bc7be 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -41,6 +41,7 @@ import android.view.InputDevice; import android.view.InputEvent; import android.view.InputMonitor; import android.view.PointerIcon; +import android.view.KeyCharacterMap; import android.view.VerifiedInputEvent; /** @hide */ @@ -63,6 +64,8 @@ interface IInputManager { // active keyboard layout. int getKeyCodeForKeyLocation(int deviceId, in int locationKeyCode); + KeyCharacterMap getKeyCharacterMap(String layoutDescriptor); + // Temporarily changes the pointer speed. void tryPointerSpeed(int speed); diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index a0cceae98ba9..ff1a6acd8e4e 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -16,6 +16,8 @@ package android.hardware.input; +import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag; + import android.Manifest; import android.annotation.FloatRange; import android.annotation.IntDef; @@ -31,6 +33,7 @@ import android.app.ActivityThread; import android.compat.annotation.ChangeId; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.graphics.drawable.Drawable; import android.hardware.BatteryState; import android.os.Build; import android.os.Handler; @@ -931,6 +934,31 @@ public final class InputManager { } /** + * Provides a Keyboard layout preview of a particular dimension. + * + * @param keyboardLayout Layout whose preview is requested. If null, will return preview of + * the default Keyboard layout defined by {@code Generic.kl}. + * @param width Expected width of the drawable + * @param height Expected height of the drawable + * + * NOTE: Width and height will auto-adjust to the width and height of the ImageView that + * shows the drawable but this allows the caller to provide an intrinsic width and height of + * the drawable allowing the ImageView to properly wrap the drawable content. + * + * @hide + */ + @Nullable + public Drawable getKeyboardLayoutPreview(@Nullable KeyboardLayout keyboardLayout, int width, + int height) { + if (!keyboardLayoutPreviewFlag()) { + return null; + } + PhysicalKeyLayout keyLayout = new PhysicalKeyLayout( + mGlobal.getKeyCharacterMap(keyboardLayout), keyboardLayout); + return new KeyboardLayoutPreviewDrawable(mContext, keyLayout, width, height); + } + + /** * Injects an input event into the event system, targeting windows owned by the provided uid. * * If a valid targetUid is provided, the system will only consider injecting the input event diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java index e886f685263c..8c598aeae67c 100644 --- a/core/java/android/hardware/input/InputManagerGlobal.java +++ b/core/java/android/hardware/input/InputManagerGlobal.java @@ -51,6 +51,7 @@ import android.view.Display; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputMonitor; +import android.view.KeyCharacterMap; import android.view.PointerIcon; import com.android.internal.annotations.GuardedBy; @@ -1206,6 +1207,21 @@ public final class InputManagerGlobal { } /** + * Returns KeyCharacterMap for the provided Keyboard layout. If provided layout is null it will + * return KeyCharacter map for the default layout {@code Generic.kl}. + */ + public KeyCharacterMap getKeyCharacterMap(@Nullable KeyboardLayout keyboardLayout) { + if (keyboardLayout == null) { + return KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); + } + try { + return mIm.getKeyCharacterMap(keyboardLayout.getDescriptor()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * @see InputManager#injectInputEvent(InputEvent, int, int) */ diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java index 4403251e0488..bbfed24f9dc1 100644 --- a/core/java/android/hardware/input/KeyboardLayout.java +++ b/core/java/android/hardware/input/KeyboardLayout.java @@ -22,6 +22,7 @@ import android.os.Parcel; import android.os.Parcelable; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.Objects; @@ -230,6 +231,33 @@ public final class KeyboardLayout implements Parcelable, Comparable<KeyboardLayo return mProductId; } + /** + * Returns if the Keyboard layout follows the ANSI Physical key layout. + */ + public boolean isAnsiLayout() { + for (int i = 0; i < mLocales.size(); i++) { + Locale locale = mLocales.get(i); + if (locale != null && locale.getCountry().equalsIgnoreCase("us") + && mLayoutType != LayoutType.EXTENDED) { + return true; + } + } + return false; + } + + /** + * Returns if the Keyboard layout follows the JIS Physical key layout. + */ + public boolean isJisLayout() { + for (int i = 0; i < mLocales.size(); i++) { + Locale locale = mLocales.get(i); + if (locale != null && locale.getCountry().equalsIgnoreCase("jp")) { + return true; + } + } + return false; + } + @Override public int describeContents() { return 0; diff --git a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java new file mode 100644 index 000000000000..d943c37e9e5b --- /dev/null +++ b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java @@ -0,0 +1,504 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import android.annotation.ColorInt; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.util.Slog; +import android.util.TypedValue; +import android.view.KeyEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * A custom drawable class that draws preview of a Physical keyboard layout. + */ +final class KeyboardLayoutPreviewDrawable extends Drawable { + + private static final String TAG = "KeyboardLayoutPreview"; + private static final int GRAVITY_LEFT = 0x1; + private static final int GRAVITY_RIGHT = 0x2; + private static final int GRAVITY_TOP = 0x4; + private static final int GRAVITY_BOTTOM = 0x8; + private static final int GRAVITY_CENTER = + GRAVITY_LEFT | GRAVITY_RIGHT | GRAVITY_TOP | GRAVITY_BOTTOM; + private static final int GRAVITY_CENTER_HORIZONTAL = GRAVITY_LEFT | GRAVITY_RIGHT; + private static final int KEY_PADDING_IN_DP = 3; + private static final int KEYBOARD_PADDING_IN_DP = 10; + private static final int KEY_RADIUS_IN_DP = 5; + private static final int KEYBOARD_RADIUS_IN_DP = 10; + private static final int GLYPH_TEXT_SIZE_IN_SP = 10; + + private final List<KeyDrawable> mKeyDrawables = new ArrayList<>(); + + private final int mWidth; + private final int mHeight; + private final RectF mKeyboardBackground = new RectF(); + private final ResourceProvider mResourceProvider; + private final PhysicalKeyLayout mKeyLayout; + + public KeyboardLayoutPreviewDrawable(Context context, PhysicalKeyLayout keyLayout, int width, + int height) { + mWidth = width; + mHeight = height; + mResourceProvider = new ResourceProvider(context); + mKeyLayout = keyLayout; + } + + @Override + public int getIntrinsicWidth() { + return mWidth; + } + + @Override + public int getIntrinsicHeight() { + return mHeight; + } + + @Override + protected void onBoundsChange(@NonNull Rect bounds) { + super.onBoundsChange(bounds); + mKeyDrawables.clear(); + final PhysicalKeyLayout.LayoutKey[][] keys = mKeyLayout.getKeys(); + if (keys == null) { + return; + } + final PhysicalKeyLayout.EnterKey enterKey = mKeyLayout.getEnterKey(); + int width = bounds.width(); + int height = bounds.height(); + final int keyboardPadding = mResourceProvider.getKeyboardPadding(); + final int keyPadding = mResourceProvider.getKeyPadding(); + final float keyRadius = mResourceProvider.getKeyRadius(); + mKeyboardBackground.set(0, 0, width, height); + width -= keyboardPadding * 2; + height -= keyboardPadding * 2; + if (width <= 0 || height <= 0) { + Slog.e(TAG, "Invalid width and height to draw layout preview, width = " + width + + ", height = " + height); + return; + } + int rowCount = keys.length; + float keyHeight = (float) (height - rowCount * 2 * keyPadding) / rowCount; + float isoEnterKeyLeft = 0; + float isoEnterKeyTop = 0; + float isoEnterWidthUnit = 0; + for (int i = 0; i < rowCount; i++) { + PhysicalKeyLayout.LayoutKey[] row = keys[i]; + float totalRowWeight = 0; + int keysInRow = row.length; + for (PhysicalKeyLayout.LayoutKey layoutKey : row) { + totalRowWeight += layoutKey.keyWeight(); + } + float keyWidthInPx = (width - keysInRow * 2 * keyPadding) / totalRowWeight; + float rowWeightOnLeft = 0; + float top = keyboardPadding + keyPadding * (2 * i + 1) + i * keyHeight; + for (int j = 0; j < keysInRow; j++) { + float left = + keyboardPadding + keyPadding * (2 * j + 1) + rowWeightOnLeft * keyWidthInPx; + rowWeightOnLeft += row[j].keyWeight(); + RectF keyRect = new RectF(left, top, left + keyWidthInPx * row[j].keyWeight(), + top + keyHeight); + if (enterKey != null && row[j].keyCode() == KeyEvent.KEYCODE_ENTER) { + if (enterKey.row() == i && enterKey.column() == j) { + isoEnterKeyLeft = keyRect.left; + isoEnterKeyTop = keyRect.top; + isoEnterWidthUnit = keyWidthInPx; + } + continue; + } + if (PhysicalKeyLayout.isSpecialKey(row[j])) { + mKeyDrawables.add(new TypingKey(null, keyRect, keyRadius, + mResourceProvider.getSpecialKeyPaint(), + mResourceProvider.getSpecialKeyPaint(), + mResourceProvider.getSpecialKeyPaint())); + } else if (PhysicalKeyLayout.isKeyPositionUnsure(row[j])) { + mKeyDrawables.add(new UnsureTypingKey(row[j].glyph(), keyRect, + keyRadius, mResourceProvider.getTypingKeyPaint(), + mResourceProvider.getPrimaryGlyphPaint(), + mResourceProvider.getSecondaryGlyphPaint())); + } else { + mKeyDrawables.add(new TypingKey(row[j].glyph(), keyRect, keyRadius, + mResourceProvider.getTypingKeyPaint(), + mResourceProvider.getPrimaryGlyphPaint(), + mResourceProvider.getSecondaryGlyphPaint())); + } + } + } + if (enterKey != null) { + IsoEnterKey.Builder isoEnterKeyBuilder = new IsoEnterKey.Builder(keyRadius, + mResourceProvider.getSpecialKeyPaint()); + isoEnterKeyBuilder.setTopWidth(enterKey.topKeyWeight() * isoEnterWidthUnit) + .setStartPoint(isoEnterKeyLeft, isoEnterKeyTop) + .setVerticalEdges(keyHeight, 2 * (keyHeight + keyPadding)) + .setBottomWidth(enterKey.bottomKeyWeight() * isoEnterWidthUnit); + mKeyDrawables.add(isoEnterKeyBuilder.build()); + } + } + + @Override + public void draw(Canvas canvas) { + final float keyboardRadius = mResourceProvider.getBackgroundRadius(); + canvas.drawRoundRect(mKeyboardBackground, keyboardRadius, keyboardRadius, + mResourceProvider.getBackgroundPaint()); + for (KeyDrawable key : mKeyDrawables) { + key.draw(canvas); + } + } + + @Override + public void setAlpha(int alpha) { + // Do nothing + } + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { + // Do nothing + } + + @Override + public int getOpacity() { + return PixelFormat.OPAQUE; + } + + private static class TypingKey implements KeyDrawable { + + private final RectF mKeyRect; + private final float mKeyRadius; + private final Paint mKeyPaint; + private final Paint mBaseTextPaint; + private final Paint mModifierTextPaint; + private final List<GlyphDrawable> mGlyphDrawables = new ArrayList<>(); + + private TypingKey(@Nullable PhysicalKeyLayout.KeyGlyph glyphData, RectF keyRect, + float keyRadius, Paint keyPaint, Paint baseTextPaint, Paint modifierTextPaint) { + mKeyRect = keyRect; + mKeyRadius = keyRadius; + mKeyPaint = keyPaint; + mBaseTextPaint = baseTextPaint; + mModifierTextPaint = modifierTextPaint; + initGlyphs(glyphData); + } + + private void initGlyphs(@Nullable PhysicalKeyLayout.KeyGlyph glyphData) { + createGlyphs(glyphData); + measureGlyphs(); + } + + private void createGlyphs(@Nullable PhysicalKeyLayout.KeyGlyph glyphData) { + if (glyphData == null) { + return; + } + if (!glyphData.hasBaseText()) { + return; + } + if (glyphData.hasValidShiftText() && glyphData.hasValidAltGrText()) { + mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(), + GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint)); + mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(), + GRAVITY_TOP | GRAVITY_LEFT, mModifierTextPaint)); + mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(), + GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint)); + } else if (glyphData.hasValidShiftText()) { + mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(), + GRAVITY_BOTTOM | GRAVITY_CENTER_HORIZONTAL, mBaseTextPaint)); + mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(), + GRAVITY_TOP | GRAVITY_CENTER_HORIZONTAL, mModifierTextPaint)); + } else if (glyphData.hasValidAltGrText()) { + mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(), + GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint)); + mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(), + GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint)); + } else { + mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(), + GRAVITY_CENTER, mBaseTextPaint)); + } + } + + private void measureGlyphs() { + float keyWidth = mKeyRect.width(); + float keyHeight = mKeyRect.height(); + for (GlyphDrawable glyph : mGlyphDrawables) { + float centerX = keyWidth / 2; + float centerY = keyHeight / 2; + if ((glyph.gravity & GRAVITY_LEFT) != 0) { + centerX -= keyWidth / 4; + } + if ((glyph.gravity & GRAVITY_RIGHT) != 0) { + centerX += keyWidth / 4; + } + if ((glyph.gravity & GRAVITY_TOP) != 0) { + centerY -= keyHeight / 4; + } + if ((glyph.gravity & GRAVITY_BOTTOM) != 0) { + centerY += keyHeight / 4; + } + Rect textBounds = new Rect(); + glyph.paint.getTextBounds(glyph.text, 0, glyph.text.length(), textBounds); + float textWidth = textBounds.width(); + float textHeight = textBounds.height(); + glyph.rect.set(centerX - textWidth / 2, centerY - textHeight / 2 - textBounds.top, + centerX + textWidth / 2, centerY + textHeight / 2 - textBounds.top); + } + } + + @Override + public void draw(Canvas canvas) { + canvas.drawRoundRect(mKeyRect, mKeyRadius, mKeyRadius, mKeyPaint); + for (GlyphDrawable glyph : mGlyphDrawables) { + float textWidth = glyph.rect.width(); + float textHeight = glyph.rect.height(); + float keyWidth = mKeyRect.width(); + float keyHeight = mKeyRect.height(); + if (textWidth == 0 || textHeight == 0 || keyWidth == 0 || keyHeight == 0) { + return; + } + canvas.drawText(glyph.text, 0, glyph.text.length(), mKeyRect.left + glyph.rect.left, + mKeyRect.top + glyph.rect.top, glyph.paint); + } + } + } + + private static class UnsureTypingKey extends TypingKey { + + private UnsureTypingKey(@Nullable PhysicalKeyLayout.KeyGlyph glyphData, + RectF keyRect, float keyRadius, Paint keyPaint, Paint baseTextPaint, + Paint modifierTextPaint) { + super(glyphData, keyRect, keyRadius, createGreyedOutPaint(keyPaint), + createGreyedOutPaint(baseTextPaint), createGreyedOutPaint(modifierTextPaint)); + } + } + + private static class IsoEnterKey implements KeyDrawable { + + private final Paint mKeyPaint; + private final Path mPath; + + private IsoEnterKey(Paint keyPaint, @NonNull Path path) { + mKeyPaint = keyPaint; + mPath = path; + } + + @Override + public void draw(Canvas canvas) { + canvas.drawPath(mPath, mKeyPaint); + } + + private static class Builder { + private final float mKeyRadius; + private final Paint mKeyPaint; + private float mLeft; + private float mTop; + private float mTopWidth; + private float mBottomWidth; + private float mLeftHeight; + private float mRightHeight; + + private Builder(float keyRadius, Paint keyPaint) { + mKeyRadius = keyRadius; + mKeyPaint = keyPaint; + } + + private Builder setStartPoint(float left, float top) { + mLeft = left; + mTop = top; + return this; + } + + private Builder setTopWidth(float width) { + mTopWidth = width; + return this; + } + + private Builder setBottomWidth(float width) { + mBottomWidth = width; + return this; + } + + private Builder setVerticalEdges(float leftHeight, float rightHeight) { + mLeftHeight = leftHeight; + mRightHeight = rightHeight; + return this; + } + + private IsoEnterKey build() { + Path enterKey = new Path(); + RectF oval = new RectF(-mKeyRadius, -mKeyRadius, mKeyRadius, mKeyRadius); + // Horizontal top line + enterKey.moveTo(mLeft + mKeyRadius, mTop); + enterKey.lineTo(mLeft + mTopWidth - mKeyRadius, mTop); + // Rounded top right corner + oval.offsetTo(mLeft + mTopWidth - 2 * mKeyRadius, mTop); + enterKey.arcTo(oval, 270, 90); + // Vertical right line + enterKey.lineTo(mLeft + mTopWidth, mTop + mRightHeight - mKeyRadius); + // Rounded bottom right corner + oval.offsetTo(mLeft + mTopWidth - 2 * mKeyRadius, + mTop + mRightHeight - 2 * mKeyRadius); + enterKey.arcTo(oval, 0, 90); + // Horizontal bottom line + enterKey.lineTo(mLeft + mTopWidth - mBottomWidth + mKeyRadius, mTop + mRightHeight); + // Rounded bottom left corner + oval.offsetTo(mLeft + mTopWidth - mBottomWidth, + mTop + mRightHeight - 2 * mKeyRadius); + enterKey.arcTo(oval, 90, 90); + // Vertical left line (bottom half) + enterKey.lineTo(mLeft + mTopWidth - mBottomWidth, mTop + mLeftHeight - mKeyRadius); + // Rounded corner + oval.offsetTo(mLeft + mTopWidth - mBottomWidth - 2 * mKeyRadius, + mTop + mLeftHeight); + enterKey.arcTo(oval, 0, -90); + // Horizontal line in the mid part + enterKey.lineTo(mLeft + mKeyRadius, mTop + mLeftHeight); + // Rounded corner + oval.offsetTo(mLeft, mTop + mLeftHeight - 2 * mKeyRadius); + enterKey.arcTo(oval, 90, 90); + // Vertical left line (top half) + enterKey.lineTo(mLeft, mTop + mKeyRadius); + // Rounded top left corner + oval.offsetTo(mLeft, mTop); + enterKey.arcTo(oval, 180, 90); + enterKey.close(); + return new IsoEnterKey(mKeyPaint, enterKey); + } + } + } + + private record GlyphDrawable(String text, RectF rect, int gravity, Paint paint) {} + + private interface KeyDrawable { + void draw(Canvas canvas); + } + + private static class ResourceProvider { + // Resources + private final Paint mBackgroundPaint; + private final Paint mTypingKeyPaint; + private final Paint mSpecialKeyPaint; + private final Paint mPrimaryGlyphPaint; + private final Paint mSecondaryGlyphPaint; + private final int mKeyPadding; + private final int mKeyboardPadding; + private final float mKeyRadius; + private final float mBackgroundRadius; + + private ResourceProvider(Context context) { + mKeyPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + KEY_PADDING_IN_DP, context.getResources().getDisplayMetrics()); + mKeyboardPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + KEYBOARD_PADDING_IN_DP, context.getResources().getDisplayMetrics()); + mKeyRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + KEY_RADIUS_IN_DP, context.getResources().getDisplayMetrics()); + mBackgroundRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + KEYBOARD_RADIUS_IN_DP, context.getResources().getDisplayMetrics()); + int textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, + GLYPH_TEXT_SIZE_IN_SP, context.getResources().getDisplayMetrics()); + boolean isDark = (context.getResources().getConfiguration().uiMode + & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + int typingKeyColor = context.getColor( + isDark ? android.R.color.system_outline_variant_dark + : android.R.color.system_surface_container_lowest_light); + int specialKeyColor = context.getColor(isDark ? android.R.color.system_neutral1_800 + : android.R.color.system_secondary_container_light); + int primaryGlyphColor = context.getColor(isDark ? android.R.color.system_on_surface_dark + : android.R.color.system_on_surface_light); + int secondaryGlyphColor = context.getColor(isDark ? android.R.color.system_outline_dark + : android.R.color.system_outline_light); + int backgroundColor = context.getColor( + isDark ? android.R.color.system_surface_container_dark + : android.R.color.system_surface_container_light); + mPrimaryGlyphPaint = createTextPaint(primaryGlyphColor, textSize, + Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD)); + mSecondaryGlyphPaint = createTextPaint(secondaryGlyphColor, textSize, + Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL)); + mTypingKeyPaint = createFillPaint(typingKeyColor); + mSpecialKeyPaint = createFillPaint(specialKeyColor); + mBackgroundPaint = createFillPaint(backgroundColor); + } + + private Paint getBackgroundPaint() { + return mBackgroundPaint; + } + + private Paint getTypingKeyPaint() { + return mTypingKeyPaint; + } + + private Paint getSpecialKeyPaint() { + return mSpecialKeyPaint; + } + + private Paint getPrimaryGlyphPaint() { + return mPrimaryGlyphPaint; + } + + private Paint getSecondaryGlyphPaint() { + return mSecondaryGlyphPaint; + } + + private int getKeyPadding() { + return mKeyPadding; + } + + private int getKeyboardPadding() { + return mKeyboardPadding; + } + + private float getKeyRadius() { + return mKeyRadius; + } + + private float getBackgroundRadius() { + return mBackgroundRadius; + } + } + + private static Paint createTextPaint(@ColorInt int textColor, int textSize, Typeface typeface) { + Paint paint = new Paint(); + paint.setColor(textColor); + paint.setStyle(Paint.Style.FILL); + paint.setTextSize(textSize); + paint.setTypeface(typeface); + return paint; + } + + private static Paint createFillPaint(@ColorInt int color) { + Paint paint = new Paint(); + paint.setColor(color); + paint.setStyle(Paint.Style.FILL); + return paint; + } + + private static Paint createGreyedOutPaint(Paint paint) { + Paint result = new Paint(paint); + result.setAlpha(100); + return result; + } +} diff --git a/core/java/android/hardware/input/PhysicalKeyLayout.java b/core/java/android/hardware/input/PhysicalKeyLayout.java new file mode 100644 index 000000000000..241c452a75eb --- /dev/null +++ b/core/java/android/hardware/input/PhysicalKeyLayout.java @@ -0,0 +1,434 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.text.TextUtils; +import android.util.SparseIntArray; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; + +import java.util.Locale; + +/** + * A complimentary class to {@link KeyboardLayoutPreviewDrawable} describing the physical key layout + * of a Physical keyboard and provides information regarding the scan codes produced by the physical + * keys. + */ +final class PhysicalKeyLayout { + + private static final String TAG = "KeyboardLayoutPreview"; + private static final int SCANCODE_1 = 2; + private static final int SCANCODE_2 = 3; + private static final int SCANCODE_3 = 4; + private static final int SCANCODE_4 = 5; + private static final int SCANCODE_5 = 6; + private static final int SCANCODE_6 = 7; + private static final int SCANCODE_7 = 8; + private static final int SCANCODE_8 = 9; + private static final int SCANCODE_9 = 10; + private static final int SCANCODE_0 = 11; + private static final int SCANCODE_MINUS = 12; + private static final int SCANCODE_EQUALS = 13; + private static final int SCANCODE_Q = 16; + private static final int SCANCODE_W = 17; + private static final int SCANCODE_E = 18; + private static final int SCANCODE_R = 19; + private static final int SCANCODE_T = 20; + private static final int SCANCODE_Y = 21; + private static final int SCANCODE_U = 22; + private static final int SCANCODE_I = 23; + private static final int SCANCODE_O = 24; + private static final int SCANCODE_P = 25; + private static final int SCANCODE_LEFT_BRACKET = 26; + private static final int SCANCODE_RIGHT_BRACKET = 27; + private static final int SCANCODE_A = 30; + private static final int SCANCODE_S = 31; + private static final int SCANCODE_D = 32; + private static final int SCANCODE_F = 33; + private static final int SCANCODE_G = 34; + private static final int SCANCODE_H = 35; + private static final int SCANCODE_J = 36; + private static final int SCANCODE_K = 37; + private static final int SCANCODE_L = 38; + private static final int SCANCODE_SEMICOLON = 39; + private static final int SCANCODE_APOSTROPHE = 40; + private static final int SCANCODE_GRAVE = 41; + private static final int SCANCODE_BACKSLASH1 = 43; + private static final int SCANCODE_Z = 44; + private static final int SCANCODE_X = 45; + private static final int SCANCODE_C = 46; + private static final int SCANCODE_V = 47; + private static final int SCANCODE_B = 48; + private static final int SCANCODE_N = 49; + private static final int SCANCODE_M = 50; + private static final int SCANCODE_COMMA = 51; + private static final int SCANCODE_PERIOD = 52; + private static final int SCANCODE_SLASH = 53; + private static final int SCANCODE_BACKSLASH2 = 86; + private static final int SCANCODE_YEN = 124; + + private static final SparseIntArray DEFAULT_KEYCODE_FOR_SCANCODE = new SparseIntArray(); + + static { + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_1, KeyEvent.KEYCODE_1); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_2, KeyEvent.KEYCODE_2); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_3, KeyEvent.KEYCODE_3); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_4, KeyEvent.KEYCODE_4); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_5, KeyEvent.KEYCODE_5); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_6, KeyEvent.KEYCODE_6); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_7, KeyEvent.KEYCODE_7); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_8, KeyEvent.KEYCODE_8); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_9, KeyEvent.KEYCODE_9); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_0, KeyEvent.KEYCODE_0); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_MINUS, KeyEvent.KEYCODE_MINUS); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_EQUALS, KeyEvent.KEYCODE_EQUALS); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_Q, KeyEvent.KEYCODE_Q); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_W, KeyEvent.KEYCODE_W); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_E, KeyEvent.KEYCODE_E); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_R, KeyEvent.KEYCODE_R); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_T, KeyEvent.KEYCODE_T); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_Y, KeyEvent.KEYCODE_Y); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_U, KeyEvent.KEYCODE_U); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_I, KeyEvent.KEYCODE_I); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_O, KeyEvent.KEYCODE_O); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_P, KeyEvent.KEYCODE_P); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_LEFT_BRACKET, KeyEvent.KEYCODE_LEFT_BRACKET); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_RIGHT_BRACKET, KeyEvent.KEYCODE_RIGHT_BRACKET); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_A, KeyEvent.KEYCODE_A); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_S, KeyEvent.KEYCODE_S); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_D, KeyEvent.KEYCODE_D); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_F, KeyEvent.KEYCODE_F); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_G, KeyEvent.KEYCODE_G); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_H, KeyEvent.KEYCODE_H); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_J, KeyEvent.KEYCODE_J); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_K, KeyEvent.KEYCODE_K); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_L, KeyEvent.KEYCODE_L); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_SEMICOLON, KeyEvent.KEYCODE_SEMICOLON); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_APOSTROPHE, KeyEvent.KEYCODE_APOSTROPHE); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_GRAVE, KeyEvent.KEYCODE_GRAVE); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_BACKSLASH1, KeyEvent.KEYCODE_BACKSLASH); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_Z, KeyEvent.KEYCODE_Z); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_X, KeyEvent.KEYCODE_X); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_C, KeyEvent.KEYCODE_C); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_V, KeyEvent.KEYCODE_V); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_B, KeyEvent.KEYCODE_B); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_N, KeyEvent.KEYCODE_N); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_M, KeyEvent.KEYCODE_M); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_COMMA, KeyEvent.KEYCODE_COMMA); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_PERIOD, KeyEvent.KEYCODE_PERIOD); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_SLASH, KeyEvent.KEYCODE_SLASH); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_BACKSLASH2, KeyEvent.KEYCODE_BACKSLASH); + DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_YEN, KeyEvent.KEYCODE_YEN); + } + + private LayoutKey[][] mKeys = null; + private EnterKey mEnterKey = null; + + public PhysicalKeyLayout(@NonNull KeyCharacterMap kcm, @Nullable KeyboardLayout layout) { + initLayoutKeys(kcm, layout); + } + + private void initLayoutKeys(KeyCharacterMap kcm, KeyboardLayout layout) { + if (layout == null) { + createIsoLayout(kcm); + return; + } + if (layout.isAnsiLayout()) { + createAnsiLayout(kcm); + } else if (layout.isJisLayout()) { + createJisLayout(kcm); + } else { + createIsoLayout(kcm); + } + } + + public LayoutKey[][] getKeys() { + return mKeys; + } + + /** + * @return Special enter key (if required) that can span multiple rows like ISO enter key. + */ + @Nullable + public EnterKey getEnterKey() { + return mEnterKey; + } + + private void createAnsiLayout(KeyCharacterMap kcm) { + mKeys = new LayoutKey[][]{ + { + getKey(kcm, SCANCODE_GRAVE), getKey(kcm, SCANCODE_1), + getKey(kcm, SCANCODE_2), getKey(kcm, SCANCODE_3), getKey(kcm, SCANCODE_4), + getKey(kcm, SCANCODE_5), getKey(kcm, SCANCODE_6), getKey(kcm, SCANCODE_7), + getKey(kcm, SCANCODE_8), getKey(kcm, SCANCODE_9), getKey(kcm, SCANCODE_0), + getKey(kcm, SCANCODE_MINUS), getKey(kcm, SCANCODE_EQUALS), + getKey(KeyEvent.KEYCODE_DEL, 1.5F) + }, + { + getKey(KeyEvent.KEYCODE_TAB, 1.5F), getKey(kcm, SCANCODE_Q), + getKey(kcm, SCANCODE_W), getKey(kcm, SCANCODE_E), getKey(kcm, SCANCODE_R), + getKey(kcm, SCANCODE_T), getKey(kcm, SCANCODE_Y), getKey(kcm, SCANCODE_U), + getKey(kcm, SCANCODE_I), getKey(kcm, SCANCODE_O), getKey(kcm, SCANCODE_P), + getKey(kcm, SCANCODE_LEFT_BRACKET), getKey(kcm, SCANCODE_RIGHT_BRACKET), + getKey(kcm, SCANCODE_BACKSLASH1) + }, + { + getKey(KeyEvent.KEYCODE_CAPS_LOCK, 1.75F), + getKey(kcm, SCANCODE_A), getKey(kcm, SCANCODE_S), getKey(kcm, SCANCODE_D), + getKey(kcm, SCANCODE_F), getKey(kcm, SCANCODE_G), getKey(kcm, SCANCODE_H), + getKey(kcm, SCANCODE_J), getKey(kcm, SCANCODE_K), getKey(kcm, SCANCODE_L), + getKey(kcm, SCANCODE_SEMICOLON), getKey(kcm, SCANCODE_APOSTROPHE), + getKey(KeyEvent.KEYCODE_ENTER, 1.75F) + }, + { + getKey(KeyEvent.KEYCODE_SHIFT_LEFT, 2.5F), + getKey(kcm, SCANCODE_Z), getKey(kcm, SCANCODE_X), getKey(kcm, SCANCODE_C), + getKey(kcm, SCANCODE_V), getKey(kcm, SCANCODE_B), getKey(kcm, SCANCODE_N), + getKey(kcm, SCANCODE_M), getKey(kcm, SCANCODE_COMMA), + getKey(kcm, SCANCODE_PERIOD), getKey(kcm, SCANCODE_SLASH), + getKey(KeyEvent.KEYCODE_SHIFT_RIGHT, 2.5F), + }, + { + getKey(KeyEvent.KEYCODE_CTRL_LEFT, 1.0F), + getKey(KeyEvent.KEYCODE_FUNCTION, 1.0F), + getKey(KeyEvent.KEYCODE_META_LEFT, 1.0F), + getKey(KeyEvent.KEYCODE_ALT_LEFT, 1.0F), + getKey(KeyEvent.KEYCODE_SPACE, 6.5F), + getKey(KeyEvent.KEYCODE_ALT_RIGHT, 1.0F), + getKey(KeyEvent.KEYCODE_META_RIGHT, 1.0F), + getKey(KeyEvent.KEYCODE_MENU, 1.0F), + getKey(KeyEvent.KEYCODE_CTRL_RIGHT, 1.0F), + } + }; + } + + private void createIsoLayout(KeyCharacterMap kcm) { + mKeys = new LayoutKey[][]{ + { + getKey(kcm, SCANCODE_GRAVE), getKey(kcm, SCANCODE_1), + getKey(kcm, SCANCODE_2), getKey(kcm, SCANCODE_3), getKey(kcm, SCANCODE_4), + getKey(kcm, SCANCODE_5), getKey(kcm, SCANCODE_6), getKey(kcm, SCANCODE_7), + getKey(kcm, SCANCODE_8), getKey(kcm, SCANCODE_9), getKey(kcm, SCANCODE_0), + getKey(kcm, SCANCODE_MINUS), getKey(kcm, SCANCODE_EQUALS), + getKey(KeyEvent.KEYCODE_DEL, 1.5F) + }, + { + getKey(KeyEvent.KEYCODE_TAB, 1.15F), getKey(kcm, SCANCODE_Q), + getKey(kcm, SCANCODE_W), getKey(kcm, SCANCODE_E), getKey(kcm, SCANCODE_R), + getKey(kcm, SCANCODE_T), getKey(kcm, SCANCODE_Y), getKey(kcm, SCANCODE_U), + getKey(kcm, SCANCODE_I), getKey(kcm, SCANCODE_O), getKey(kcm, SCANCODE_P), + getKey(kcm, SCANCODE_LEFT_BRACKET), getKey(kcm, SCANCODE_RIGHT_BRACKET), + getKey(KeyEvent.KEYCODE_ENTER, 1.35F) + }, + { + getKey(KeyEvent.KEYCODE_TAB, 1.5F), getKey(kcm, SCANCODE_A), + getKey(kcm, SCANCODE_S), getKey(kcm, SCANCODE_D), getKey(kcm, SCANCODE_F), + getKey(kcm, SCANCODE_G), getKey(kcm, SCANCODE_H), getKey(kcm, SCANCODE_J), + getKey(kcm, SCANCODE_K), getKey(kcm, SCANCODE_L), + getKey(kcm, SCANCODE_SEMICOLON), getKey(kcm, SCANCODE_APOSTROPHE), + getKey(kcm, SCANCODE_BACKSLASH1), + getKey(KeyEvent.KEYCODE_ENTER, 1.0F) + }, + { + getKey(KeyEvent.KEYCODE_SHIFT_LEFT, 1.15F), + getKey(kcm, SCANCODE_BACKSLASH2), getKey(kcm, SCANCODE_Z), + getKey(kcm, SCANCODE_X), getKey(kcm, SCANCODE_C), getKey(kcm, SCANCODE_V), + getKey(kcm, SCANCODE_B), getKey(kcm, SCANCODE_N), getKey(kcm, SCANCODE_M), + getKey(kcm, SCANCODE_COMMA), getKey(kcm, SCANCODE_PERIOD), + getKey(kcm, SCANCODE_SLASH), + getKey(KeyEvent.KEYCODE_SHIFT_RIGHT, 2.35F) + }, + { + getKey(KeyEvent.KEYCODE_CTRL_LEFT, 1.0F), + getKey(KeyEvent.KEYCODE_FUNCTION, 1.0F), + getKey(KeyEvent.KEYCODE_META_LEFT, 1.0F), + getKey(KeyEvent.KEYCODE_ALT_LEFT, 1.0F), + getKey(KeyEvent.KEYCODE_SPACE, 6.5F), + getKey(KeyEvent.KEYCODE_ALT_RIGHT, 1.0F), + getKey(KeyEvent.KEYCODE_META_RIGHT, 1.0F), + getKey(KeyEvent.KEYCODE_MENU, 1.0F), + getKey(KeyEvent.KEYCODE_CTRL_RIGHT, 1.0F), + } + }; + mEnterKey = new EnterKey(1, 13, 1.35F, 1.0F); + } + + private void createJisLayout(KeyCharacterMap kcm) { + mKeys = new LayoutKey[][]{ + { + getKey(kcm, SCANCODE_GRAVE), getKey(kcm, SCANCODE_1), + getKey(kcm, SCANCODE_2), getKey(kcm, SCANCODE_3), getKey(kcm, SCANCODE_4), + getKey(kcm, SCANCODE_5), getKey(kcm, SCANCODE_6), getKey(kcm, SCANCODE_7), + getKey(kcm, SCANCODE_8), getKey(kcm, SCANCODE_9), getKey(kcm, SCANCODE_0), + getKey(kcm, SCANCODE_MINUS, 0.8F), getKey(kcm, SCANCODE_EQUALS, 0.8f), + getKey(kcm, SCANCODE_YEN, 0.8f), getKey(KeyEvent.KEYCODE_DEL, 1.1F) + }, + { + getKey(KeyEvent.KEYCODE_TAB, 1.15F), getKey(kcm, SCANCODE_Q), + getKey(kcm, SCANCODE_W), getKey(kcm, SCANCODE_E), getKey(kcm, SCANCODE_R), + getKey(kcm, SCANCODE_T), getKey(kcm, SCANCODE_Y), getKey(kcm, SCANCODE_U), + getKey(kcm, SCANCODE_I), getKey(kcm, SCANCODE_O), getKey(kcm, SCANCODE_P), + getKey(kcm, SCANCODE_LEFT_BRACKET), getKey(kcm, SCANCODE_RIGHT_BRACKET), + getKey(KeyEvent.KEYCODE_ENTER, 1.35F) + }, + { + getKey(KeyEvent.KEYCODE_TAB, 1.5F), getKey(kcm, SCANCODE_A), + getKey(kcm, SCANCODE_S), getKey(kcm, SCANCODE_D), getKey(kcm, SCANCODE_F), + getKey(kcm, SCANCODE_G), getKey(kcm, SCANCODE_H), getKey(kcm, SCANCODE_J), + getKey(kcm, SCANCODE_K), getKey(kcm, SCANCODE_L), + getKey(kcm, SCANCODE_SEMICOLON), getKey(kcm, SCANCODE_APOSTROPHE), + getKey(kcm, SCANCODE_BACKSLASH2), + getKey(KeyEvent.KEYCODE_ENTER, 1.0F) + }, + { + getKey(KeyEvent.KEYCODE_SHIFT_LEFT, 1.15F), + getKey(kcm, SCANCODE_Z), getKey(kcm, SCANCODE_X), getKey(kcm, SCANCODE_C), + getKey(kcm, SCANCODE_V), getKey(kcm, SCANCODE_B), getKey(kcm, SCANCODE_N), + getKey(kcm, SCANCODE_M), getKey(kcm, SCANCODE_COMMA), + getKey(kcm, SCANCODE_PERIOD), getKey(kcm, SCANCODE_SLASH), + getKey(kcm, SCANCODE_BACKSLASH1), + getKey(KeyEvent.KEYCODE_SHIFT_RIGHT, 2.35F) + }, + { + getKey(KeyEvent.KEYCODE_CTRL_LEFT, 1.0F), + getKey(KeyEvent.KEYCODE_FUNCTION, 1.0F), + getKey(KeyEvent.KEYCODE_META_LEFT, 1.0F), + getKey(KeyEvent.KEYCODE_ALT_LEFT, 1.0F), + getKey(KeyEvent.KEYCODE_UNKNOWN, 1.0F), + getKey(KeyEvent.KEYCODE_SPACE, 3.5F), + getKey(KeyEvent.KEYCODE_UNKNOWN, 1.0F), + getKey(KeyEvent.KEYCODE_UNKNOWN, 1.0F), + getKey(KeyEvent.KEYCODE_ALT_RIGHT, 1.0F), + getKey(KeyEvent.KEYCODE_META_RIGHT, 1.0F), + getKey(KeyEvent.KEYCODE_MENU, 1.0F), + getKey(KeyEvent.KEYCODE_CTRL_RIGHT, 1.0F), + } + }; + mEnterKey = new EnterKey(1, 13, 1.35F, 1.0F); + } + + private static LayoutKey getKey(KeyCharacterMap kcm, int scanCode, float keyWeight) { + int keyCode = kcm.getMappedKeyOrDefault(scanCode, + DEFAULT_KEYCODE_FOR_SCANCODE.get(scanCode, KeyEvent.KEYCODE_UNKNOWN)); + return new LayoutKey(keyCode, scanCode, keyWeight, new KeyGlyph(kcm, keyCode)); + } + + private static LayoutKey getKey(KeyCharacterMap kcm, int scanCode) { + return getKey(kcm, scanCode, 1.0F); + } + + private static String getKeyText(KeyCharacterMap kcm, int keyCode, int modifierState) { + if (isSpecialKey(keyCode)) { + return ""; + } + int utf8Char = (kcm.get(keyCode, modifierState) & KeyCharacterMap.COMBINING_ACCENT_MASK); + if (Character.isValidCodePoint(utf8Char)) { + return String.valueOf(Character.toChars(utf8Char)).toUpperCase(Locale.getDefault()); + } else { + return String.valueOf(kcm.getDisplayLabel(keyCode)).toUpperCase(Locale.getDefault()); + } + } + + private static LayoutKey getKey(int keyCode, float keyWeight) { + return new LayoutKey(keyCode, keyCode, keyWeight, null); + } + + /** + * Util function that tells if a key corresponds to a special key which are keys on a Physical + * layout that perform some special action like modifier keys, enter key, space key, character + * set changing keys, etc. + */ + private static boolean isSpecialKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_DEL: + case KeyEvent.KEYCODE_TAB: + case KeyEvent.KEYCODE_CAPS_LOCK: + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_SHIFT_LEFT: + case KeyEvent.KEYCODE_SHIFT_RIGHT: + case KeyEvent.KEYCODE_CTRL_LEFT: + case KeyEvent.KEYCODE_CTRL_RIGHT: + case KeyEvent.KEYCODE_FUNCTION: + case KeyEvent.KEYCODE_ALT_LEFT: + case KeyEvent.KEYCODE_ALT_RIGHT: + case KeyEvent.KEYCODE_META_LEFT: + case KeyEvent.KEYCODE_META_RIGHT: + case KeyEvent.KEYCODE_SPACE: + case KeyEvent.KEYCODE_MENU: + case KeyEvent.KEYCODE_UNKNOWN: + return true; + } + return false; + } + + public static boolean isSpecialKey(LayoutKey key) { + return isSpecialKey(key.keyCode); + } + + public static boolean isKeyPositionUnsure(LayoutKey key) { + switch (key.scanCode) { + case SCANCODE_GRAVE: + case SCANCODE_BACKSLASH1: + case SCANCODE_BACKSLASH2: + return true; + } + return false; + } + + public record LayoutKey(int keyCode, int scanCode, float keyWeight, KeyGlyph glyph) {} + public record EnterKey(int row, int column, float topKeyWeight, float bottomKeyWeight) {} + + public static class KeyGlyph { + private final String mBaseText; + private final String mShiftText; + private final String mAltGrText; + + public KeyGlyph(KeyCharacterMap kcm, int keyCode) { + mBaseText = getKeyText(kcm, keyCode, 0); + mShiftText = getKeyText(kcm, keyCode, + KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON); + mAltGrText = getKeyText(kcm, keyCode, + KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON); + } + + public String getBaseText() { + return mBaseText; + } + + public String getShiftText() { + return mShiftText; + } + + public String getAltGrText() { + return mAltGrText; + } + + public boolean hasBaseText() { + return !TextUtils.isEmpty(mBaseText); + } + + public boolean hasValidShiftText() { + return !TextUtils.isEmpty(mShiftText) && !TextUtils.equals(mBaseText, mShiftText); + } + + public boolean hasValidAltGrText() { + return !TextUtils.isEmpty(mAltGrText) && !TextUtils.equals(mBaseText, mAltGrText); + } + } +} diff --git a/core/java/android/hardware/usb/DisplayPortAltModeInfo.java b/core/java/android/hardware/usb/DisplayPortAltModeInfo.java index 9da2f4c12977..36c4a2ac09e4 100644 --- a/core/java/android/hardware/usb/DisplayPortAltModeInfo.java +++ b/core/java/android/hardware/usb/DisplayPortAltModeInfo.java @@ -200,19 +200,43 @@ public final class DisplayPortAltModeInfo implements Parcelable { dest.writeInt(mLinkTrainingStatus); } + private String displayPortAltModeStatusToString(@DisplayPortAltModeStatus int status) { + switch (status) { + case DISPLAYPORT_ALT_MODE_STATUS_NOT_CAPABLE: + return "not capable"; + case DISPLAYPORT_ALT_MODE_STATUS_CAPABLE_DISABLED: + return "capable disabled"; + case DISPLAYPORT_ALT_MODE_STATUS_ENABLED: + return "enabled"; + default: + return "unknown"; + } + } + + private String linkTrainingStatusToString(@LinkTrainingStatus int status) { + switch (status) { + case LINK_TRAINING_STATUS_SUCCESS: + return "success"; + case LINK_TRAINING_STATUS_FAILURE: + return "failure"; + default: + return "unknown"; + } + } + @NonNull @Override public String toString() { return "DisplayPortAltModeInfo{partnerSink=" - + mPartnerSinkStatus - + " cable=" - + mCableStatus - + " numLanes=" + + displayPortAltModeStatusToString(mPartnerSinkStatus) + + ", cable=" + + displayPortAltModeStatusToString(mCableStatus) + + ", numLanes=" + mNumLanes - + " hotPlugDetect=" + + ", hotPlugDetect=" + mHotPlugDetect - + " linkTrainingStatus=" - + mLinkTrainingStatus + + ", linkTrainingStatus=" + + linkTrainingStatusToString(mLinkTrainingStatus) + "}"; } diff --git a/core/java/android/nfc/NfcAntennaInfo.java b/core/java/android/nfc/NfcAntennaInfo.java index d54fcd2ed5b3..b002ca21e8e3 100644 --- a/core/java/android/nfc/NfcAntennaInfo.java +++ b/core/java/android/nfc/NfcAntennaInfo.java @@ -85,8 +85,8 @@ public final class NfcAntennaInfo implements Parcelable { this.mDeviceHeight = in.readInt(); this.mDeviceFoldable = in.readByte() != 0; this.mAvailableNfcAntennas = new ArrayList<>(); - in.readParcelableList(this.mAvailableNfcAntennas, - AvailableNfcAntenna.class.getClassLoader()); + in.readTypedList(this.mAvailableNfcAntennas, + AvailableNfcAntenna.CREATOR); } public static final @NonNull Parcelable.Creator<NfcAntennaInfo> CREATOR = diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index 1929a4d562d4..ada55325aded 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -227,7 +227,7 @@ public final class BinderProxy implements IBinder { Log.v(Binder.TAG, "BinderProxy map growth! bucket size = " + size + " total = " + totalSize); mWarnBucketSize += WARN_INCREMENT; - if (Build.IS_DEBUGGABLE && totalSize >= CRASH_AT_SIZE) { + if (totalSize >= CRASH_AT_SIZE) { // Use the number of uncleared entries to determine whether we should // really report a histogram and crash. We don't want to fundamentally // change behavior for a debuggable process, so we GC only if we are diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 62d9c69565da..c527cb5344c1 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppGlobals; @@ -1953,6 +1954,23 @@ public final class Debug */ public static native long getPss(int pid, long[] outUssSwapPssRss, long[] outMemtrack); + /** + * Retrieves the RSS memory used by the process as given by the status file. + */ + @FlaggedApi(Flags.FLAG_REMOVE_APP_PROFILER_PSS_COLLECTION) + public static native long getRss(); + + /** + * Retrieves the RSS memory used by the process as given by the status file. Optionally supply a + * long array of up to 4 entries to retrieve the total memtrack reported size, memtrack + * graphics, memtrack gl, and memtrack other. + * + * @return The RSS memory usage, or 0 if retrieval failed (i.e. the PID is gone). + * @hide + */ + @FlaggedApi(Flags.FLAG_REMOVE_APP_PROFILER_PSS_COLLECTION) + public static native long getRss(int pid, long[] outMemtrack); + /** @hide */ public static final int MEMINFO_TOTAL = 0; /** @hide */ diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java index b210c4644d7d..e96c24d677f1 100644 --- a/core/java/android/os/ServiceManager.java +++ b/core/java/android/os/ServiceManager.java @@ -245,7 +245,7 @@ public final class ServiceManager { public static boolean isDeclared(@NonNull String name) { try { return getIServiceManager().isDeclared(name); - } catch (RemoteException e) { + } catch (RemoteException | SecurityException e) { Log.e(TAG, "error in isDeclared", e); return false; } diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig new file mode 100644 index 000000000000..77be5d400853 --- /dev/null +++ b/core/java/android/permission/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.permission.flags" + +flag { + name: "device_aware_permission_apis" + namespace: "permissions" + description: "enable device aware permission APIs" + bug: "274852670" +} diff --git a/core/java/android/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java index 132700d289b8..d6f3bf334456 100644 --- a/core/java/android/security/FileIntegrityManager.java +++ b/core/java/android/security/FileIntegrityManager.java @@ -133,11 +133,13 @@ public final class FileIntegrityManager { * also use this API to download the best signature on the running device. * * @return whether the certificate is trusted in the system + * @deprecated The feature is no longer supported, and this API now always returns false. */ @RequiresPermission(anyOf = { android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES }) + @Deprecated public boolean isAppSourceCertificateTrusted(@NonNull X509Certificate certificate) throws CertificateEncodingException { try { diff --git a/core/java/android/security/TEST_MAPPING b/core/java/android/security/TEST_MAPPING index 7e43381ee6a9..5a679b1a2bf7 100644 --- a/core/java/android/security/TEST_MAPPING +++ b/core/java/android/security/TEST_MAPPING @@ -1,5 +1,5 @@ { - "postsubmit": [ + "presubmit": [ { "name": "CtsSecurityTestCases", "options": [ diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java index 00c30b12c93d..83f96629a582 100644 --- a/core/java/android/service/autofill/AutofillServiceInfo.java +++ b/core/java/android/service/autofill/AutofillServiceInfo.java @@ -63,6 +63,10 @@ public final class AutofillServiceInfo { private static final String TAG_AUTOFILL_SERVICE = "autofill-service"; private static final String TAG_COMPATIBILITY_PACKAGE = "compatibility-package"; + private static final ComponentName CREDMAN_SERVICE_COMPONENT_NAME = + new ComponentName("com.android.credentialmanager", + "com.android.credentialmanager.autofill.CredentialAutofillService"); + private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, int userHandle) throws PackageManager.NameNotFoundException { try { @@ -307,6 +311,11 @@ public final class AutofillServiceInfo { for (ResolveInfo resolveInfo : resolveInfos) { final ServiceInfo serviceInfo = resolveInfo.serviceInfo; try { + if (serviceInfo != null && isCredentialManagerAutofillService( + serviceInfo.getComponentName())) { + // Skip this service as it is for internal use only + continue; + } services.add(new AutofillServiceInfo(context, serviceInfo)); } catch (SecurityException e) { // Service does not declare the proper permission, ignore it. @@ -316,6 +325,13 @@ public final class AutofillServiceInfo { return services; } + private static boolean isCredentialManagerAutofillService(ComponentName componentName) { + if (componentName == null) { + return false; + } + return componentName.equals(CREDMAN_SERVICE_COMPONENT_NAME); + } + @Override public String toString() { final StringBuilder builder = new StringBuilder(); diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index b48b7ecd73a2..3f41c56ac7f1 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -46,6 +46,7 @@ import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SharedMemory; +import android.os.SystemProperties; import android.provider.Settings; import android.util.ArraySet; import android.util.Log; @@ -131,6 +132,9 @@ public class VoiceInteractionService extends Service { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) static final long MULTIPLE_ACTIVE_HOTWORD_DETECTORS = 193232191L; + private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED = + SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false); + IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() { @Override public void ready() { @@ -947,6 +951,10 @@ public class VoiceInteractionService extends Service { Objects.requireNonNull(executor); Objects.requireNonNull(callback); + if (!SYSPROP_VISUAL_QUERY_SERVICE_ENABLED) { + throw new IllegalStateException("VisualQueryDetectionService is not enabled on this " + + "system. Please set ro.hotword.visual_query_service_enabled to true."); + } if (mSystemService == null) { throw new IllegalStateException("Not available until onReady() is called"); } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 0ed275c7821d..2906d86f803d 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -187,14 +187,6 @@ public class FeatureFlagUtils { public static final String SETTINGS_FLASH_NOTIFICATIONS = "settings_flash_notifications"; /** - * Flag to disable/enable showing udfps enroll view in settings. If it's disabled, udfps enroll - * view is shown in system ui. - * @hide - */ - public static final String SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS = - "settings_show_udfps_enroll_in_settings"; - - /** * Flag to enable lock screen credentials transfer API in Android U. * @hide */ @@ -208,14 +200,6 @@ public class FeatureFlagUtils { public static final String SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION = "settings_remote_device_credential_validation"; - // TODO(b/295516544): Remove this when trunk stable feature flag is available. - /** - * Flag to enable / disable the Private Space Settings. It's disabled by default. - * @hide - */ - public static final String SETTINGS_PRIVATE_SPACE_SETTINGS = - "settings_private_space_settings"; - private static final Map<String, String> DEFAULT_FLAGS; @@ -258,13 +242,11 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false"); DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false"); DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true"); - DEFAULT_FLAGS.put(SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, "true"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true"); DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true"); DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false"); // TODO: b/298454866 Replace with Trunk Stable Feature Flag DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false"); - DEFAULT_FLAGS.put(SETTINGS_PRIVATE_SPACE_SETTINGS, "false"); } private static final Set<String> PERSISTENT_FLAGS; diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java index bc837501037f..2761aaeb4a7d 100644 --- a/core/java/android/view/InputWindowHandle.java +++ b/core/java/android/view/InputWindowHandle.java @@ -35,8 +35,6 @@ import java.lang.ref.WeakReference; * @hide */ public final class InputWindowHandle { - // TODO (b/300094445): Convert to use correct flagging infrastructure - public static final boolean USE_SURFACE_TRUSTED_OVERLAY = true; /** * An internal annotation for all the {@link android.os.InputConfig} flags that can be @@ -61,6 +59,7 @@ public final class InputWindowHandle { InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER, InputConfig.IS_WALLPAPER, InputConfig.PAUSE_DISPATCHING, + InputConfig.TRUSTED_OVERLAY, InputConfig.WATCH_OUTSIDE_TOUCH, InputConfig.SLIPPERY, InputConfig.DISABLE_USER_ACTIVITY, @@ -273,13 +272,4 @@ public final class InputWindowHandle { } this.inputConfig &= ~inputConfig; } - - public void setTrustedOverlay(SurfaceControl.Transaction t, SurfaceControl sc, - boolean isTrusted) { - if (USE_SURFACE_TRUSTED_OVERLAY) { - t.setTrustedOverlay(sc, isTrusted); - } else if (isTrusted) { - inputConfig |= InputConfig.TRUSTED_OVERLAY; - } - } } diff --git a/core/java/android/view/KeyCharacterMap.aidl b/core/java/android/view/KeyCharacterMap.aidl new file mode 100644 index 000000000000..1a761a67b520 --- /dev/null +++ b/core/java/android/view/KeyCharacterMap.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +parcelable KeyCharacterMap;
\ No newline at end of file diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java index d8221a6267fd..4fe53c2410f5 100644 --- a/core/java/android/view/KeyCharacterMap.java +++ b/core/java/android/view/KeyCharacterMap.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.input.InputManagerGlobal; @@ -309,6 +310,10 @@ public class KeyCharacterMap implements Parcelable { private static native KeyCharacterMap nativeObtainEmptyKeyCharacterMap(int deviceId); private static native boolean nativeEquals(long ptr1, long ptr2); + private static native void nativeApplyOverlay(long ptr, String layoutDescriptor, + String overlay); + private static native int nativeGetMappedKey(long ptr, int scanCode); + private KeyCharacterMap(Parcel in) { if (in == null) { throw new IllegalArgumentException("parcel must not be null"); @@ -368,6 +373,38 @@ public class KeyCharacterMap implements Parcelable { } /** + * Loads the key character map with applied KCM overlay. + * + * @param layoutDescriptor descriptor of the applied overlay KCM + * @param overlay string describing the overlay KCM + * @return The resultant key character map. + * @throws {@link UnavailableException} if the key character map + * could not be loaded because it was malformed or the default key character map + * is missing from the system. + * @hide + */ + public static KeyCharacterMap load(@NonNull String layoutDescriptor, @NonNull String overlay) { + KeyCharacterMap kcm = KeyCharacterMap.load(VIRTUAL_KEYBOARD); + kcm.applyOverlay(layoutDescriptor, overlay); + return kcm; + } + + private void applyOverlay(@NonNull String layoutDescriptor, @NonNull String overlay) { + nativeApplyOverlay(mPtr, layoutDescriptor, overlay); + } + + /** + * Gets the mapped key for the provided scan code. Returns the provided default if no mapping + * found in the KeyCharacterMap. + * + * @hide + */ + public int getMappedKeyOrDefault(int scanCode, int defaultKeyCode) { + int keyCode = nativeGetMappedKey(mPtr, scanCode); + return keyCode == KeyEvent.KEYCODE_UNKNOWN ? defaultKeyCode : keyCode; + } + + /** * Gets the Unicode character generated by the specified key and meta * key state combination. * <p> diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index cdf5eec32fec..776eeda7484e 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -18,7 +18,6 @@ package android.view; import static android.os.IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT; import static android.view.Display.DEFAULT_DISPLAY; - import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; @@ -4361,6 +4360,17 @@ public final class MotionEvent extends InputEvent implements Parcelable { public boolean isResampled; /** + * Returns true if this pointer coords object was generated by resampling, rather than from + * an actual input event from the device at this time. + * + * @hide + */ + @TestApi + public boolean isResampled() { + return isResampled; + } + + /** * Clears the contents of this object. * Resets all axes to zero. */ diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 85d7c10ef91e..fe515cd3091a 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -4437,7 +4437,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @param drawingPosition the drawing order position. * @return the container position of a child for this drawing order position. * - * @see #getChildDrawingOrder(int, int)} + * @see #getChildDrawingOrder(int, int) */ public final int getChildDrawingOrder(int drawingPosition) { return getChildDrawingOrder(getChildCount(), drawingPosition); diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java index 3b8298ed3627..dda399357d8c 100644 --- a/core/java/android/view/WindowLayout.java +++ b/core/java/android/view/WindowLayout.java @@ -124,16 +124,16 @@ public class WindowLayout { || cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) { final Insets systemBarsInsets = state.calculateInsets( displayFrame, systemBars(), requestedVisibleTypes); - if (systemBarsInsets.left > 0) { + if (systemBarsInsets.left >= cutout.getSafeInsetLeft()) { displayCutoutSafeExceptMaybeBars.left = MIN_X; } - if (systemBarsInsets.top > 0) { + if (systemBarsInsets.top >= cutout.getSafeInsetTop()) { displayCutoutSafeExceptMaybeBars.top = MIN_Y; } - if (systemBarsInsets.right > 0) { + if (systemBarsInsets.right >= cutout.getSafeInsetRight()) { displayCutoutSafeExceptMaybeBars.right = MAX_X; } - if (systemBarsInsets.bottom > 0) { + if (systemBarsInsets.bottom >= cutout.getSafeInsetBottom()) { displayCutoutSafeExceptMaybeBars.bottom = MAX_Y; } } diff --git a/core/java/android/view/inputmethod/TEST_MAPPING b/core/java/android/view/inputmethod/TEST_MAPPING index 4b2ea1a096c8..ad59463ea1f1 100644 --- a/core/java/android/view/inputmethod/TEST_MAPPING +++ b/core/java/android/view/inputmethod/TEST_MAPPING @@ -11,6 +11,9 @@ }, { "exclude-annotation": "android.platform.test.annotations.AppModeFull" + }, + { + "exclude-annotation": "androidx.test.filters.LargeTest" } ] } diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java index 065089f53633..53c73c6bf161 100644 --- a/core/java/android/widget/AdapterViewFlipper.java +++ b/core/java/android/widget/AdapterViewFlipper.java @@ -16,10 +16,7 @@ package android.widget; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.res.TypedArray; import android.os.Message; import android.util.AttributeSet; @@ -48,7 +45,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator { private boolean mRunning = false; private boolean mStarted = false; private boolean mVisible = false; - private boolean mUserPresent = true; private boolean mAdvancedByHost = false; public AdapterViewFlipper(Context context) { @@ -82,40 +78,10 @@ public class AdapterViewFlipper extends AdapterViewAnimator { a.recycle(); } - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (Intent.ACTION_SCREEN_OFF.equals(action)) { - mUserPresent = false; - updateRunning(); - } else if (Intent.ACTION_USER_PRESENT.equals(action)) { - mUserPresent = true; - updateRunning(false); - } - } - }; - @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - // Listen for broadcasts related to user-presence - final IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_USER_PRESENT); - - // OK, this is gross but needed. This class is supported by the - // remote views machanism and as a part of that the remote views - // can be inflated by a context for another user without the app - // having interact users permission - just for loading resources. - // For exmaple, when adding widgets from a user profile to the - // home screen. Therefore, we register the receiver as the current - // user not the one the context is for. - getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(), - filter, null, getHandler()); - - if (mAutoStart) { // Automatically start when requested startFlipping(); @@ -126,8 +92,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mVisible = false; - - getContext().unregisterReceiver(mReceiver); updateRunning(); } @@ -235,8 +199,7 @@ public class AdapterViewFlipper extends AdapterViewAnimator { * true. */ private void updateRunning(boolean flipNow) { - boolean running = !mAdvancedByHost && mVisible && mStarted && mUserPresent - && mAdapter != null; + boolean running = !mAdvancedByHost && mVisible && mStarted && mAdapter != null; if (running != mRunning) { if (running) { showOnly(mWhichChild, flipNow); @@ -248,7 +211,7 @@ public class AdapterViewFlipper extends AdapterViewAnimator { } if (LOGD) { Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted - + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning); + + ", mRunning=" + mRunning); } } diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java index 1f0e95ea305a..e01583322979 100644 --- a/core/java/android/widget/AnalogClock.java +++ b/core/java/android/widget/AnalogClock.java @@ -23,7 +23,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.BlendMode; @@ -37,6 +36,9 @@ import android.view.RemotableViewMethod; import android.view.View; import android.view.inspector.InspectableProperty; import android.widget.RemoteViews.RemoteView; +import android.widget.TextClock.ClockEventDelegate; + +import com.android.internal.util.Preconditions; import java.time.Clock; import java.time.DateTimeException; @@ -112,6 +114,7 @@ public class AnalogClock extends View { public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mClockEventDelegate = new ClockEventDelegate(context); mSecondsHandFps = AppGlobals.getIntCoreSetting( WidgetFlags.KEY_ANALOG_CLOCK_SECONDS_HAND_FPS, context.getResources() @@ -584,21 +587,9 @@ public class AnalogClock extends View { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - IntentFilter filter = new IntentFilter(); if (!mReceiverAttached) { - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - - // OK, this is gross but needed. This class is supported by the - // remote views mechanism and as a part of that the remote views - // can be inflated by a context for another user without the app - // having interact users permission - just for loading resources. - // For example, when adding widgets from a user profile to the - // home screen. Therefore, we register the receiver as the current - // user not the one the context is for. - getContext().registerReceiverAsUser(mIntentReceiver, - android.os.Process.myUserHandle(), filter, null, getHandler()); + mClockEventDelegate.registerTimeChangeReceiver(mIntentReceiver, getHandler()); mReceiverAttached = true; } @@ -615,12 +606,23 @@ public class AnalogClock extends View { @Override protected void onDetachedFromWindow() { if (mReceiverAttached) { - getContext().unregisterReceiver(mIntentReceiver); + mClockEventDelegate.unregisterTimeChangeReceiver(mIntentReceiver); mReceiverAttached = false; } super.onDetachedFromWindow(); } + /** + * Sets a delegate to handle clock event registration. This must be called before the view is + * attached to the window + * + * @hide + */ + public void setClockEventDelegate(ClockEventDelegate delegate) { + Preconditions.checkState(!mReceiverAttached, "Clock events already registered"); + mClockEventDelegate = delegate; + } + private void onVisible() { if (!mVisible) { mVisible = true; @@ -797,6 +799,7 @@ public class AnalogClock extends View { } }; private boolean mReceiverAttached; + private ClockEventDelegate mClockEventDelegate; private final Runnable mTick = new Runnable() { @Override diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java index e48afb2a3cc8..255bd679dc35 100644 --- a/core/java/android/widget/TextClock.java +++ b/core/java/android/widget/TextClock.java @@ -16,6 +16,7 @@ package android.widget; +import static android.os.Process.myUserHandle; import static android.view.ViewDebug.ExportedProperty; import static android.widget.RemoteViews.RemoteView; @@ -24,7 +25,6 @@ import android.annotation.TestApi; import android.app.ActivityManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -43,6 +43,7 @@ import android.view.ViewHierarchyEncoder; import android.view.inspector.InspectableProperty; import com.android.internal.R; +import com.android.internal.util.Preconditions; import java.time.Duration; import java.time.Instant; @@ -141,6 +142,8 @@ public class TextClock extends TextView { private boolean mRegistered; private boolean mShouldRunTicker; + private ClockEventDelegate mClockEventDelegate; + private Calendar mTime; private String mTimeZone; @@ -178,8 +181,7 @@ public class TextClock extends TextView { if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { final String timeZone = intent.getStringExtra(Intent.EXTRA_TIMEZONE); createTime(timeZone); - } else if (!mShouldRunTicker && (Intent.ACTION_TIME_TICK.equals(intent.getAction()) - || Intent.ACTION_TIME_CHANGED.equals(intent.getAction()))) { + } else if (!mShouldRunTicker && Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) { return; } onTimeChanged(); @@ -282,6 +284,7 @@ public class TextClock extends TextView { if (mFormat24 == null) { mFormat24 = getBestDateTimePattern("Hm"); } + mClockEventDelegate = new ClockEventDelegate(getContext()); createTime(mTimeZone); chooseFormat(); @@ -431,6 +434,17 @@ public class TextClock extends TextView { } /** + * Sets a delegate to handle clock event registration. This must be called before the view is + * attached to the window + * + * @hide + */ + public void setClockEventDelegate(ClockEventDelegate delegate) { + Preconditions.checkState(!mRegistered, "Clock events already registered"); + mClockEventDelegate = delegate; + } + + /** * Update the displayed time if necessary and invalidate the view. */ public void refreshTime() { @@ -557,7 +571,7 @@ public class TextClock extends TextView { if (!mRegistered) { mRegistered = true; - registerReceiver(); + mClockEventDelegate.registerTimeChangeReceiver(mIntentReceiver, getHandler()); registerObserver(); createTime(mTimeZone); @@ -582,7 +596,7 @@ public class TextClock extends TextView { super.onDetachedFromWindow(); if (mRegistered) { - unregisterReceiver(); + mClockEventDelegate.unregisterTimeChangeReceiver(mIntentReceiver); unregisterObserver(); mRegistered = false; @@ -598,56 +612,27 @@ public class TextClock extends TextView { mStopTicking = true; } - private void registerReceiver() { - final IntentFilter filter = new IntentFilter(); - - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - - // OK, this is gross but needed. This class is supported by the - // remote views mechanism and as a part of that the remote views - // can be inflated by a context for another user without the app - // having interact users permission - just for loading resources. - // For example, when adding widgets from a managed profile to the - // home screen. Therefore, we register the receiver as the user - // the app is running as not the one the context is for. - getContext().registerReceiverAsUser(mIntentReceiver, android.os.Process.myUserHandle(), - filter, null, getHandler()); - } - private void registerObserver() { if (mRegistered) { if (mFormatChangeObserver == null) { mFormatChangeObserver = new FormatChangeObserver(getHandler()); } - final ContentResolver resolver = getContext().getContentResolver(); - Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24); - if (mShowCurrentUserTime) { - resolver.registerContentObserver(uri, true, - mFormatChangeObserver, UserHandle.USER_ALL); - } else { - // UserHandle.myUserId() is needed. This class is supported by the - // remote views mechanism and as a part of that the remote views - // can be inflated by a context for another user without the app - // having interact users permission - just for loading resources. - // For example, when adding widgets from a managed profile to the - // home screen. Therefore, we register the ContentObserver with the user - // the app is running (e.g. the launcher) and not the user of the - // context (e.g. the widget's profile). - resolver.registerContentObserver(uri, true, - mFormatChangeObserver, UserHandle.myUserId()); - } + // UserHandle.myUserId() is needed. This class is supported by the + // remote views mechanism and as a part of that the remote views + // can be inflated by a context for another user without the app + // having interact users permission - just for loading resources. + // For example, when adding widgets from a managed profile to the + // home screen. Therefore, we register the ContentObserver with the user + // the app is running (e.g. the launcher) and not the user of the + // context (e.g. the widget's profile). + int userHandle = mShowCurrentUserTime ? UserHandle.USER_ALL : UserHandle.myUserId(); + mClockEventDelegate.registerFormatChangeObserver(mFormatChangeObserver, userHandle); } } - private void unregisterReceiver() { - getContext().unregisterReceiver(mIntentReceiver); - } - private void unregisterObserver() { if (mFormatChangeObserver != null) { - final ContentResolver resolver = getContext().getContentResolver(); - resolver.unregisterContentObserver(mFormatChangeObserver); + mClockEventDelegate.unregisterFormatChangeObserver(mFormatChangeObserver); } } @@ -674,4 +659,59 @@ public class TextClock extends TextView { stream.addProperty("format", mFormat == null ? null : mFormat.toString()); stream.addProperty("hasSeconds", mHasSeconds); } + + /** + * Utility class to delegate some system event handling to allow overring the default behavior + * + * @hide + */ + public static class ClockEventDelegate { + + private final Context mContext; + + public ClockEventDelegate(Context context) { + mContext = context; + } + + /** + * Registers a receiver for actions {@link Intent#ACTION_TIME_CHANGED} and + * {@link Intent#ACTION_TIMEZONE_CHANGED} + * + * OK, this is gross but needed. This class is supported by the remote views mechanism and + * as a part of that the remote views can be inflated by a context for another user without + * the app having interact users permission - just for loading resources. For example, + * when adding widgets from a managed profile to the home screen. Therefore, we register + * the receiver as the user the app is running as not the one the context is for. + */ + public void registerTimeChangeReceiver(BroadcastReceiver receiver, Handler handler) { + final IntentFilter filter = new IntentFilter(); + + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + + mContext.registerReceiverAsUser(receiver, myUserHandle(), filter, null, handler); + } + + /** + * Unregisters a previously registered receiver + */ + public void unregisterTimeChangeReceiver(BroadcastReceiver receiver) { + mContext.unregisterReceiver(receiver); + } + + /** + * Registers an observer for time format changes + */ + public void registerFormatChangeObserver(ContentObserver observer, int userHandle) { + Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24); + mContext.getContentResolver().registerContentObserver(uri, true, observer, userHandle); + } + + /** + * Unregisters a previously registered observer + */ + public void unregisterFormatChangeObserver(ContentObserver observer) { + mContext.getContentResolver().unregisterContentObserver(observer); + } + } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 59344b00e79a..05063365561f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -15143,6 +15143,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final ClipDescription description = getClipboardManagerForUser().getPrimaryClipDescription(); + if (description == null) { + return false; + } final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); return (isPlainType && description.isStyledText()) || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java index 5abb6e1637e7..eaf037e68976 100644 --- a/core/java/android/widget/ViewFlipper.java +++ b/core/java/android/widget/ViewFlipper.java @@ -18,10 +18,7 @@ package android.widget; import android.annotation.IntRange; import android.compat.annotation.UnsupportedAppUsage; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.res.TypedArray; import android.os.Build; import android.os.Message; @@ -51,8 +48,6 @@ public class ViewFlipper extends ViewAnimator { private boolean mRunning = false; private boolean mStarted = false; private boolean mVisible = false; - @UnsupportedAppUsage - private boolean mUserPresent = true; public ViewFlipper(Context context) { super(context); @@ -70,39 +65,10 @@ public class ViewFlipper extends ViewAnimator { a.recycle(); } - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (Intent.ACTION_SCREEN_OFF.equals(action)) { - mUserPresent = false; - updateRunning(); - } else if (Intent.ACTION_USER_PRESENT.equals(action)) { - mUserPresent = true; - updateRunning(false); - } - } - }; - @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - // Listen for broadcasts related to user-presence - final IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(Intent.ACTION_USER_PRESENT); - - // OK, this is gross but needed. This class is supported by the - // remote views machanism and as a part of that the remote views - // can be inflated by a context for another user without the app - // having interact users permission - just for loading resources. - // For exmaple, when adding widgets from a user profile to the - // home screen. Therefore, we register the receiver as the current - // user not the one the context is for. - getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(), - filter, null, getHandler()); - if (mAutoStart) { // Automatically start when requested startFlipping(); @@ -113,8 +79,6 @@ public class ViewFlipper extends ViewAnimator { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mVisible = false; - - getContext().unregisterReceiver(mReceiver); updateRunning(); } @@ -186,7 +150,7 @@ public class ViewFlipper extends ViewAnimator { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void updateRunning(boolean flipNow) { - boolean running = mVisible && mStarted && mUserPresent; + boolean running = mVisible && mStarted; if (running != mRunning) { if (running) { showOnly(mWhichChild, flipNow); @@ -198,7 +162,7 @@ public class ViewFlipper extends ViewAnimator { } if (LOGD) { Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted - + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning); + + ", mRunning=" + mRunning); } } diff --git a/core/java/android/widget/inline/TEST_MAPPING b/core/java/android/widget/inline/TEST_MAPPING index 26a556906dd1..82c6f61c3486 100644 --- a/core/java/android/widget/inline/TEST_MAPPING +++ b/core/java/android/widget/inline/TEST_MAPPING @@ -8,6 +8,9 @@ }, { "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "androidx.test.filters.LargeTest" } ] } diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java index f40874b77536..758582615a46 100644 --- a/core/java/android/window/SnapshotDrawerUtils.java +++ b/core/java/android/window/SnapshotDrawerUtils.java @@ -237,13 +237,14 @@ public class SnapshotDrawerUtils { PixelFormat.RGBA_8888, GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER | GraphicBuffer.USAGE_SW_WRITE_RARELY); - if (background == null) { + final Canvas c = background != null ? background.lockCanvas() : null; + if (c == null) { Log.e(TAG, "Unable to draw snapshot: failed to allocate graphic buffer for " + mTitle); + mTransaction.clear(); + childSurfaceControl.release(); return; } - // TODO: Support this on HardwareBuffer - final Canvas c = background.lockCanvas(); drawBackgroundAndBars(c, frame); background.unlockCanvasAndPost(c); mTransaction.setBuffer(mRootSurface, diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java index edea2978c340..9b10a7ff5d12 100644 --- a/core/java/android/window/TransitionRequestInfo.java +++ b/core/java/android/window/TransitionRequestInfo.java @@ -36,11 +36,17 @@ public final class TransitionRequestInfo implements Parcelable { private final @WindowManager.TransitionType int mType; /** - * If non-null, If non-null, the task containing the activity whose lifecycle change (start or + * If non-null, the task containing the activity whose lifecycle change (start or * finish) has caused this transition to occur. */ private @Nullable ActivityManager.RunningTaskInfo mTriggerTask; + /** + * If non-null, the task containing the pip activity that participates in this + * transition. + */ + private @Nullable ActivityManager.RunningTaskInfo mPipTask; + /** If non-null, a remote-transition associated with the source of this transition. */ private @Nullable RemoteTransition mRemoteTransition; @@ -59,7 +65,8 @@ public final class TransitionRequestInfo implements Parcelable { @WindowManager.TransitionType int type, @Nullable ActivityManager.RunningTaskInfo triggerTask, @Nullable RemoteTransition remoteTransition) { - this(type, triggerTask, remoteTransition, null /* displayChange */, 0 /* flags */); + this(type, triggerTask, null /* pipTask */, + remoteTransition, null /* displayChange */, 0 /* flags */); } /** constructor override */ @@ -68,7 +75,17 @@ public final class TransitionRequestInfo implements Parcelable { @Nullable ActivityManager.RunningTaskInfo triggerTask, @Nullable RemoteTransition remoteTransition, int flags) { - this(type, triggerTask, remoteTransition, null /* displayChange */, flags); + this(type, triggerTask, null /* pipTask */, + remoteTransition, null /* displayChange */, flags); + } + + public TransitionRequestInfo( + @WindowManager.TransitionType int type, + @Nullable ActivityManager.RunningTaskInfo triggerTask, + @Nullable RemoteTransition remoteTransition, + @Nullable TransitionRequestInfo.DisplayChange displayChange, + int flags) { + this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags); } /** Requested change to a display. */ @@ -246,7 +263,7 @@ public final class TransitionRequestInfo implements Parcelable { }; @DataClass.Generated( - time = 1691627678294L, + time = 1693425051905L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java", inputSignatures = "private final int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate int mStartRotation\nprivate int mEndRotation\nprivate boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)") @@ -283,6 +300,9 @@ public final class TransitionRequestInfo implements Parcelable { * @param triggerTask * If non-null, If non-null, the task containing the activity whose lifecycle change (start or * finish) has caused this transition to occur. + * @param pipTask + * If non-null, If non-null, the task containing the activity whose lifecycle change (start or + * finish) has caused this transition to occur. * @param remoteTransition * If non-null, a remote-transition associated with the source of this transition. * @param displayChange @@ -296,6 +316,7 @@ public final class TransitionRequestInfo implements Parcelable { public TransitionRequestInfo( @WindowManager.TransitionType int type, @Nullable ActivityManager.RunningTaskInfo triggerTask, + @Nullable ActivityManager.RunningTaskInfo pipTask, @Nullable RemoteTransition remoteTransition, @Nullable TransitionRequestInfo.DisplayChange displayChange, int flags) { @@ -303,6 +324,7 @@ public final class TransitionRequestInfo implements Parcelable { com.android.internal.util.AnnotationValidations.validate( WindowManager.TransitionType.class, null, mType); this.mTriggerTask = triggerTask; + this.mPipTask = pipTask; this.mRemoteTransition = remoteTransition; this.mDisplayChange = displayChange; this.mFlags = flags; @@ -319,7 +341,7 @@ public final class TransitionRequestInfo implements Parcelable { } /** - * If non-null, If non-null, the task containing the activity whose lifecycle change (start or + * If non-null, the task containing the activity whose lifecycle change (start or * finish) has caused this transition to occur. */ @DataClass.Generated.Member @@ -328,6 +350,15 @@ public final class TransitionRequestInfo implements Parcelable { } /** + * If non-null, the task containing the pip activity that participates in this + * transition. + */ + @DataClass.Generated.Member + public @Nullable ActivityManager.RunningTaskInfo getPipTask() { + return mPipTask; + } + + /** * If non-null, a remote-transition associated with the source of this transition. */ @DataClass.Generated.Member @@ -354,7 +385,7 @@ public final class TransitionRequestInfo implements Parcelable { } /** - * If non-null, If non-null, the task containing the activity whose lifecycle change (start or + * If non-null, the task containing the activity whose lifecycle change (start or * finish) has caused this transition to occur. */ @DataClass.Generated.Member @@ -364,6 +395,16 @@ public final class TransitionRequestInfo implements Parcelable { } /** + * If non-null, the task containing the pip activity that participates in this + * transition. + */ + @DataClass.Generated.Member + public @android.annotation.NonNull TransitionRequestInfo setPipTask(@android.annotation.NonNull ActivityManager.RunningTaskInfo value) { + mPipTask = value; + return this; + } + + /** * If non-null, a remote-transition associated with the source of this transition. */ @DataClass.Generated.Member @@ -392,6 +433,7 @@ public final class TransitionRequestInfo implements Parcelable { return "TransitionRequestInfo { " + "type = " + mType + ", " + "triggerTask = " + mTriggerTask + ", " + + "pipTask = " + mPipTask + ", " + "remoteTransition = " + mRemoteTransition + ", " + "displayChange = " + mDisplayChange + ", " + "flags = " + mFlags + @@ -406,11 +448,13 @@ public final class TransitionRequestInfo implements Parcelable { byte flg = 0; if (mTriggerTask != null) flg |= 0x2; - if (mRemoteTransition != null) flg |= 0x4; - if (mDisplayChange != null) flg |= 0x8; + if (mPipTask != null) flg |= 0x4; + if (mRemoteTransition != null) flg |= 0x8; + if (mDisplayChange != null) flg |= 0x10; dest.writeByte(flg); dest.writeInt(mType); if (mTriggerTask != null) dest.writeTypedObject(mTriggerTask, flags); + if (mPipTask != null) dest.writeTypedObject(mPipTask, flags); if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags); if (mDisplayChange != null) dest.writeTypedObject(mDisplayChange, flags); dest.writeInt(mFlags); @@ -430,14 +474,16 @@ public final class TransitionRequestInfo implements Parcelable { byte flg = in.readByte(); int type = in.readInt(); ActivityManager.RunningTaskInfo triggerTask = (flg & 0x2) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); - RemoteTransition remoteTransition = (flg & 0x4) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR); - TransitionRequestInfo.DisplayChange displayChange = (flg & 0x8) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR); + ActivityManager.RunningTaskInfo pipTask = (flg & 0x4) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); + RemoteTransition remoteTransition = (flg & 0x8) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR); + TransitionRequestInfo.DisplayChange displayChange = (flg & 0x10) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR); int flags = in.readInt(); this.mType = type; com.android.internal.util.AnnotationValidations.validate( WindowManager.TransitionType.class, null, mType); this.mTriggerTask = triggerTask; + this.mPipTask = pipTask; this.mRemoteTransition = remoteTransition; this.mDisplayChange = displayChange; this.mFlags = flags; @@ -460,10 +506,10 @@ public final class TransitionRequestInfo implements Parcelable { }; @DataClass.Generated( - time = 1691627678327L, + time = 1693425051928L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java", - inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") + inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig deleted file mode 100644 index f1d981ac5b4b..000000000000 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ /dev/null @@ -1,8 +0,0 @@ -package: "com.android.window.flags" - -flag { - name: "letterbox_background_wallpaper_flag" - namespace: "large_screen_experiences_app_compat" - description: "Whether the letterbox wallpaper style is enabled by default" - bug: "297195682" -} diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig new file mode 100644 index 000000000000..7a4c5bc669fc --- /dev/null +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -0,0 +1,8 @@ +package: "com.android.window.flags" + +flag { + name: "nav_bar_transparent_by_default" + namespace: "windowing_frontend" + description: "Make nav bar color transparent by default when targeting SDK 35 or greater" + bug: "232195501" +} diff --git a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java index b9f02365bbe7..d2fdc65b2c36 100644 --- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java +++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java @@ -61,6 +61,8 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator private final ModelBuilder mModelBuilder; private ResolverComparatorModel mComparatorModel; + private ResolverAppPredictorCallback mSortingCallback; + // If this is non-null (and this is not destroyed), it means APS is disabled and we should fall // back to using the ResolverRankerService. // TODO: responsibility for this fallback behavior can live outside of the AppPrediction client. @@ -94,6 +96,9 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator // TODO: may not be necessary to build a new model, since we're destroying anyways. mComparatorModel = mModelBuilder.buildFallbackModel(mResolverRankerService); } + if (mSortingCallback != null) { + mSortingCallback.destroy(); + } } @Override @@ -140,22 +145,27 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator .setClassName(target.name.getClassName()) .build()); } + + if (mSortingCallback != null) { + mSortingCallback.destroy(); + } + mSortingCallback = new ResolverAppPredictorCallback(sortedAppTargets -> { + if (sortedAppTargets.isEmpty()) { + Log.i(TAG, "AppPredictionService disabled. Using resolver."); + setupFallbackModel(targets); + } else { + Log.i(TAG, "AppPredictionService response received"); + // Skip sending to Handler which takes extra time to dispatch messages. + // TODO: the Handler guards some concurrency conditions, so this could + // probably result in a race (we're not currently on the Handler thread?). + // We'll leave this as-is since we intend to remove the Handler design + // shortly, but this is still an unsound shortcut. + handleResult(sortedAppTargets); + } + }); + mAppPredictor.sortTargets(appTargets, Executors.newSingleThreadExecutor(), - sortedAppTargets -> { - if (sortedAppTargets.isEmpty()) { - Log.i(TAG, "AppPredictionService disabled. Using resolver."); - setupFallbackModel(targets); - } else { - Log.i(TAG, "AppPredictionService response received"); - // Skip sending to Handler which takes extra time to dispatch messages. - // TODO: the Handler guards some concurrency conditions, so this could - // probably result in a race (we're not currently on the Handler thread?). - // We'll leave this as-is since we intend to remove the Handler design - // shortly, but this is still an unsound shortcut. - handleResult(sortedAppTargets); - } - } - ); + mSortingCallback.asConsumer()); } private void setupFallbackModel(List<ResolvedComponentInfo> targets) { diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 2b39bb4eb7a5..7e2c0179b327 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -24,9 +24,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROS import static android.content.ContentProvider.getUserIdFromUri; import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL; import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK; - import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET; - import static java.lang.annotation.RetentionPolicy.SOURCE; import android.animation.Animator; @@ -777,9 +775,9 @@ public class ChooserActivity extends ResolverActivity implements return appPredictor; } - private AppPredictor.Callback createAppPredictorCallback( + private ResolverAppPredictorCallback createAppPredictorCallback( ChooserListAdapter chooserListAdapter) { - return resultList -> { + return new ResolverAppPredictorCallback(resultList -> { if (isFinishing() || isDestroyed()) { return; } @@ -811,7 +809,7 @@ public class ChooserActivity extends ResolverActivity implements } sendShareShortcutInfoList(shareShortcutInfos, chooserListAdapter, resultList, chooserListAdapter.getUserHandle()); - }; + }); } static SharedPreferences getPinnedSharedPrefs(Context context) { @@ -2559,10 +2557,13 @@ public class ChooserActivity extends ResolverActivity implements boolean filterLastUsed, UserHandle userHandle) { ChooserListAdapter chooserListAdapter = createChooserListAdapter(context, payloadIntents, initialIntents, rList, filterLastUsed, userHandle); - AppPredictor.Callback appPredictorCallback = createAppPredictorCallback(chooserListAdapter); + ResolverAppPredictorCallback appPredictorCallbackWrapper = + createAppPredictorCallback(chooserListAdapter); + AppPredictor.Callback appPredictorCallback = appPredictorCallbackWrapper.asCallback(); AppPredictor appPredictor = setupAppPredictorForUser(userHandle, appPredictorCallback); chooserListAdapter.setAppPredictor(appPredictor); - chooserListAdapter.setAppPredictorCallback(appPredictorCallback); + chooserListAdapter.setAppPredictorCallback( + appPredictorCallback, appPredictorCallbackWrapper); return new ChooserGridAdapter(chooserListAdapter); } @@ -3023,28 +3024,31 @@ public class ChooserActivity extends ResolverActivity implements return shouldShowTabs() && (mMultiProfilePagerAdapter.getListAdapterForUserHandle( UserHandle.of(UserHandle.myUserId())).getCount() > 0 - || shouldShowContentPreviewWhenEmpty()) + || shouldShowStickyContentPreviewWhenEmpty()) && shouldShowContentPreview(); } /** - * This method could be used to override the default behavior when we hide the preview area - * when the current tab doesn't have any items. + * This method could be used to override the default behavior when we hide the sticky preview + * area when the current tab doesn't have any items. * - * @return true if we want to show the content preview area even if the tab for the current - * user is empty + * @return {@code true} if we want to show the sticky content preview area even if the tab for + * the current user is empty */ - protected boolean shouldShowContentPreviewWhenEmpty() { + protected boolean shouldShowStickyContentPreviewWhenEmpty() { return false; } - /** - * @return true if we want to show the content preview area - */ - protected boolean shouldShowContentPreview() { + @Override + public boolean shouldShowContentPreview() { return isSendAction(getTargetIntent()); } + @Override + public boolean shouldShowServiceTargets() { + return shouldShowContentPreview() && !ActivityManager.isLowRamDeviceStatic(); + } + private void updateStickyContentPreview() { if (shouldShowStickyContentPreviewNoOrientationCheck()) { // The sticky content preview is only shown when we show the work and personal tabs. @@ -3406,11 +3410,7 @@ public class ChooserActivity extends ResolverActivity implements // There can be at most one row in the listview, that is internally // a ViewGroup with 2 rows public int getServiceTargetRowCount() { - if (shouldShowContentPreview() - && !ActivityManager.isLowRamDeviceStatic()) { - return 1; - } - return 0; + return shouldShowServiceTargets() ? 1 : 0; } public int getAzLabelRowCount() { diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index 1eecb413adcb..b3e828d15737 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -19,7 +19,6 @@ package com.android.internal.app; import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE; import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER; -import android.app.ActivityManager; import android.app.prediction.AppPredictor; import android.content.ComponentName; import android.content.Context; @@ -103,6 +102,7 @@ public class ChooserListAdapter extends ResolverListAdapter { // Sorted list of DisplayResolveInfos for the alphabetical app section. private List<DisplayResolveInfo> mSortedList = new ArrayList<>(); private AppPredictor mAppPredictor; + private ResolverAppPredictorCallback mAppPredictorCallbackWrapper; private AppPredictor.Callback mAppPredictorCallback; // Represents the UserSpace in which the Initial Intents should be resolved. @@ -425,11 +425,9 @@ public class ChooserListAdapter extends ResolverListAdapter { } public int getServiceTargetCount() { - if (mChooserListCommunicator.isSendAction(mChooserListCommunicator.getTargetIntent()) - && !ActivityManager.isLowRamDeviceStatic()) { + if (mChooserListCommunicator.shouldShowServiceTargets()) { return Math.min(mServiceTargets.size(), mChooserListCommunicator.getMaxRankedTargets()); } - return 0; } @@ -747,8 +745,11 @@ public class ChooserListAdapter extends ResolverListAdapter { mAppPredictor = appPredictor; } - public void setAppPredictorCallback(AppPredictor.Callback appPredictorCallback) { + public void setAppPredictorCallback( + AppPredictor.Callback appPredictorCallback, + ResolverAppPredictorCallback appPredictorCallbackWrapper) { mAppPredictorCallback = appPredictorCallback; + mAppPredictorCallbackWrapper = appPredictorCallbackWrapper; } public void destroyAppPredictor() { @@ -757,6 +758,10 @@ public class ChooserListAdapter extends ResolverListAdapter { getAppPredictor().destroy(); setAppPredictor(null); } + + if (mAppPredictorCallbackWrapper != null) { + mAppPredictorCallbackWrapper.destroy(); + } } /** @@ -771,6 +776,10 @@ public class ChooserListAdapter extends ResolverListAdapter { void sendListViewUpdateMessage(UserHandle userHandle); boolean isSendAction(Intent targetIntent); + + boolean shouldShowContentPreview(); + + boolean shouldShowServiceTargets(); } /** diff --git a/core/java/com/android/internal/app/ResolverAppPredictorCallback.java b/core/java/com/android/internal/app/ResolverAppPredictorCallback.java new file mode 100644 index 000000000000..c35e536275f0 --- /dev/null +++ b/core/java/com/android/internal/app/ResolverAppPredictorCallback.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.app; + +import android.app.prediction.AppPredictor; +import android.app.prediction.AppTarget; + +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; + +/** + * Callback wrapper that works around potential memory leaks in app predictor. + * + * Nulls the callback itself when destroyed, so at worst you'll leak just this object. + */ +public class ResolverAppPredictorCallback { + private volatile Consumer<List<AppTarget>> mCallback; + + public ResolverAppPredictorCallback(Consumer<List<AppTarget>> callback) { + mCallback = callback; + } + + private void notifyCallback(List<AppTarget> list) { + Consumer<List<AppTarget>> callback = mCallback; + if (callback != null) { + callback.accept(Objects.requireNonNullElseGet(list, List::of)); + } + } + + public Consumer<List<AppTarget>> asConsumer() { + return this::notifyCallback; + } + + public AppPredictor.Callback asCallback() { + return this::notifyCallback; + } + + public void destroy() { + mCallback = null; + } +} diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 3e16df4d7f67..1be916f44f5b 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -1160,7 +1160,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mForceWindowDrawsBarBackgrounds, requestedVisibleTypes); boolean oldDrawLegacy = mDrawLegacyNavigationBarBackground; mDrawLegacyNavigationBarBackground = - (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0; + ((requestedVisibleTypes | mLastForceConsumingTypes) + & WindowInsets.Type.navigationBars()) != 0 + && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0; if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) { mDrawLegacyNavigationBarBackgroundHandled = mWindow.onDrawLegacyNavigationBarBackgroundChanged( diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index 483a184a7ba2..8f4df806296e 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -1304,8 +1304,9 @@ public class TransitionAnimation { t.setBuffer(layer, buffer.getHardwareBuffer()); t.setDataSpace(layer, buffer.getColorSpace().getDataSpace()); // Avoid showing dimming effect for HDR content when running animations. - // TODO(b/298219334): Only do this if we know we already dimmed in the screenshot - t.setDimmingEnabled(layer, false); + if (buffer.containsHdrLayers()) { + t.setDimmingEnabled(layer, false); + } } /** Returns whether the hardware buffer passed in is marked as protected. */ diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java index f974d9d10efd..21c3e7b5c6b8 100644 --- a/core/java/com/android/internal/usb/DumpUtils.java +++ b/core/java/com/android/internal/usb/DumpUtils.java @@ -16,6 +16,7 @@ package com.android.internal.usb; +import static android.hardware.usb.UsbPort.FLAG_ALT_MODE_TYPE_DISPLAYPORT; import static android.hardware.usb.UsbPortStatus.MODE_AUDIO_ACCESSORY; import static android.hardware.usb.UsbPortStatus.MODE_DEBUG_ACCESSORY; import static android.hardware.usb.UsbPortStatus.MODE_DFP; @@ -26,6 +27,7 @@ import static android.hardware.usb.UsbPortStatus.MODE_UFP; import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull; import android.annotation.NonNull; +import android.hardware.usb.DisplayPortAltModeInfo; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbConfiguration; import android.hardware.usb.UsbDevice; @@ -177,6 +179,10 @@ public class DumpUtils { dump.write("supports_compliance_warnings", UsbPortProto.SUPPORTS_COMPLIANCE_WARNINGS, port.supportsComplianceWarnings()); + if (port.isAltModeSupported(FLAG_ALT_MODE_TYPE_DISPLAYPORT)) { + dump.write("supported_alt_modes", UsbPortProto.SUPPORTED_ALT_MODES, + FLAG_ALT_MODE_TYPE_DISPLAYPORT); + } dump.end(token); } @@ -255,6 +261,12 @@ public class DumpUtils { UsbPort.powerBrickConnectionStatusToString(status.getPowerBrickConnectionStatus())); dump.write("compliance_warning_status", UsbPortStatusProto.COMPLIANCE_WARNINGS_STRING, UsbPort.complianceWarningsToString(status.getComplianceWarnings())); + DisplayPortAltModeInfo displayPortAltModeInfo = status.getDisplayPortAltModeInfo(); + if (displayPortAltModeInfo != null) { + dump.write("displayport_alt_mode_status", + UsbPortStatusProto.DISPLAYPORT_ALT_MODE_STATUS, + status.getDisplayPortAltModeInfo().toString()); + } dump.end(token); } } diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index fe957624cf4b..e0bcef642d82 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -565,6 +565,51 @@ static jlong android_os_Debug_getPss(JNIEnv *env, jobject clazz) return android_os_Debug_getPssPid(env, clazz, getpid(), NULL, NULL); } +static jlong android_os_Debug_getRssPid(JNIEnv* env, jobject clazz, jint pid, + jlongArray outMemtrack) { + jlong rss = 0; + jlong memtrack = 0; + + struct graphics_memory_pss graphics_mem; + if (read_memtrack_memory(pid, &graphics_mem) == 0) { + rss = memtrack = graphics_mem.graphics + graphics_mem.gl + graphics_mem.other; + } + + ::android::meminfo::ProcMemInfo proc_mem(pid); + uint64_t status_rss; + if (proc_mem.StatusVmRSS(&status_rss)) { + rss += status_rss; + } else { + return 0; + } + + if (outMemtrack != NULL) { + int outLen = env->GetArrayLength(outMemtrack); + if (outLen >= 1) { + jlong* outMemtrackArray = env->GetLongArrayElements(outMemtrack, 0); + if (outMemtrackArray != NULL) { + outMemtrackArray[0] = memtrack; + if (outLen >= 2) { + outMemtrackArray[1] = graphics_mem.graphics; + } + if (outLen >= 3) { + outMemtrackArray[2] = graphics_mem.gl; + } + if (outLen >= 4) { + outMemtrackArray[3] = graphics_mem.other; + } + } + env->ReleaseLongArrayElements(outMemtrack, outMemtrackArray, 0); + } + } + + return rss; +} + +static jlong android_os_Debug_getRss(JNIEnv* env, jobject clazz) { + return android_os_Debug_getRssPid(env, clazz, getpid(), NULL); +} + // The 1:1 mapping of MEMINFO_* enums here must match with the constants from // Debug.java. enum { @@ -974,62 +1019,43 @@ static jboolean android_os_Debug_isVmapStack(JNIEnv *env, jobject clazz) */ static const JNINativeMethod gMethods[] = { - { "getNativeHeapSize", "()J", - (void*) android_os_Debug_getNativeHeapSize }, - { "getNativeHeapAllocatedSize", "()J", - (void*) android_os_Debug_getNativeHeapAllocatedSize }, - { "getNativeHeapFreeSize", "()J", - (void*) android_os_Debug_getNativeHeapFreeSize }, - { "getMemoryInfo", "(Landroid/os/Debug$MemoryInfo;)V", - (void*) android_os_Debug_getDirtyPages }, - { "getMemoryInfo", "(ILandroid/os/Debug$MemoryInfo;)Z", - (void*) android_os_Debug_getDirtyPagesPid }, - { "getPss", "()J", - (void*) android_os_Debug_getPss }, - { "getPss", "(I[J[J)J", - (void*) android_os_Debug_getPssPid }, - { "getMemInfo", "([J)V", - (void*) android_os_Debug_getMemInfo }, - { "dumpNativeHeap", "(Ljava/io/FileDescriptor;)V", - (void*) android_os_Debug_dumpNativeHeap }, - { "dumpNativeMallocInfo", "(Ljava/io/FileDescriptor;)V", - (void*) android_os_Debug_dumpNativeMallocInfo }, - { "getBinderSentTransactions", "()I", - (void*) android_os_Debug_getBinderSentTransactions }, - { "getBinderReceivedTransactions", "()I", - (void*) android_os_getBinderReceivedTransactions }, - { "getBinderLocalObjectCount", "()I", - (void*)android_os_Debug_getLocalObjectCount }, - { "getBinderProxyObjectCount", "()I", - (void*)android_os_Debug_getProxyObjectCount }, - { "getBinderDeathObjectCount", "()I", - (void*)android_os_Debug_getDeathObjectCount }, - { "dumpJavaBacktraceToFileTimeout", "(ILjava/lang/String;I)Z", - (void*)android_os_Debug_dumpJavaBacktraceToFileTimeout }, - { "dumpNativeBacktraceToFileTimeout", "(ILjava/lang/String;I)Z", - (void*)android_os_Debug_dumpNativeBacktraceToFileTimeout }, - { "getUnreachableMemory", "(IZ)Ljava/lang/String;", - (void*)android_os_Debug_getUnreachableMemory }, - { "getZramFreeKb", "()J", - (void*)android_os_Debug_getFreeZramKb }, - { "getIonHeapsSizeKb", "()J", - (void*)android_os_Debug_getIonHeapsSizeKb }, - { "getDmabufTotalExportedKb", "()J", - (void*)android_os_Debug_getDmabufTotalExportedKb }, - { "getGpuPrivateMemoryKb", "()J", - (void*)android_os_Debug_getGpuPrivateMemoryKb }, - { "getDmabufHeapTotalExportedKb", "()J", - (void*)android_os_Debug_getDmabufHeapTotalExportedKb }, - { "getIonPoolsSizeKb", "()J", - (void*)android_os_Debug_getIonPoolsSizeKb }, - { "getDmabufMappedSizeKb", "()J", - (void*)android_os_Debug_getDmabufMappedSizeKb }, - { "getDmabufHeapPoolsSizeKb", "()J", - (void*)android_os_Debug_getDmabufHeapPoolsSizeKb }, - { "getGpuTotalUsageKb", "()J", - (void*)android_os_Debug_getGpuTotalUsageKb }, - { "isVmapStack", "()Z", - (void*)android_os_Debug_isVmapStack }, + {"getNativeHeapSize", "()J", (void*)android_os_Debug_getNativeHeapSize}, + {"getNativeHeapAllocatedSize", "()J", (void*)android_os_Debug_getNativeHeapAllocatedSize}, + {"getNativeHeapFreeSize", "()J", (void*)android_os_Debug_getNativeHeapFreeSize}, + {"getMemoryInfo", "(Landroid/os/Debug$MemoryInfo;)V", + (void*)android_os_Debug_getDirtyPages}, + {"getMemoryInfo", "(ILandroid/os/Debug$MemoryInfo;)Z", + (void*)android_os_Debug_getDirtyPagesPid}, + {"getPss", "()J", (void*)android_os_Debug_getPss}, + {"getPss", "(I[J[J)J", (void*)android_os_Debug_getPssPid}, + {"getRss", "()J", (void*)android_os_Debug_getRss}, + {"getRss", "(I[J)J", (void*)android_os_Debug_getRssPid}, + {"getMemInfo", "([J)V", (void*)android_os_Debug_getMemInfo}, + {"dumpNativeHeap", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Debug_dumpNativeHeap}, + {"dumpNativeMallocInfo", "(Ljava/io/FileDescriptor;)V", + (void*)android_os_Debug_dumpNativeMallocInfo}, + {"getBinderSentTransactions", "()I", (void*)android_os_Debug_getBinderSentTransactions}, + {"getBinderReceivedTransactions", "()I", (void*)android_os_getBinderReceivedTransactions}, + {"getBinderLocalObjectCount", "()I", (void*)android_os_Debug_getLocalObjectCount}, + {"getBinderProxyObjectCount", "()I", (void*)android_os_Debug_getProxyObjectCount}, + {"getBinderDeathObjectCount", "()I", (void*)android_os_Debug_getDeathObjectCount}, + {"dumpJavaBacktraceToFileTimeout", "(ILjava/lang/String;I)Z", + (void*)android_os_Debug_dumpJavaBacktraceToFileTimeout}, + {"dumpNativeBacktraceToFileTimeout", "(ILjava/lang/String;I)Z", + (void*)android_os_Debug_dumpNativeBacktraceToFileTimeout}, + {"getUnreachableMemory", "(IZ)Ljava/lang/String;", + (void*)android_os_Debug_getUnreachableMemory}, + {"getZramFreeKb", "()J", (void*)android_os_Debug_getFreeZramKb}, + {"getIonHeapsSizeKb", "()J", (void*)android_os_Debug_getIonHeapsSizeKb}, + {"getDmabufTotalExportedKb", "()J", (void*)android_os_Debug_getDmabufTotalExportedKb}, + {"getGpuPrivateMemoryKb", "()J", (void*)android_os_Debug_getGpuPrivateMemoryKb}, + {"getDmabufHeapTotalExportedKb", "()J", + (void*)android_os_Debug_getDmabufHeapTotalExportedKb}, + {"getIonPoolsSizeKb", "()J", (void*)android_os_Debug_getIonPoolsSizeKb}, + {"getDmabufMappedSizeKb", "()J", (void*)android_os_Debug_getDmabufMappedSizeKb}, + {"getDmabufHeapPoolsSizeKb", "()J", (void*)android_os_Debug_getDmabufHeapPoolsSizeKb}, + {"getGpuTotalUsageKb", "()J", (void*)android_os_Debug_getGpuTotalUsageKb}, + {"isVmapStack", "()Z", (void*)android_os_Debug_isVmapStack}, }; int register_android_os_Debug(JNIEnv *env) diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp index ddaeb5a4d272..7f69e22fb0d1 100644 --- a/core/jni/android_view_KeyCharacterMap.cpp +++ b/core/jni/android_view_KeyCharacterMap.cpp @@ -240,6 +240,36 @@ static jboolean nativeEquals(JNIEnv* env, jobject clazz, jlong ptr1, jlong ptr2) return static_cast<jboolean>(*map1 == *map2); } +static void nativeApplyOverlay(JNIEnv* env, jobject clazz, jlong ptr, jstring nameObj, + jstring overlayObj) { + NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr); + if (!map || !map->getMap()) { + return; + } + ScopedUtfChars nameChars(env, nameObj); + ScopedUtfChars overlayChars(env, overlayObj); + base::Result<std::shared_ptr<KeyCharacterMap>> ret = + KeyCharacterMap::loadContents(nameChars.c_str(), overlayChars.c_str(), + KeyCharacterMap::Format::OVERLAY); + if (ret.ok()) { + std::shared_ptr<KeyCharacterMap> overlay = *ret; + map->getMap()->combine(*overlay); + } +} + +static jint nativeGetMappedKey(JNIEnv* env, jobject clazz, jlong ptr, jint scanCode) { + NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr); + if (!map || !map->getMap()) { + return 0; + } + int32_t outKeyCode; + status_t mapKeyRes = map->getMap()->mapKey(scanCode, /*usageCode=*/0, &outKeyCode); + if (mapKeyRes != OK) { + return 0; + } + return static_cast<jint>(outKeyCode); +} + /* * JNI registration. */ @@ -260,7 +290,9 @@ static const JNINativeMethod g_methods[] = { {"nativeObtainEmptyKeyCharacterMap", "(I)Landroid/view/KeyCharacterMap;", (void*)nativeObtainEmptyKeyCharacterMap}, {"nativeEquals", "(JJ)Z", (void*)nativeEquals}, -}; + {"nativeApplyOverlay", "(JLjava/lang/String;Ljava/lang/String;)V", + (void*)nativeApplyOverlay}, + {"nativeGetMappedKey", "(JI)I", (void*)nativeGetMappedKey}}; int register_android_view_KeyCharacterMap(JNIEnv* env) { diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto index 279a5d0c17f8..b9905e8cf446 100644 --- a/core/proto/android/content/package_item_info.proto +++ b/core/proto/android/content/package_item_info.proto @@ -30,6 +30,7 @@ message PackageItemInfoProto { optional string non_localized_label = 4; optional int32 icon = 5; optional int32 banner = 6; + optional bool is_archived = 7; } // Proto of android.content.pm.ApplicationInfo which extends PackageItemInfo diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto index 88893205c79e..49b8450fcc67 100644 --- a/core/proto/android/service/usb.proto +++ b/core/proto/android/service/usb.proto @@ -238,10 +238,15 @@ message UsbPortProto { MODE_DEBUG_ACCESSORY = 8; } + enum AltMode { + ALT_MODE_DISPLAYPORT = 1; + } + // ID of the port. A device (eg: Chromebooks) might have multiple ports. optional string id = 1; repeated Mode supported_modes = 2; optional bool supports_compliance_warnings = 3; + repeated AltMode supported_alt_modes = 4; } message UsbPortStatusProto { @@ -271,6 +276,7 @@ message UsbPortStatusProto { optional bool is_power_transfer_limited = 8; optional string usb_power_brick_status = 9; optional string compliance_warnings_string = 10; + optional string displayport_alt_mode_status = 11; } message UsbPortStatusRoleCombinationProto { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 3180ffbef79d..c09f0a3ed624 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1699,6 +1699,15 @@ android:description="@string/permdesc_cameraOpenCloseListener" android:protectionLevel="signature" /> + <!-- @SystemApi Allows camera access by Headless System User 0 when device is running in + HSUM Mode. + @hide --> + <permission android:name="android.permission.CAMERA_HEADLESS_SYSTEM_USER" + android:permissionGroup="android.permission-group.UNDEFINED" + android:label="@string/permlab_cameraHeadlessSystemUser" + android:description="@string/permdesc_cameraHeadlessSystemUser" + android:protectionLevel="signature" /> + <!-- ====================================================================== --> <!-- Permissions for accessing the device sensors --> <!-- ====================================================================== --> @@ -7654,6 +7663,10 @@ <permission android:name="android.permission.DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA" android:protectionLevel="signature" /> + <!-- @hide @TestApi Allows tests running in CTS-in-sandbox mode to launch activities --> + <permission android:name="android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX" + android:protectionLevel="signature" /> + <!-- @SystemApi Allows the holder to call health connect migration APIs. @hide --> <permission android:name="android.permission.MIGRATE_HEALTH_CONNECT_DATA" diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml index 5319762c27d1..4027f5c78035 100644 --- a/core/res/res/values-watch/config.xml +++ b/core/res/res/values-watch/config.xml @@ -81,4 +81,9 @@ <!-- Floating windows can be fullscreen (i.e. windowIsFloating can still have fullscreen window that does not wrap content). --> <bool name="config_allowFloatingWindowsFillScreen">true</bool> + + <!-- Whether scroll haptic feedback is enabled for rotary encoder scrolls on + {@link MotionEvent#AXIS_SCROLL} generated by {@link InputDevice#SOURCE_ROTARY_ENCODER} + devices. --> + <bool name="config_viewRotaryEncoderHapticScrollFedbackEnabled">true</bool> </resources> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 6bb87f3c4e99..9bf3ce4268dd 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -151,6 +151,27 @@ <integer name="config_timeout_to_receive_delivered_ack_millis">300000</integer> <java-symbol type="integer" name="config_timeout_to_receive_delivered_ack_millis" /> + <!-- The time duration in millis that the satellite will stay at listening state to wait for the + next incoming page before disabling listening and moving to IDLE state. This timeout + duration is used when transitioning from sending state to listening state. + --> + <integer name="config_satellite_stay_at_listening_from_sending_millis">180000</integer> + <java-symbol type="integer" name="config_satellite_stay_at_listening_from_sending_millis" /> + + <!-- The time duration in millis that the satellite will stay at listening state to wait for the + next incoming page before disabling listening and moving to IDLE state. This timeout + duration is used when transitioning from receiving state to listening state. + --> + <integer name="config_satellite_stay_at_listening_from_receiving_millis">30000</integer> + <java-symbol type="integer" name="config_satellite_stay_at_listening_from_receiving_millis" /> + + <!-- The time duration in millis after which cellular scanning will be enabled and satellite + will move to IDLE state. This timeout duration is used for satellite with NB IOT radio + technologies. + --> + <integer name="config_satellite_nb_iot_inactivity_timeout_millis">180000</integer> + <java-symbol type="integer" name="config_satellite_nb_iot_inactivity_timeout_millis" /> + <!-- Telephony config for services supported by satellite providers. The format of each config string in the array is as follows: "PLMN_1:service_1,service_2,..." where PLMN is the satellite PLMN of a provider and service is an integer with the diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 591e505a792f..fac6aac1db16 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1444,6 +1444,11 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] --> <string name="permdesc_cameraOpenCloseListener">This app can receive callbacks when any camera device is being opened (by what application) or closed.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] --> + <string name="permlab_cameraHeadlessSystemUser">Allow an application or service to access camera as Headless System User.</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] --> + <string name="permdesc_cameraHeadlessSystemUser">This app can access camera as Headless System User.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_vibrate">control vibration</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java index 89b91cf27b38..14f268a91abf 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java @@ -89,9 +89,6 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { private static final ProgramSelector.Identifier TEST_DAB_FREQUENCY_ID = new ProgramSelector.Identifier( ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, TEST_DAB_FREQUENCY_VALUE); - private static final ProgramSelector.Identifier TEST_FM_FREQUENCY_ID = - new ProgramSelector.Identifier( - ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, TEST_FM_FREQUENCY_VALUE); private static final ProgramSelector.Identifier TEST_VENDOR_ID = new ProgramSelector.Identifier( ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, TEST_VENDOR_ID_VALUE); @@ -251,6 +248,20 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { } @Test + public void identifierToHalProgramIdentifier_withDeprecateDabId() { + long value = 0x98765ABCDL; + ProgramSelector.Identifier dabId = new ProgramSelector.Identifier( + ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, value); + ProgramIdentifier halDabIdExpected = AidlTestUtils.makeHalIdentifier( + IdentifierType.DAB_SID_EXT, 0x987650000ABCDL); + + ProgramIdentifier halDabId = ConversionUtils.identifierToHalProgramIdentifier(dabId); + + expect.withMessage("Converted 28-bit DAB identifier for HAL").that(halDabId) + .isEqualTo(halDabIdExpected); + } + + @Test public void identifierFromHalProgramIdentifier_withDabId() { ProgramSelector.Identifier dabId = ConversionUtils.identifierFromHalProgramIdentifier(TEST_HAL_DAB_SID_EXT_ID); diff --git a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java index 5dbeac2f32e9..407c6c3e2e2c 100644 --- a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java +++ b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java @@ -26,6 +26,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.List; + @RunWith(AndroidJUnit4.class) @SmallTest public class BroadcastReceiverTests { @@ -47,15 +50,22 @@ public class BroadcastReceiverTests { @Test public void testReceiverLimit() { final IntentFilter mockFilter = new IntentFilter("android.content.tests.TestAction"); + final List<EmptyReceiver> receivers = new ArrayList<>(RECEIVER_LIMIT_PER_APP); try { for (int i = 0; i < RECEIVER_LIMIT_PER_APP + 1; i++) { - mContext.registerReceiver(new EmptyReceiver(), mockFilter, + final EmptyReceiver receiver = new EmptyReceiver(); + mContext.registerReceiver(receiver, mockFilter, Context.RECEIVER_EXPORTED_UNAUDITED); + receivers.add(receiver); } fail("No exception thrown when registering " + (RECEIVER_LIMIT_PER_APP + 1) + " receivers"); } catch (IllegalStateException ise) { // Expected + } finally { + for (int i = receivers.size() - 1; i >= 0; i--) { + mContext.unregisterReceiver(receivers.remove(i)); + } } } } diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java index 555390231983..37ef6cba8814 100644 --- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java +++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java @@ -85,9 +85,11 @@ public class RegisteredServicesCacheTest extends AndroidTestCase { assertEquals(2, cache.getAllServicesSize(U0)); assertEquals(2, cache.getPersistentServicesSize(U0)); assertNotEmptyFileCreated(cache, U0); + cache.unregisterReceivers(); // Make sure all services can be loaded from xml cache = new TestServicesCache(); assertEquals(2, cache.getPersistentServicesSize(U0)); + cache.unregisterReceivers(); } public void testGetAllServicesReplaceUid() { @@ -110,6 +112,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase { assertTrue("UID must be updated to the new value", uids.contains(SYSTEM_IMAGE_UID)); assertFalse("UID must be updated to the new value", uids.contains(UID2)); + cache.unregisterReceivers(); } public void testGetAllServicesServiceRemoved() { @@ -118,6 +121,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase { cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2)); assertEquals(2, cache.getAllServicesSize(U0)); assertEquals(2, cache.getPersistentServicesSize(U0)); + cache.unregisterReceivers(); // Re-read data from disk and verify services were saved cache = new TestServicesCache(); assertEquals(2, cache.getPersistentServicesSize(U0)); @@ -125,6 +129,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase { cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1)); assertEquals(1, cache.getAllServicesSize(U0)); assertEquals(1, cache.getPersistentServicesSize(U0)); + cache.unregisterReceivers(); } public void testGetAllServicesMultiUser() { @@ -137,12 +142,14 @@ public class RegisteredServicesCacheTest extends AndroidTestCase { assertEquals(1, cache.getAllServicesSize(U1)); assertEquals(1, cache.getPersistentServicesSize(U1)); assertEquals("No services should be available for user 3", 0, cache.getAllServicesSize(3)); + cache.unregisterReceivers(); // Re-read data from disk and verify services were saved cache = new TestServicesCache(); assertEquals(1, cache.getPersistentServicesSize(U0)); assertEquals(1, cache.getPersistentServicesSize(U1)); assertNotEmptyFileCreated(cache, U0); assertNotEmptyFileCreated(cache, U1); + cache.unregisterReceivers(); } public void testOnRemove() { @@ -158,6 +165,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase { cache.clearServicesForQuerying(); assertEquals(1, cache.getAllServicesSize(U0)); assertEquals(0, cache.getAllServicesSize(U1)); + cache.unregisterReceivers(); } public void testMigration() { @@ -186,10 +194,12 @@ public class RegisteredServicesCacheTest extends AndroidTestCase { cache.addServiceForQuerying(0, r2, newServiceInfo(t2, 2)); assertEquals(2, cache.getAllServicesSize(u0)); assertEquals(0, cache.getAllServicesSize(u1)); + cache.unregisterReceivers(); // Re-read data from disk. Verify that services were saved and old file was ignored cache = new TestServicesCache(); assertEquals(2, cache.getPersistentServicesSize(u0)); assertEquals(0, cache.getPersistentServicesSize(u1)); + cache.unregisterReceivers(); } private static RegisteredServicesCache.ServiceInfo<TestServiceType> newServiceInfo( diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java index 9840e15c5975..0e1a6b7e7daa 100644 --- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java +++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java @@ -71,6 +71,7 @@ public class DeviceConfigTest { deleteViaContentProvider(NAMESPACE, KEY2); deleteViaContentProvider(NAMESPACE, KEY3); DeviceConfig.clearAllLocalOverrides(); + DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE); } /** diff --git a/core/tests/coretests/src/android/service/quicksettings/OWNERS b/core/tests/coretests/src/android/service/quicksettings/OWNERS new file mode 100644 index 000000000000..5665490ac3bd --- /dev/null +++ b/core/tests/coretests/src/android/service/quicksettings/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/core/java/android/service/quicksettings/OWNERS diff --git a/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java b/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java index d28eeffae742..322a5532cc4b 100644 --- a/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java +++ b/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java @@ -34,6 +34,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -88,6 +89,8 @@ public class TileServiceTest { verify(mIQSService, never()).onStartSuccessful(any()); } + // TODO(b/298075609): Test is disabled due to flakiness. + @Ignore @Test public void testBindSuccessful() throws RemoteException { Intent intent = new Intent(); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt index 2eeaf53ef146..1c9f04a299cc 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt +++ b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt @@ -19,8 +19,12 @@ package com.android.internal.app import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.ResolveInfo +import android.content.pm.ShortcutInfo +import android.graphics.drawable.Icon import android.os.Bundle import android.os.UserHandle import android.service.chooser.ChooserTarget @@ -32,12 +36,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.android.internal.R import com.android.internal.app.ChooserListAdapter.LoadDirectShareIconTask +import com.android.internal.app.chooser.DisplayResolveInfo import com.android.internal.app.chooser.SelectableTargetInfo import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator import com.android.internal.app.chooser.TargetInfo import com.android.server.testutils.any import com.android.server.testutils.mock import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.anyInt @@ -46,22 +53,25 @@ import org.mockito.Mockito.verify @RunWith(AndroidJUnit4::class) class ChooserListAdapterTest { - private val packageManager = mock<PackageManager> { - whenever(resolveActivity(any(), anyInt())).thenReturn(mock()) - } + private val packageManager = + mock<PackageManager> { whenever(resolveActivity(any(), anyInt())).thenReturn(mock()) } private val context = InstrumentationRegistry.getInstrumentation().getContext() private val resolverListController = mock<ResolverListController>() - private val chooserListCommunicator = mock<ChooserListAdapter.ChooserListCommunicator> { - whenever(maxRankedTargets).thenReturn(0) - } - private val selectableTargetInfoCommunicator = - mock<SelectableTargetInfoCommunicator> { - whenever(targetIntent).thenReturn(mock()) + private val chooserListCommunicator = + mock<ChooserListAdapter.ChooserListCommunicator> { + whenever(maxRankedTargets).thenReturn(0) } + private val selectableTargetInfoCommunicator = + mock<SelectableTargetInfoCommunicator> { whenever(targetIntent).thenReturn(mock()) } private val chooserActivityLogger = mock<ChooserActivityLogger>() + @Before + fun setUp() { + whenever(resolverListController.userHandle).thenReturn(UserHandle.CURRENT) + } + private fun createChooserListAdapter( - taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask + taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask = createTaskProvider() ) = ChooserListAdapterOverride( context, @@ -98,9 +108,8 @@ class ChooserListAdapterTest { view.tag = viewHolderOne val targetInfo = createSelectableTargetInfo() val iconTaskOne = mock<LoadDirectShareIconTask>() - val testTaskProvider = mock<() -> LoadDirectShareIconTask> { - whenever(invoke()).thenReturn(iconTaskOne) - } + val testTaskProvider = + mock<() -> LoadDirectShareIconTask> { whenever(invoke()).thenReturn(iconTaskOne) } val testSubject = createChooserListAdapter { testTaskProvider.invoke() } testSubject.testViewBind(view, targetInfo, 0) @@ -114,6 +123,65 @@ class ChooserListAdapterTest { verify(testTaskProvider, times(1)).invoke() } + @Test + fun getServiceTargetCount_shouldNotShowServiceTargets_returnsZero() { + whenever(chooserListCommunicator.shouldShowServiceTargets()).thenReturn(false) + val adapter = createChooserListAdapter() + whenever(chooserListCommunicator.maxRankedTargets).thenReturn(10) + addServiceTargets(adapter, targetCount = 50) + + assertThat(adapter.serviceTargetCount).isEqualTo(0) + } + + private fun createTaskProvider(): (SelectableTargetInfo?) -> LoadDirectShareIconTask { + val iconTaskOne = mock<LoadDirectShareIconTask>() + val testTaskProvider = + mock<() -> LoadDirectShareIconTask> { whenever(invoke()).thenReturn(iconTaskOne) } + return { testTaskProvider.invoke() } + } + + private fun addServiceTargets(adapter: ChooserListAdapter, targetCount: Int) { + val origTarget = + DisplayResolveInfo( + Intent(), + createResolveInfo(), + Intent(), + ResolverListAdapter.ResolveInfoPresentationGetter(context, 200, createResolveInfo()) + ) + val targets = mutableListOf<ChooserTarget>() + for (i in 1..targetCount) { + val score = 1f + val componentName = ComponentName("chooser.list.adapter", "Test$i") + val extras = Bundle() + val icon: Icon? = null + targets += ChooserTarget("Title $i", icon, score, componentName, extras) + } + val directShareToShortcutInfos = mapOf<ChooserTarget, ShortcutInfo>() + adapter.addServiceResults( + origTarget, + targets, + ChooserActivity.TARGET_TYPE_DEFAULT, + directShareToShortcutInfos + ) + } + + private fun createResolveInfo(): ResolveInfo { + val applicationInfo = + ApplicationInfo().apply { + packageName = "chooser.list.adapter" + name = "ChooserListAdapterTestApplication" + } + val activityInfo = + ActivityInfo().apply { + packageName = applicationInfo.packageName + name = "ChooserListAdapterTest" + } + activityInfo.applicationInfo = applicationInfo + val resolveInfo = ResolveInfo() + resolveInfo.activityInfo = activityInfo + return resolveInfo + } + private fun createSelectableTargetInfo(): SelectableTargetInfo = SelectableTargetInfo( context, @@ -125,13 +193,7 @@ class ChooserListAdapterTest { ) private fun createChooserTarget(): ChooserTarget = - ChooserTarget( - "Title", - null, - 1f, - ComponentName("package", "package.Class"), - Bundle() - ) + ChooserTarget("Title", null, 1f, ComponentName("package", "package.Class"), Bundle()) private fun createView(): View { val view = FrameLayout(context) @@ -164,23 +226,23 @@ private class ChooserListAdapterOverride( chooserActivityLogger: ChooserActivityLogger?, initialIntentsUserHandle: UserHandle?, private val taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask -) : ChooserListAdapter( - context, - payloadIntents, - initialIntents, - rList, - filterLastUsed, - resolverListController, - chooserListCommunicator, - selectableTargetInfoCommunicator, - packageManager, - chooserActivityLogger, - initialIntentsUserHandle, -) { +) : + ChooserListAdapter( + context, + payloadIntents, + initialIntents, + rList, + filterLastUsed, + resolverListController, + chooserListCommunicator, + selectableTargetInfoCommunicator, + packageManager, + chooserActivityLogger, + initialIntentsUserHandle, + ) { override fun createLoadDirectShareIconTask( info: SelectableTargetInfo? - ): LoadDirectShareIconTask = - taskProvider.invoke(info) + ): LoadDirectShareIconTask = taskProvider.invoke(info) fun testViewBind(view: View?, info: TargetInfo?, position: Int) { onBindView(view, info, position) diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverAppPredictorCallbackTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverAppPredictorCallbackTest.java new file mode 100644 index 000000000000..4aca854469f2 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/app/ResolverAppPredictorCallbackTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.app; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetId; +import android.os.UserHandle; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; +import java.util.function.Consumer; + +@RunWith(AndroidJUnit4.class) +public class ResolverAppPredictorCallbackTest { + private class Callback implements Consumer<List<AppTarget>> { + public int count = 0; + public List<AppTarget> latest = null; + @Override + public void accept(List<AppTarget> appTargets) { + count++; + latest = appTargets; + } + }; + + @Test + public void testAsConsumer() { + Callback callback = new Callback(); + ResolverAppPredictorCallback wrapped = new ResolverAppPredictorCallback(callback); + assertThat(callback.count).isEqualTo(0); + + List<AppTarget> targets = createAppTargetList(); + wrapped.asConsumer().accept(targets); + + assertThat(callback.count).isEqualTo(1); + assertThat(callback.latest).isEqualTo(targets); + + wrapped.destroy(); + + // Shouldn't do anything: + wrapped.asConsumer().accept(targets); + + assertThat(callback.count).isEqualTo(1); + } + + @Test + public void testAsCallback() { + Callback callback = new Callback(); + ResolverAppPredictorCallback wrapped = new ResolverAppPredictorCallback(callback); + assertThat(callback.count).isEqualTo(0); + + List<AppTarget> targets = createAppTargetList(); + wrapped.asCallback().onTargetsAvailable(targets); + + assertThat(callback.count).isEqualTo(1); + assertThat(callback.latest).isEqualTo(targets); + + wrapped.destroy(); + + // Shouldn't do anything: + wrapped.asConsumer().accept(targets); + + assertThat(callback.count).isEqualTo(1); + } + + @Test + public void testAsConsumer_null() { + Callback callback = new Callback(); + ResolverAppPredictorCallback wrapped = new ResolverAppPredictorCallback(callback); + assertThat(callback.count).isEqualTo(0); + + wrapped.asConsumer().accept(null); + + assertThat(callback.count).isEqualTo(1); + assertThat(callback.latest).isEmpty(); + + wrapped.destroy(); + + // Shouldn't do anything: + wrapped.asConsumer().accept(null); + + assertThat(callback.count).isEqualTo(1); + } + + private List<AppTarget> createAppTargetList() { + AppTarget.Builder builder = new AppTarget.Builder( + new AppTargetId("ID"), "package", UserHandle.CURRENT); + return List.of(builder.build()); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java index e870d6022058..8d825e4deb81 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java @@ -99,6 +99,7 @@ public class BatteryInputSuspendTest { if (isCharging(intent) == mExpectedChargingState) { mReady.open(); } + context.unregisterReceiver(this); } }, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 3206dd2123d5..69aa40156e78 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -235,6 +235,7 @@ applications that come with the platform <privapp-permissions package="com.android.shell"> <!-- Needed for test only --> + <permission name="android.permission.CAMERA_HEADLESS_SYSTEM_USER"/> <permission name="android.permission.MANAGE_HEALTH_DATA"/> <permission name="android.permission.LAUNCH_DEVICE_MANAGER_SETUP"/> <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index ee2eacfd304f..28a4b49e9d00 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -253,12 +253,6 @@ "group": "WM_DEBUG_BOOT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "-1883484959": { - "message": "Content Recording: Display %d state is now (%d), so update recording?", - "level": "VERBOSE", - "group": "WM_DEBUG_CONTENT_RECORDING", - "at": "com\/android\/server\/wm\/DisplayContent.java" - }, "-1872288685": { "message": "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b Callers=%s", "level": "VERBOSE", @@ -445,6 +439,12 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/ResetTargetTaskHelper.java" }, + "-1700778361": { + "message": "Content Recording: Going ahead with updating recording for display %d to new bounds %s and\/or orientation %d and\/or surface size %s", + "level": "VERBOSE", + "group": "WM_DEBUG_CONTENT_RECORDING", + "at": "com\/android\/server\/wm\/ContentRecorder.java" + }, "-1699018375": { "message": "Adding activity %s to task %s callers: %s", "level": "INFO", @@ -649,12 +649,6 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DisplayContent.java" }, - "-1480264178": { - "message": "Content Recording: Unable to update recording for display %d to new bounds %s and\/or orientation %d, since the surface is not available.", - "level": "VERBOSE", - "group": "WM_DEBUG_CONTENT_RECORDING", - "at": "com\/android\/server\/wm\/ContentRecorder.java" - }, "-1478175541": { "message": "No longer animating wallpaper targets!", "level": "VERBOSE", @@ -1855,12 +1849,6 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/Task.java" }, - "-452750194": { - "message": "Content Recording: Going ahead with updating recording for display %d to new bounds %s and\/or orientation %d.", - "level": "VERBOSE", - "group": "WM_DEBUG_CONTENT_RECORDING", - "at": "com\/android\/server\/wm\/ContentRecorder.java" - }, "-451552570": { "message": "Current focused window being animated by recents. Overriding back callback to recents controller callback.", "level": "DEBUG", @@ -2377,6 +2365,12 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimationController.java" }, + "34106798": { + "message": "Content Recording: Display %d state was (%d), is now (%d), so update recording?", + "level": "VERBOSE", + "group": "WM_DEBUG_CONTENT_RECORDING", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "34682671": { "message": "Not moving display (displayId=%d) to top. Top focused displayId=%d. Reason: FLAG_STEAL_TOP_FOCUS_DISABLED", "level": "INFO", @@ -2413,6 +2407,12 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, + "61363198": { + "message": "Auto-PIP allowed, requesting PIP mode via requestStartTransition(): %s, willAutoPip: %b", + "level": "DEBUG", + "group": "WM_DEBUG_STATES", + "at": "com\/android\/server\/wm\/TaskFragment.java" + }, "74885950": { "message": "Waiting for top state to be released by %s", "level": "VERBOSE", @@ -2425,12 +2425,6 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimationController.java" }, - "90764070": { - "message": "Could not report token removal to the window token client.", - "level": "WARN", - "group": "WM_ERROR", - "at": "com\/android\/server\/wm\/WindowContextListenerController.java" - }, "95216706": { "message": "hideIme target: %s ", "level": "DEBUG", @@ -3079,12 +3073,6 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, - "643263584": { - "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop %d x %d for display %d", - "level": "VERBOSE", - "group": "WM_DEBUG_CONTENT_RECORDING", - "at": "com\/android\/server\/wm\/ContentRecorder.java" - }, "644675193": { "message": "Real start recents", "level": "DEBUG", @@ -4105,6 +4093,12 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1687944543": { + "message": "Content Recording: Unable to update recording for display %d to new bounds %s and\/or orientation %d and\/or surface size %s, since the surface is not available.", + "level": "VERBOSE", + "group": "WM_DEBUG_CONTENT_RECORDING", + "at": "com\/android\/server\/wm\/ContentRecorder.java" + }, "1699269281": { "message": "Don't organize or trigger events for untrusted displayId=%d", "level": "WARN", @@ -4333,6 +4327,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "1936800105": { + "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d", + "level": "VERBOSE", + "group": "WM_DEBUG_CONTENT_RECORDING", + "at": "com\/android\/server\/wm\/ContentRecorder.java" + }, "1945495497": { "message": "Focused window didn't have a valid surface drawn.", "level": "DEBUG", @@ -4351,12 +4351,6 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, - "1948483534": { - "message": "Could not report config changes to the window token client.", - "level": "WARN", - "group": "WM_ERROR", - "at": "com\/android\/server\/wm\/WindowContextListenerController.java" - }, "1964565370": { "message": "Starting remote animation", "level": "INFO", diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java index 919a93b8f107..0f3488bbe8d1 100644 --- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java +++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.ServiceManager; import android.os.ServiceSpecificException; +import android.os.StrictMode; import android.security.maintenance.IKeystoreMaintenance; import android.system.keystore2.Domain; import android.system.keystore2.KeyDescriptor; @@ -51,6 +52,7 @@ public class AndroidKeyStoreMaintenance { * @hide */ public static int onUserAdded(@NonNull int userId) { + StrictMode.noteDiskWrite(); try { getService().onUserAdded(userId); return 0; @@ -71,6 +73,7 @@ public class AndroidKeyStoreMaintenance { * @hide */ public static int onUserRemoved(int userId) { + StrictMode.noteDiskWrite(); try { getService().onUserRemoved(userId); return 0; @@ -93,6 +96,7 @@ public class AndroidKeyStoreMaintenance { * @hide */ public static int onUserPasswordChanged(int userId, @Nullable byte[] password) { + StrictMode.noteDiskWrite(); try { getService().onUserPasswordChanged(userId, password); return 0; @@ -110,6 +114,7 @@ public class AndroidKeyStoreMaintenance { * be cleared. */ public static int clearNamespace(@Domain int domain, long namespace) { + StrictMode.noteDiskWrite(); try { getService().clearNamespace(domain, namespace); return 0; @@ -129,6 +134,7 @@ public class AndroidKeyStoreMaintenance { * @return UserState enum variant as integer if successful or an error */ public static int getState(int userId) { + StrictMode.noteDiskRead(); try { return getService().getState(userId); } catch (ServiceSpecificException e) { @@ -144,6 +150,7 @@ public class AndroidKeyStoreMaintenance { * Informs Keystore 2.0 that an off body event was detected. */ public static void onDeviceOffBody() { + StrictMode.noteDiskWrite(); try { getService().onDeviceOffBody(); } catch (Exception e) { @@ -172,6 +179,7 @@ public class AndroidKeyStoreMaintenance { * * SYSTEM_ERROR if an unexpected error occurred. */ public static int migrateKeyNamespace(KeyDescriptor source, KeyDescriptor destination) { + StrictMode.noteDiskWrite(); try { getService().migrateKeyNamespace(source, destination); return 0; diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java index 00219e7f28ac..2d2dd24763c4 100644 --- a/keystore/java/android/security/Authorization.java +++ b/keystore/java/android/security/Authorization.java @@ -22,6 +22,7 @@ import android.hardware.security.keymint.HardwareAuthToken; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; +import android.os.StrictMode; import android.security.authorization.IKeystoreAuthorization; import android.security.authorization.LockScreenEvent; import android.system.keystore2.ResponseCode; @@ -48,6 +49,7 @@ public class Authorization { * @return 0 if successful or {@code ResponseCode.SYSTEM_ERROR}. */ public static int addAuthToken(@NonNull HardwareAuthToken authToken) { + StrictMode.noteSlowCall("addAuthToken"); try { getService().addAuthToken(authToken); return 0; @@ -81,6 +83,7 @@ public class Authorization { */ public static int onLockScreenEvent(@NonNull boolean locked, @NonNull int userId, @Nullable byte[] syntheticPassword, @Nullable long[] unlockingSids) { + StrictMode.noteDiskWrite(); try { if (locked) { getService().onLockScreenEvent(LockScreenEvent.LOCK, userId, null, unlockingSids); diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 8811a7fec932..8045f55f6b4c 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -18,6 +18,7 @@ package android.security; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; +import android.os.StrictMode; import android.os.UserHandle; import android.security.maintenance.UserState; @@ -126,6 +127,8 @@ public class KeyStore { * a {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode. */ public int addAuthToken(byte[] authToken) { + StrictMode.noteDiskWrite(); + return Authorization.addAuthToken(authToken); } diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java index c83f298046a5..5e16bcee1a0e 100644 --- a/keystore/java/android/security/KeyStore2.java +++ b/keystore/java/android/security/KeyStore2.java @@ -23,6 +23,7 @@ import android.os.Binder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; +import android.os.StrictMode; import android.security.keymaster.KeymasterDefs; import android.system.keystore2.Domain; import android.system.keystore2.IKeystoreService; @@ -148,6 +149,8 @@ public class KeyStore2 { } void delete(KeyDescriptor descriptor) throws KeyStoreException { + StrictMode.noteDiskWrite(); + handleRemoteExceptionWithRetry((service) -> { service.deleteKey(descriptor); return 0; @@ -158,6 +161,8 @@ public class KeyStore2 { * List all entries in the keystore for in the given namespace. */ public KeyDescriptor[] list(int domain, long namespace) throws KeyStoreException { + StrictMode.noteDiskRead(); + return handleRemoteExceptionWithRetry((service) -> service.listEntries(domain, namespace)); } @@ -166,6 +171,8 @@ public class KeyStore2 { */ public KeyDescriptor[] listBatch(int domain, long namespace, String startPastAlias) throws KeyStoreException { + StrictMode.noteDiskRead(); + return handleRemoteExceptionWithRetry( (service) -> service.listEntriesBatched(domain, namespace, startPastAlias)); } @@ -228,6 +235,8 @@ public class KeyStore2 { */ public KeyDescriptor grant(KeyDescriptor descriptor, int granteeUid, int accessVector) throws KeyStoreException { + StrictMode.noteDiskWrite(); + return handleRemoteExceptionWithRetry( (service) -> service.grant(descriptor, granteeUid, accessVector) ); @@ -243,6 +252,8 @@ public class KeyStore2 { */ public void ungrant(KeyDescriptor descriptor, int granteeUid) throws KeyStoreException { + StrictMode.noteDiskWrite(); + handleRemoteExceptionWithRetry((service) -> { service.ungrant(descriptor, granteeUid); return 0; @@ -259,6 +270,8 @@ public class KeyStore2 { */ public KeyEntryResponse getKeyEntry(@NonNull KeyDescriptor descriptor) throws KeyStoreException { + StrictMode.noteDiskRead(); + return handleRemoteExceptionWithRetry((service) -> service.getKeyEntry(descriptor)); } @@ -290,6 +303,8 @@ public class KeyStore2 { */ public void updateSubcomponents(@NonNull KeyDescriptor key, byte[] publicCert, byte[] publicCertChain) throws KeyStoreException { + StrictMode.noteDiskWrite(); + handleRemoteExceptionWithRetry((service) -> { service.updateSubcomponent(key, publicCert, publicCertChain); return 0; @@ -305,6 +320,8 @@ public class KeyStore2 { */ public void deleteKey(@NonNull KeyDescriptor descriptor) throws KeyStoreException { + StrictMode.noteDiskWrite(); + handleRemoteExceptionWithRetry((service) -> { service.deleteKey(descriptor); return 0; @@ -315,6 +332,8 @@ public class KeyStore2 { * Returns the number of Keystore entries for a given domain and namespace. */ public int getNumberOfEntries(int domain, long namespace) throws KeyStoreException { + StrictMode.noteDiskRead(); + return handleRemoteExceptionWithRetry((service) -> service.getNumberOfEntries(domain, namespace)); } diff --git a/keystore/java/android/security/KeyStoreOperation.java b/keystore/java/android/security/KeyStoreOperation.java index 737ff2b4822f..7c9b8eb06764 100644 --- a/keystore/java/android/security/KeyStoreOperation.java +++ b/keystore/java/android/security/KeyStoreOperation.java @@ -21,6 +21,7 @@ import android.hardware.security.keymint.KeyParameter; import android.os.Binder; import android.os.RemoteException; import android.os.ServiceSpecificException; +import android.os.StrictMode; import android.security.keymaster.KeymasterDefs; import android.system.keystore2.IKeystoreOperation; import android.system.keystore2.ResponseCode; @@ -97,6 +98,7 @@ public class KeyStoreOperation { * @throws KeyStoreException */ public void updateAad(@NonNull byte[] input) throws KeyStoreException { + StrictMode.noteSlowCall("updateAad"); handleExceptions(() -> { mOperation.updateAad(input); return 0; @@ -112,6 +114,7 @@ public class KeyStoreOperation { * @hide */ public byte[] update(@NonNull byte[] input) throws KeyStoreException { + StrictMode.noteSlowCall("update"); return handleExceptions(() -> mOperation.update(input)); } @@ -125,6 +128,7 @@ public class KeyStoreOperation { * @hide */ public byte[] finish(byte[] input, byte[] signature) throws KeyStoreException { + StrictMode.noteSlowCall("finish"); return handleExceptions(() -> mOperation.finish(input, signature)); } @@ -135,6 +139,7 @@ public class KeyStoreOperation { * @hide */ public void abort() throws KeyStoreException { + StrictMode.noteSlowCall("abort"); handleExceptions(() -> { mOperation.abort(); return 0; diff --git a/keystore/java/android/security/KeyStoreSecurityLevel.java b/keystore/java/android/security/KeyStoreSecurityLevel.java index 9c0b46c8e87b..6ab148a8b4ea 100644 --- a/keystore/java/android/security/KeyStoreSecurityLevel.java +++ b/keystore/java/android/security/KeyStoreSecurityLevel.java @@ -22,6 +22,7 @@ import android.hardware.security.keymint.KeyParameter; import android.os.Binder; import android.os.RemoteException; import android.os.ServiceSpecificException; +import android.os.StrictMode; import android.security.keystore.BackendBusyException; import android.security.keystore.KeyStoreConnectException; import android.system.keystore2.AuthenticatorSpec; @@ -75,6 +76,7 @@ public class KeyStoreSecurityLevel { */ public KeyStoreOperation createOperation(@NonNull KeyDescriptor keyDescriptor, Collection<KeyParameter> args) throws KeyStoreException { + StrictMode.noteDiskWrite(); while (true) { try { CreateOperationResponse createOperationResponse = @@ -142,6 +144,8 @@ public class KeyStoreSecurityLevel { public KeyMetadata generateKey(@NonNull KeyDescriptor descriptor, KeyDescriptor attestationKey, Collection<KeyParameter> args, int flags, byte[] entropy) throws KeyStoreException { + StrictMode.noteDiskWrite(); + return handleExceptions(() -> mSecurityLevel.generateKey( descriptor, attestationKey, args.toArray(new KeyParameter[args.size()]), flags, entropy)); @@ -163,6 +167,8 @@ public class KeyStoreSecurityLevel { public KeyMetadata importKey(KeyDescriptor descriptor, KeyDescriptor attestationKey, Collection<KeyParameter> args, int flags, byte[] keyData) throws KeyStoreException { + StrictMode.noteDiskWrite(); + return handleExceptions(() -> mSecurityLevel.importKey(descriptor, attestationKey, args.toArray(new KeyParameter[args.size()]), flags, keyData)); } @@ -186,6 +192,7 @@ public class KeyStoreSecurityLevel { @NonNull byte[] wrappedKey, byte[] maskingKey, Collection<KeyParameter> args, @NonNull AuthenticatorSpec[] authenticatorSpecs) throws KeyStoreException { + StrictMode.noteDiskWrite(); KeyDescriptor keyDescriptor = new KeyDescriptor(); keyDescriptor.alias = wrappedKeyDescriptor.alias; keyDescriptor.nspace = wrappedKeyDescriptor.nspace; diff --git a/keystore/java/android/security/LegacyVpnProfileStore.java b/keystore/java/android/security/LegacyVpnProfileStore.java index c85b6b1efd9a..0cc4dfab12f8 100644 --- a/keystore/java/android/security/LegacyVpnProfileStore.java +++ b/keystore/java/android/security/LegacyVpnProfileStore.java @@ -19,6 +19,7 @@ package android.security; import android.annotation.NonNull; import android.os.ServiceManager; import android.os.ServiceSpecificException; +import android.os.StrictMode; import android.security.legacykeystore.ILegacyKeystore; import android.util.Log; @@ -51,6 +52,7 @@ public class LegacyVpnProfileStore { * @hide */ public static boolean put(@NonNull String alias, @NonNull byte[] profile) { + StrictMode.noteDiskWrite(); try { getService().put(alias, ILegacyKeystore.UID_SELF, profile); return true; @@ -70,6 +72,7 @@ public class LegacyVpnProfileStore { * @hide */ public static byte[] get(@NonNull String alias) { + StrictMode.noteDiskRead(); try { return getService().get(alias, ILegacyKeystore.UID_SELF); } catch (ServiceSpecificException e) { @@ -89,6 +92,7 @@ public class LegacyVpnProfileStore { * @hide */ public static boolean remove(@NonNull String alias) { + StrictMode.noteDiskWrite(); try { getService().remove(alias, ILegacyKeystore.UID_SELF); return true; @@ -109,6 +113,7 @@ public class LegacyVpnProfileStore { * @hide */ public static @NonNull String[] list(@NonNull String prefix) { + StrictMode.noteDiskRead(); try { final String[] aliases = getService().list(prefix, ILegacyKeystore.UID_SELF); for (int i = 0; i < aliases.length; ++i) { diff --git a/keystore/java/android/security/SystemKeyStore.java b/keystore/java/android/security/SystemKeyStore.java index e07eaa2e32ed..d481a078ab00 100644 --- a/keystore/java/android/security/SystemKeyStore.java +++ b/keystore/java/android/security/SystemKeyStore.java @@ -18,6 +18,9 @@ package android.security; import android.os.Environment; import android.os.FileUtils; +import android.os.StrictMode; + +import libcore.io.IoUtils; import java.io.File; import java.io.FileOutputStream; @@ -28,8 +31,6 @@ import java.security.SecureRandom; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; -import libcore.io.IoUtils; - /** *@hide */ @@ -69,6 +70,7 @@ public class SystemKeyStore { public byte[] generateNewKey(int numBits, String algName, String keyName) throws NoSuchAlgorithmException { + StrictMode.noteDiskWrite(); // Check if key with similar name exists. If so, return null. File keyFile = getKeyFile(keyName); @@ -103,6 +105,7 @@ public class SystemKeyStore { } private File getKeyFile(String keyName) { + StrictMode.noteDiskWrite(); File sysKeystoreDir = new File(Environment.getDataDirectory(), SYSTEM_KEYSTORE_DIRECTORY); File keyFile = new File(sysKeystoreDir, keyName + KEY_FILE_EXTENSION); @@ -114,6 +117,7 @@ public class SystemKeyStore { } public byte[] retrieveKey(String keyName) throws IOException { + StrictMode.noteDiskRead(); File keyFile = getKeyFile(keyName); if (!keyFile.exists()) { return null; @@ -122,6 +126,7 @@ public class SystemKeyStore { } public void deleteKey(String keyName) { + StrictMode.noteDiskWrite(); // Get the file first. File keyFile = getKeyFile(keyName); diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java index d12989187281..9ac0f6d304f6 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java @@ -20,6 +20,7 @@ import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.security.keymint.KeyParameter; +import android.os.StrictMode; import android.security.KeyStoreException; import android.security.KeyStoreOperation; import android.security.keymaster.KeymasterDefs; @@ -137,6 +138,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor if (!(key instanceof AndroidKeyStorePrivateKey) && (key instanceof PrivateKey || key instanceof PublicKey)) { try { + StrictMode.noteSlowCall("engineInit"); mCipher = Cipher.getInstance(getTransform()); String transform = getTransform(); @@ -203,6 +205,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor if (!(key instanceof AndroidKeyStorePrivateKey) && (key instanceof PrivateKey || key instanceof PublicKey)) { try { + StrictMode.noteSlowCall("engineInit"); mCipher = Cipher.getInstance(getTransform()); mCipher.init(opmode, key, params, random); return; @@ -233,6 +236,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor if (!(key instanceof AndroidKeyStorePrivateKey) && (key instanceof PrivateKey || key instanceof PublicKey)) { try { + StrictMode.noteSlowCall("engineInit"); mCipher = Cipher.getInstance(getTransform()); mCipher.init(opmode, key, params, random); return; @@ -346,6 +350,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor parameters.add(KeyStore2ParameterUtils.makeEnum(KeymasterDefs.KM_TAG_PURPOSE, purpose)); try { + StrictMode.noteDiskRead(); mOperation = mKey.getSecurityLevel().createOperation( mKey.getKeyIdDescriptor(), parameters @@ -521,6 +526,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor @Override protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) { if (mCipher != null) { + StrictMode.noteSlowCall("engineUpdateAAD"); mCipher.updateAAD(input, inputOffset, inputLen); return; } @@ -562,6 +568,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor @Override protected final void engineUpdateAAD(ByteBuffer src) { if (mCipher != null) { + StrictMode.noteSlowCall("engineUpdateAAD"); mCipher.updateAAD(src); return; } @@ -715,6 +722,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor throw new NullPointerException("key == null"); } byte[] encoded = null; + StrictMode.noteSlowCall("engineWrap"); if (key instanceof SecretKey) { if ("RAW".equalsIgnoreCase(key.getFormat())) { encoded = key.getEncoded(); @@ -807,6 +815,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor throw new InvalidKeyException("Failed to unwrap key", e); } + StrictMode.noteSlowCall("engineUnwrap"); switch (wrappedKeyType) { case Cipher.SECRET_KEY: { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java index ace2053cc1a7..9d3fca86903b 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java @@ -195,7 +195,7 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException { if (!ACCEPTED_SIGNING_SCHEMES.contains(key.getAlgorithm().toLowerCase())) { throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm() - + ". Only" + Arrays.toString(ACCEPTED_SIGNING_SCHEMES.stream().toArray()) + + ". Only " + Arrays.toString(ACCEPTED_SIGNING_SCHEMES.stream().toArray()) + " supported"); } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java index 7292cd3c5fb1..66e9f71a1f7b 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java @@ -20,6 +20,7 @@ import android.hardware.security.keymint.Algorithm; import android.hardware.security.keymint.KeyParameter; import android.hardware.security.keymint.KeyPurpose; import android.hardware.security.keymint.Tag; +import android.os.StrictMode; import android.security.KeyStoreException; import android.security.KeyStoreOperation; import android.security.keystore.KeyStoreCryptoOperation; @@ -174,6 +175,7 @@ public class AndroidKeyStoreKeyAgreementSpi extends KeyAgreementSpi } byte[] otherPartyKeyEncoded = mOtherPartyKey.getEncoded(); + StrictMode.noteSlowCall("engineGenerateSecret"); try { return mOperation.finish(otherPartyKeyEncoded, null); } catch (KeyStoreException e) { @@ -245,6 +247,7 @@ public class AndroidKeyStoreKeyAgreementSpi extends KeyAgreementSpi Tag.PURPOSE, KeyPurpose.AGREE_KEY )); + StrictMode.noteDiskWrite(); try { mOperation = mKey.getSecurityLevel().createOperation(mKey.getKeyIdDescriptor(), parameters); diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java index f1681ec1f7d2..d283b05a85e1 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java @@ -18,6 +18,7 @@ package android.security.keystore2; import android.hardware.security.keymint.KeyParameter; import android.hardware.security.keymint.SecurityLevel; +import android.os.StrictMode; import android.security.KeyStore2; import android.security.KeyStoreSecurityLevel; import android.security.keymaster.KeymasterDefs; @@ -281,6 +282,7 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { @Override protected SecretKey engineGenerateKey() { + StrictMode.noteSlowCall("engineGenerateKey"); KeyGenParameterSpec spec = mSpec; if (spec == null) { throw new IllegalStateException("Not initialized"); diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index 474b7ea56be9..1398da3f5ef2 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -27,6 +27,7 @@ import android.hardware.security.keymint.KeyPurpose; import android.hardware.security.keymint.SecurityLevel; import android.hardware.security.keymint.Tag; import android.os.Build; +import android.os.StrictMode; import android.security.KeyPairGeneratorSpec; import android.security.KeyStore2; import android.security.KeyStoreException; @@ -617,6 +618,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato @Override public KeyPair generateKeyPair() { + StrictMode.noteSlowCall("generateKeyPair"); if (mKeyStore == null || mSpec == null) { throw new IllegalStateException("Not initialized"); } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java index 931c2f864eba..d5fb49a5cb94 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java @@ -189,7 +189,7 @@ abstract class AndroidKeyStoreRSASignatureSpi extends AndroidKeyStoreSignatureSp protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException { if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) { throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm() - + ". Only" + KeyProperties.KEY_ALGORITHM_RSA + " supported"); + + ". Only " + KeyProperties.KEY_ALGORITHM_RSA + " supported"); } super.initKey(key); } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java index b4d8defd4f90..273dff1cb8b3 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -24,6 +24,7 @@ import android.hardware.security.keymint.EcCurve; import android.hardware.security.keymint.HardwareAuthenticatorType; import android.hardware.security.keymint.KeyParameter; import android.hardware.security.keymint.SecurityLevel; +import android.os.StrictMode; import android.security.GateKeeper; import android.security.KeyStore2; import android.security.KeyStoreParameter; @@ -164,6 +165,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { KeyDescriptor descriptor = makeKeyDescriptor(alias); try { + StrictMode.noteDiskRead(); return mKeyStore.getKeyEntry(descriptor); } catch (android.security.KeyStoreException e) { if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) { @@ -447,6 +449,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { assertCanReplace(alias, targetDomain, mNamespace, descriptor); try { + StrictMode.noteDiskWrite(); mKeyStore.updateSubcomponents( ((AndroidKeyStorePrivateKey) key).getKeyIdDescriptor(), userCertBytes, chainBytes); @@ -597,6 +600,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { importArgs, flags, pkcs8EncodedPrivateKeyBytes); try { + StrictMode.noteDiskWrite(); mKeyStore.updateSubcomponents(metadata.key, userCertBytes, chainBytes); } catch (android.security.KeyStoreException e) { mKeyStore.deleteKey(metadata.key); @@ -938,6 +942,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { KeyEntryResponse response = null; try { + StrictMode.noteDiskRead(); response = mKeyStore.getKeyEntry(wrappingkey); } catch (android.security.KeyStoreException e) { throw new KeyStoreException("Failed to import wrapped key. Keystore error code: " @@ -994,6 +999,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } try { + StrictMode.noteDiskWrite(); securityLevel.importWrappedKey( wrappedKey, wrappingkey, entry.getWrappedKeyBytes(), @@ -1054,6 +1060,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } try { + StrictMode.noteDiskWrite(); mKeyStore.updateSubcomponents(makeKeyDescriptor(alias), null /* publicCert - unused when used as pure certificate store. */, encoded); @@ -1066,6 +1073,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { public void engineDeleteEntry(String alias) throws KeyStoreException { KeyDescriptor descriptor = makeKeyDescriptor(alias); try { + StrictMode.noteDiskWrite(); mKeyStore.deleteKey(descriptor); } catch (android.security.KeyStoreException e) { if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) { @@ -1076,6 +1084,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { private KeyDescriptor[] getAliasesBatch(String startPastAlias) { try { + StrictMode.noteDiskRead(); return mKeyStore.listBatch( getTargetDomain(), mNamespace, @@ -1103,6 +1112,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { @Override public int engineSize() { try { + StrictMode.noteDiskRead(); return mKeyStore.getNumberOfEntries( getTargetDomain(), mNamespace @@ -1166,6 +1176,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { KeyDescriptor[] keyDescriptors = null; try { + StrictMode.noteDiskRead(); keyDescriptors = mKeyStore.list( getTargetDomain(), mNamespace diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java index 0112e32e05a7..15d14e87fcf6 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java @@ -213,9 +213,6 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, if (mRearDisplayStateRequest != null || isRearDisplayActive()) { mRearDisplayStateRequest = null; mDeviceStateManager.cancelStateRequest(); - } else { - throw new IllegalStateException( - "Unable to cancel a rear display session as there is no active session"); } } } @@ -432,10 +429,6 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, synchronized (mLock) { if (mRearDisplayPresentationController != null) { mDeviceStateManager.cancelStateRequest(); - } else { - throw new IllegalStateException( - "Unable to cancel a rear display presentation session as there is no " - + "active session"); } } } diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index e9abc7e522d5..c72a42cce2bd 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -158,6 +158,7 @@ android_library { "kotlinx-coroutines-android", "kotlinx-coroutines-core", "iconloader_base", + "com_android_wm_shell_flags_lib", "WindowManager-Shell-proto", "dagger2", "jsr330", diff --git a/libs/WindowManager/Shell/aconfig/Android.bp b/libs/WindowManager/Shell/aconfig/Android.bp new file mode 100644 index 000000000000..1a98ffcea9e7 --- /dev/null +++ b/libs/WindowManager/Shell/aconfig/Android.bp @@ -0,0 +1,12 @@ +aconfig_declarations { + name: "com_android_wm_shell_flags", + package: "com.android.wm.shell", + srcs: [ + "multitasking.aconfig", + ], +} + +java_aconfig_library { + name: "com_android_wm_shell_flags_lib", + aconfig_declarations: "com_android_wm_shell_flags", +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig new file mode 100644 index 000000000000..d55a41fc0cf7 --- /dev/null +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -0,0 +1,8 @@ +package: "com.android.wm.shell" + +flag { + name: "example_flag" + namespace: "multitasking" + description: "An Example Flag" + bug: "300136750" +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml index 5f9dbdb60516..da8abde8407f 100644 --- a/libs/WindowManager/Shell/res/values-television/config.xml +++ b/libs/WindowManager/Shell/res/values-television/config.xml @@ -44,6 +44,12 @@ if a custom action is present before closing it. --> <integer name="config_pipForceCloseDelay">5000</integer> + <!-- Animation duration when exit starting window: fade out icon --> + <integer name="starting_window_app_reveal_icon_fade_out_duration">500</integer> + + <!-- Animation delay when exit starting window: reveal app --> + <integer name="starting_window_app_reveal_anim_delay">0</integer> + <!-- Animation duration when exit starting window: reveal app --> <integer name="starting_window_app_reveal_anim_duration">500</integer> diff --git a/libs/WindowManager/Shell/res/values-watch/config.xml b/libs/WindowManager/Shell/res/values-watch/config.xml new file mode 100644 index 000000000000..03736edc4ec6 --- /dev/null +++ b/libs/WindowManager/Shell/res/values-watch/config.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- These resources are around just to allow their values to be customized + for watch products. Do not translate. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Animation duration when exit starting window: fade out icon --> + <integer name="starting_window_app_reveal_icon_fade_out_duration">50</integer> + + <!-- Animation delay when exit starting window: reveal app --> + <integer name="starting_window_app_reveal_anim_delay">50</integer> + + <!-- Animation duration when exit starting window: reveal app --> + <integer name="starting_window_app_reveal_anim_duration">200</integer> + + <!-- Default animation type when hiding the starting window. The possible values are: + - 0 for radial vanish + slide up + - 1 for fade out --> + <integer name="starting_window_exit_animation_type">1</integer> +</resources> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index eabe3a4eca0b..b556150e2ab9 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -142,6 +142,10 @@ <string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string> <!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]--> <string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string> + <!-- Accessibility announcement when the stack of bubbles expands. [CHAR LIMIT=NONE]--> + <string name="bubble_accessibility_announce_expand">expand <xliff:g id="bubble_title" example="Messages">%1$s</xliff:g></string> + <!-- Accessibility announcement when the stack of bubbles collapses. [CHAR LIMIT=NONE]--> + <string name="bubble_accessibility_announce_collapse">collapse <xliff:g id="bubble_title" example="Messages">%1$s</xliff:g></string> <!-- Label for the button that takes the user to the notification settings for the given app. --> <string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string> <!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=30] --> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 3f9dcc19dda7..c124b532b89d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -2037,6 +2037,7 @@ public class BubbleStackView extends FrameLayout }); } notifyExpansionChanged(mExpandedBubble, mIsExpanded); + announceExpandForAccessibility(mExpandedBubble, mIsExpanded); } /** @@ -2053,6 +2054,34 @@ public class BubbleStackView extends FrameLayout } } + private void announceExpandForAccessibility(BubbleViewProvider bubble, boolean expanded) { + if (bubble instanceof Bubble) { + String contentDescription = getBubbleContentDescription((Bubble) bubble); + String message = getResources().getString( + expanded + ? R.string.bubble_accessibility_announce_expand + : R.string.bubble_accessibility_announce_collapse, contentDescription); + announceForAccessibility(message); + } + } + + @NonNull + private String getBubbleContentDescription(Bubble bubble) { + final String appName = bubble.getAppName(); + final String title = bubble.getTitle() != null + ? bubble.getTitle() + : getResources().getString(R.string.notification_bubble_title); + + if (appName == null || title.equals(appName)) { + // App bubble title equals the app name, so return only the title to avoid having + // content description like: `<app> from <app>`. + return title; + } else { + return getResources().getString( + R.string.bubble_content_description_single, title, appName); + } + } + private boolean isGestureNavEnabled() { return mContext.getResources().getInteger( com.android.internal.R.integer.config_navBarInteractionMode) 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 9ce01184c044..f8dd208f96db 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 @@ -17,7 +17,6 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo -import android.app.WindowConfiguration import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM @@ -61,6 +60,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksLi import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.splitscreen.SplitScreenController +import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit @@ -213,6 +213,7 @@ class DesktopTasksController( "DesktopTasksController: moveToDesktop taskId=%d", task.taskId ) + exitSplitIfApplicable(wct, task) // Bring other apps to front first bringDesktopAppsToFront(task.displayId, wct) addMoveToDesktopChanges(wct, task) @@ -240,6 +241,7 @@ class DesktopTasksController( taskInfo.taskId ) val wct = WindowContainerTransaction() + exitSplitIfApplicable(wct, taskInfo) moveHomeTaskToFront(wct) addMoveToDesktopChanges(wct, taskInfo) wct.setBounds(taskInfo.token, startBounds) @@ -319,7 +321,7 @@ class DesktopTasksController( task.taskId ) val wct = WindowContainerTransaction() - wct.setWindowingMode(task.token, WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW) + wct.setWindowingMode(task.token, WINDOWING_MODE_MULTI_WINDOW) wct.setBounds(task.token, Rect()) wct.setDensityDpi(task.token, getDefaultDensityDpi()) if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -329,6 +331,13 @@ class DesktopTasksController( } } + private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) { + if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) { + splitScreenController.prepareExitSplitScreen(wct, + splitScreenController.getStageOfTask(taskInfo.taskId), EXIT_REASON_ENTER_DESKTOP) + } + } + /** * The second part of the animated move to desktop transition, called after * {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index dc6dc7910feb..bb4e0792e83b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -171,13 +171,14 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { return false; } final RecentsController controller = mControllers.get(controllerIdx); - Transitions.setRunningRemoteTransitionDelegate(mAnimApp); + final IApplicationThread animApp = mAnimApp; mAnimApp = null; if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "RecentsTransitionHandler.startAnimation: failed to start animation"); return false; } + Transitions.setRunningRemoteTransitionDelegate(animApp); return true; } @@ -225,6 +226,13 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { private ArrayList<TaskState> mPausingTasks = null; /** + * List of tasks were pausing but closed in a subsequent merged transition. If a + * closing task is reopened, the leash is not initially hidden since it is already + * visible. + */ + private ArrayList<TaskState> mClosingTasks = null; + + /** * List of tasks that we are switching to. Upon finish, these will remain visible and * on top. */ @@ -375,6 +383,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } mFinishTransaction = null; mPausingTasks = null; + mClosingTasks = null; mOpeningTasks = null; mInfo = null; mTransition = null; @@ -419,6 +428,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { mFinishCB = finishCB; mFinishTransaction = finishT; mPausingTasks = new ArrayList<>(); + mClosingTasks = new ArrayList<>(); mOpeningTasks = new ArrayList<>(); mLeashMap = new ArrayMap<>(); mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0; @@ -670,7 +680,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { final TransitionInfo.Change change = closingTasks.get(i); final int pausingIdx = TaskState.indexOf(mPausingTasks, change); if (pausingIdx >= 0) { - mPausingTasks.remove(pausingIdx); + // We are closing the pausing task, but it is still visible and can be + // restart by another transition prior to this transition finishing + final TaskState closingTask = mPausingTasks.remove(pausingIdx); + mClosingTasks.add(closingTask); didMergeThings = true; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, " closing pausing taskId=%d", change.getTaskInfo().taskId); @@ -706,7 +719,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { for (int i = 0; i < openingTasks.size(); ++i) { final TransitionInfo.Change change = openingTasks.get(i); final boolean isLeaf = openingTaskIsLeafs.get(i) == 1; - int pausingIdx = TaskState.indexOf(mPausingTasks, change); + final int closingIdx = TaskState.indexOf(mClosingTasks, change); + if (closingIdx >= 0) { + // Remove opening tasks from closing set + mClosingTasks.remove(closingIdx); + } + final int pausingIdx = TaskState.indexOf(mPausingTasks, change); if (pausingIdx >= 0) { // Something is showing/opening a previously-pausing app. if (isLeaf) { @@ -729,12 +747,23 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { appearedTargets[nextTargetIdx++] = target; // reparent into the original `mInfo` since that's where we are animating. final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo); + final boolean wasClosing = closingIdx >= 0; t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash()); t.setLayer(target.leash, layer); - // Hide the animation leash, let listener show it. - t.hide(target.leash); + if (wasClosing) { + // App was previously visible and is closing + t.show(target.leash); + t.setAlpha(target.leash, 1f); + // Also override the task alpha as it was set earlier when dispatching + // the transition and setting up the leash to hide the + t.setAlpha(change.getLeash(), 1f); + } else { + // Hide the animation leash, let the listener show it + t.hide(target.leash); + } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, - " opening new leaf taskId=%d", target.taskId); + " opening new leaf taskId=%d wasClosing=%b", + target.taskId, wasClosing); mOpeningTasks.add(new TaskState(change, target.leash)); } else { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, 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 991b699161ea..f70b0fc5af48 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 @@ -23,7 +23,6 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; - import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; @@ -131,6 +130,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9; public static final int EXIT_REASON_RECREATE_SPLIT = 10; public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 11; + public static final int EXIT_REASON_ENTER_DESKTOP = 12; @IntDef(value = { EXIT_REASON_UNKNOWN, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, @@ -144,6 +144,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, EXIT_REASON_CHILD_TASK_ENTER_PIP, EXIT_REASON_RECREATE_SPLIT, EXIT_REASON_FULLSCREEN_SHORTCUT, + EXIT_REASON_ENTER_DESKTOP }) @Retention(RetentionPolicy.SOURCE) @interface ExitReason{} @@ -1011,6 +1012,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return "CHILD_TASK_ENTER_PIP"; case EXIT_REASON_RECREATE_SPLIT: return "RECREATE_SPLIT"; + case EXIT_REASON_ENTER_DESKTOP: + return "ENTER_DESKTOP"; default: return "unknown reason, reason int = " + exitReason; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java index 5483fa5d29f6..f4ab2266179a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java @@ -25,6 +25,7 @@ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED_ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT; +import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED; @@ -42,6 +43,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME; @@ -192,6 +194,8 @@ public class SplitscreenEventLogger { return SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT; case EXIT_REASON_FULLSCREEN_SHORTCUT: return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT; + case EXIT_REASON_ENTER_DESKTOP: + return SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP; case EXIT_REASON_UNKNOWN: // Fall through default: diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java index 20c4d5ae5f58..e7e1e0a98550 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java @@ -35,13 +35,14 @@ class SnapshotWindowCreator { void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) { final int taskId = startingWindowInfo.taskInfo.taskId; // Remove any existing starting window for this task before adding. - mStartingWindowRecordManager.removeWindow(taskId, true); + mStartingWindowRecordManager.removeWindow(taskId); final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, startingWindowInfo.appToken, snapshot, mMainExecutor, - () -> mStartingWindowRecordManager.removeWindow(taskId, true)); + () -> mStartingWindowRecordManager.removeWindow(taskId)); if (surface != null) { final SnapshotWindowRecord tView = new SnapshotWindowRecord(surface, - startingWindowInfo.taskInfo.topActivityType, mMainExecutor); + startingWindowInfo.taskInfo.topActivityType, mMainExecutor, + taskId, mStartingWindowRecordManager); mStartingWindowRecordManager.addRecord(taskId, tView); } } @@ -50,8 +51,9 @@ class SnapshotWindowCreator { private final TaskSnapshotWindow mTaskSnapshotWindow; SnapshotWindowRecord(TaskSnapshotWindow taskSnapshotWindow, - int activityType, ShellExecutor removeExecutor) { - super(activityType, removeExecutor); + int activityType, ShellExecutor removeExecutor, int id, + StartingSurfaceDrawer.StartingWindowRecordManager recordManager) { + super(activityType, removeExecutor, id, recordManager); mTaskSnapshotWindow = taskSnapshotWindow; mBGColor = mTaskSnapshotWindow.getBackgroundColor(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java index cb76044da7e5..edb5aba1e46b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java @@ -20,7 +20,6 @@ import static android.view.View.GONE; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_EXIT_ANIM; import android.animation.Animator; -import android.annotation.IntDef; import android.content.Context; import android.graphics.Rect; import android.util.Slog; @@ -47,7 +46,7 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener { private final int mIconFadeOutDuration; private final int mAppRevealDelay; private final int mAppRevealDuration; - @ExitAnimationType + @SplashScreenExitAnimationUtils.ExitAnimationType private final int mAnimationType; private final int mAnimationDuration; private final float mIconStartAlpha; @@ -58,24 +57,6 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener { private Runnable mFinishCallback; - /** - * This splash screen exit animation type uses a radial vanish to hide - * the starting window and slides up the main window content. - */ - private static final int TYPE_RADIAL_VANISH_SLIDE_UP = 0; - - /** - * This splash screen exit animation type fades out the starting window - * to reveal the main window content. - */ - private static final int TYPE_FADE_OUT = 1; - - @IntDef(prefix = { "TYPE_" }, value = { - TYPE_RADIAL_VANISH_SLIDE_UP, - TYPE_FADE_OUT, - }) - private @interface ExitAnimationType {} - SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash, Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish, float roundedCornerRadius) { @@ -121,15 +102,10 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener { } void startAnimations() { - if (mAnimationType == TYPE_FADE_OUT) { - SplashScreenExitAnimationUtils.startFadeOutAnimation(mSplashScreenView, - mAnimationDuration, this); - } else { - SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface, - mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration, - mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay, - mAppRevealDuration, this, mRoundedCornerRadius); - } + SplashScreenExitAnimationUtils.startAnimations(mAnimationType, mSplashScreenView, + mFirstWindowSurface, mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, + mAnimationDuration, mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, + mAppRevealDelay, mAppRevealDuration, this, mRoundedCornerRadius); } private void reset() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java index 64f09a77c839..e86b62dee86d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java @@ -20,6 +20,7 @@ import static android.view.Choreographer.CALLBACK_COMMIT; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.annotation.IntDef; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Configuration; @@ -54,6 +55,7 @@ import com.android.wm.shell.common.TransactionPool; public class SplashScreenExitAnimationUtils { private static final boolean DEBUG_EXIT_ANIMATION = false; private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false; + private static final boolean DEBUG_EXIT_FADE_ANIMATION = false; private static final String TAG = "SplashScreenExitAnimationUtils"; private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f); @@ -62,19 +64,47 @@ public class SplashScreenExitAnimationUtils { private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f); /** + * This splash screen exit animation type uses a radial vanish to hide + * the starting window and slides up the main window content. + * @hide + */ + public static final int TYPE_RADIAL_VANISH_SLIDE_UP = 0; + + /** + * This splash screen exit animation type fades out the starting window + * to reveal the main window content. + * @hide + */ + public static final int TYPE_FADE_OUT = 1; + + /** @hide */ + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_RADIAL_VANISH_SLIDE_UP, + TYPE_FADE_OUT, + }) + public @interface ExitAnimationType {} + + /** * Creates and starts the animator to fade out the icon, reveal the app, and shift up main * window with rounded corner radius. */ - static void startAnimations(ViewGroup splashScreenView, - SurfaceControl firstWindowSurface, int mainWindowShiftLength, - TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration, - int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha, - int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener, - float roundedCornerRadius) { - ValueAnimator animator = createRadialVanishSlideUpAnimator(splashScreenView, - firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame, - animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha, - appRevealDelay, appRevealDuration, animatorListener, roundedCornerRadius); + static void startAnimations(@ExitAnimationType int animationType, + ViewGroup splashScreenView, SurfaceControl firstWindowSurface, + int mainWindowShiftLength, TransactionPool transactionPool, Rect firstWindowFrame, + int animationDuration, int iconFadeOutDuration, float iconStartAlpha, + float brandingStartAlpha, int appRevealDelay, int appRevealDuration, + Animator.AnimatorListener animatorListener, float roundedCornerRadius) { + ValueAnimator animator; + if (animationType == TYPE_FADE_OUT) { + animator = createFadeOutAnimation(splashScreenView, animationDuration, + iconFadeOutDuration, iconStartAlpha, brandingStartAlpha, appRevealDelay, + appRevealDuration, animatorListener); + } else { + animator = createRadialVanishSlideUpAnimator(splashScreenView, + firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame, + animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha, + appRevealDelay, appRevealDuration, animatorListener, roundedCornerRadius); + } animator.start(); } @@ -88,10 +118,11 @@ public class SplashScreenExitAnimationUtils { TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration, int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha, int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) { - startAnimations(splashScreenView, firstWindowSurface, mainWindowShiftLength, - transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration, - iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration, - animatorListener, 0f /* roundedCornerRadius */); + // Start the default 'reveal' animation. + startAnimations(TYPE_RADIAL_VANISH_SLIDE_UP, splashScreenView, + firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame, + animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha, + appRevealDelay, appRevealDuration, animatorListener, 0f /* roundedCornerRadius */); } /** @@ -209,18 +240,57 @@ public class SplashScreenExitAnimationUtils { return nightMode == Configuration.UI_MODE_NIGHT_YES; } - static void startFadeOutAnimation(ViewGroup splashScreenView, - int animationDuration, Animator.AnimatorListener animatorListener) { - final ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f); + private static ValueAnimator createFadeOutAnimation(ViewGroup splashScreenView, + int animationDuration, int iconFadeOutDuration, float iconStartAlpha, + float brandingStartAlpha, int appRevealDelay, int appRevealDuration, + Animator.AnimatorListener animatorListener) { + + if (DEBUG_EXIT_FADE_ANIMATION) { + splashScreenView.setBackgroundColor(Color.BLUE); + } + + final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); animator.setDuration(animationDuration); animator.setInterpolator(Interpolators.LINEAR); animator.addUpdateListener(animation -> { - splashScreenView.setAlpha((float) animation.getAnimatedValue()); + + float linearProgress = (float) animation.getAnimatedValue(); + + // Icon fade out progress (always starts immediately) + final float iconFadeProgress = ICON_INTERPOLATOR.getInterpolation(getProgress( + linearProgress, 0 /* delay */, iconFadeOutDuration, animationDuration)); + View iconView = null; + View brandingView = null; + + if (splashScreenView instanceof SplashScreenView) { + iconView = ((SplashScreenView) splashScreenView).getIconView(); + brandingView = ((SplashScreenView) splashScreenView).getBrandingView(); + } + if (iconView != null) { + iconView.setAlpha(iconStartAlpha * (1f - iconFadeProgress)); + } + if (brandingView != null) { + brandingView.setAlpha(brandingStartAlpha * (1f - iconFadeProgress)); + } + + // Splash screen fade out progress (possibly delayed) + final float splashFadeProgress = Interpolators.ALPHA_OUT.getInterpolation( + getProgress(linearProgress, appRevealDelay, + appRevealDuration, animationDuration)); + + splashScreenView.setAlpha(1f - splashFadeProgress); + + if (DEBUG_EXIT_FADE_ANIMATION) { + Slog.d(TAG, "progress -> animation: " + linearProgress + + "\t icon alpha: " + ((iconView != null) ? iconView.getAlpha() : "n/a") + + "\t splash alpha: " + splashScreenView.getAlpha() + ); + } }); if (animatorListener != null) { animator.addListener(animatorListener); } - animator.start(); + return animator; } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java index 4cfbbd971fe3..31fc98b713ab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java @@ -358,7 +358,7 @@ class SplashscreenWindowCreator extends AbsSplashWindowCreator { } } if (shouldSaveView) { - mStartingWindowRecordManager.removeWindow(taskId, true); + mStartingWindowRecordManager.removeWindow(taskId); saveSplashScreenRecord(appToken, taskId, view, suggestType); } return shouldSaveView; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 7cbf263f7cb1..e2be1533118a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -188,7 +188,7 @@ public class StartingSurfaceDrawer { final SnapshotRecord record = sRecord instanceof SnapshotRecord ? (SnapshotRecord) sRecord : null; if (record != null && record.hasImeSurface()) { - records.removeWindow(taskId, true); + records.removeWindow(taskId); } } @@ -256,10 +256,15 @@ public class StartingSurfaceDrawer { @WindowConfiguration.ActivityType protected final int mActivityType; protected final ShellExecutor mRemoveExecutor; + private final int mTaskId; + private final StartingWindowRecordManager mRecordManager; - SnapshotRecord(int activityType, ShellExecutor removeExecutor) { + SnapshotRecord(int activityType, ShellExecutor removeExecutor, int taskId, + StartingWindowRecordManager recordManager) { mActivityType = activityType; mRemoveExecutor = removeExecutor; + mTaskId = taskId; + mRecordManager = recordManager; } @Override @@ -301,6 +306,7 @@ public class StartingSurfaceDrawer { @CallSuper protected void removeImmediately() { mRemoveExecutor.removeCallbacks(mScheduledRunnable); + mRecordManager.onRecordRemoved(mTaskId); } } @@ -316,7 +322,7 @@ public class StartingSurfaceDrawer { taskIds[i] = mStartingWindowRecords.keyAt(i); } for (int i = taskSize - 1; i >= 0; --i) { - removeWindow(taskIds[i], true); + removeWindow(taskIds[i]); } } @@ -335,9 +341,13 @@ public class StartingSurfaceDrawer { } } - void removeWindow(int taskId, boolean immediately) { + void removeWindow(int taskId) { mTmpRemovalInfo.taskId = taskId; - removeWindow(mTmpRemovalInfo, immediately); + removeWindow(mTmpRemovalInfo, true/* immediately */); + } + + void onRecordRemoved(int taskId) { + mStartingWindowRecords.remove(taskId); } StartingWindowRecord getRecord(int taskId) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java index 144547885501..fed2f34b5e0c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java @@ -92,7 +92,8 @@ class WindowlessSnapshotWindowCreator { final SnapshotWindowRecord record = new SnapshotWindowRecord(mViewHost, wlw.mChildSurface, taskDescription.getBackgroundColor(), snapshot.hasImeSurface(), - runningTaskInfo.topActivityType, removeExecutor); + runningTaskInfo.topActivityType, removeExecutor, + taskId, mStartingWindowRecordManager); mStartingWindowRecordManager.addRecord(taskId, record); info.notifyAddComplete(wlw.mChildSurface); } @@ -104,8 +105,9 @@ class WindowlessSnapshotWindowCreator { SnapshotWindowRecord(SurfaceControlViewHost viewHost, SurfaceControl childSurface, int bgColor, boolean hasImeSurface, int activityType, - ShellExecutor removeExecutor) { - super(activityType, removeExecutor); + ShellExecutor removeExecutor, int id, + StartingSurfaceDrawer.StartingWindowRecordManager recordManager) { + super(activityType, removeExecutor, id, recordManager); mViewHost = viewHost; mChildSurface = childSurface; mBGColor = bgColor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java index adae21b20b3c..93d763608b5f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java @@ -54,12 +54,13 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { private static final String TAG = TaskViewTaskController.class.getSimpleName(); private final CloseGuard mGuard = new CloseGuard(); - + private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + /** Used to inset the activity content to allow space for a caption bar. */ + private final Binder mCaptionInsetsOwner = new Binder(); private final ShellTaskOrganizer mTaskOrganizer; private final Executor mShellExecutor; private final SyncTransactionQueue mSyncQueue; private final TaskViewTransitions mTaskViewTransitions; - private TaskViewBase mTaskViewBase; private final Context mContext; /** @@ -70,21 +71,19 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { * in this situation to allow us to notify listeners correctly if the task failed to open. */ private ActivityManager.RunningTaskInfo mPendingInfo; - /* Indicates that the task we attempted to launch in the task view failed to launch. */ - private boolean mTaskNotFound; + private TaskViewBase mTaskViewBase; protected ActivityManager.RunningTaskInfo mTaskInfo; private WindowContainerToken mTaskToken; private SurfaceControl mTaskLeash; - private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + /* Indicates that the task we attempted to launch in the task view failed to launch. */ + private boolean mTaskNotFound; private boolean mSurfaceCreated; private SurfaceControl mSurfaceControl; private boolean mIsInitialized; private boolean mNotifiedForInitialized; + private boolean mHideTaskWithSurface = true; private TaskView.Listener mListener; private Executor mListenerExecutor; - - /** Used to inset the activity content to allow space for a caption bar. */ - private final Binder mCaptionInsetsOwner = new Binder(); private Rect mCaptionInsets; public TaskViewTaskController(Context context, ShellTaskOrganizer organizer, @@ -102,6 +101,19 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { mGuard.open("release"); } + /** + * Specifies if the task should be hidden when the surface is destroyed. + * <p>This is {@code true} by default. + * + * @param hideTaskWithSurface {@code false} if task needs to remain visible even when the + * surface is destroyed, {@code true} otherwise. + */ + public void setHideTaskWithSurface(boolean hideTaskWithSurface) { + // TODO(b/299535374): Remove mHideTaskWithSurface once the taskviews with launch root tasks + // are moved to a window in SystemUI in auto. + mHideTaskWithSurface = hideTaskWithSurface; + } + SurfaceControl getSurfaceControl() { return mSurfaceControl; } @@ -257,9 +269,17 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { mTaskNotFound = false; } + /** This method shouldn't be called when shell transitions are enabled. */ private void updateTaskVisibility() { + boolean visible = mSurfaceCreated; + if (!visible && !mHideTaskWithSurface) { + return; + } WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */); + wct.setHidden(mTaskToken, !visible /* hidden */); + if (!visible) { + wct.reorder(mTaskToken, false /* onTop */); + } mSyncQueue.queue(wct); if (mListener == null) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index bbf67a6155d7..a90edf20f94e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -137,7 +137,6 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { }); } }; - Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread()); try { // If the remote is actually in the same process, then make a copy of parameters since // remote impls assume that they have to clean-up native references. @@ -149,6 +148,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb); // assume that remote will apply the start transaction. startTransaction.clear(); + Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread()); } catch (RemoteException e) { Log.e(Transitions.TAG, "Error running remote transition.", e); unhandleDeath(remote.asBinder(), finishCallback); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 026d0cdd7c52..b4e181808194 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -68,6 +68,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition; @@ -348,13 +349,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final int id = v.getId(); if (id == R.id.close_window || id == R.id.close_button) { mTaskOperations.closeTask(mTaskToken); - if (mSplitScreenController != null - && mSplitScreenController.isSplitScreenVisible()) { - int remainingTaskPosition = mTaskId == mSplitScreenController - .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId - ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT; - ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController - .getTaskInfo(remainingTaskPosition); + if (isTaskInSplitScreen(mTaskId)) { + RunningTaskInfo remainingTask = getOtherSplitTask(mTaskId); mSplitScreenController.moveTaskToFullscreen(remainingTask.taskId); } decoration.closeMaximizeMenu(); @@ -376,6 +372,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct); decoration.incrementRelayoutBlock(); mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct); + closeOtherSplitTask(mTaskId); } decoration.closeHandleMenu(); } else if (id == R.id.fullscreen_button) { @@ -720,6 +717,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { relevantDecor.mTaskInfo.displayId); if (ev.getY() > statusBarHeight) { if (mMoveToDesktopAnimator == null) { + closeOtherSplitTask(relevantDecor.mTaskInfo.taskId); mMoveToDesktopAnimator = new MoveToDesktopAnimator( mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo, relevantDecor.mTaskSurface); @@ -810,7 +808,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) { if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) { // We can't look at focused task here as only one task will have focus. - return getSplitScreenDecor(ev); + DesktopModeWindowDecoration splitTaskDecor = getSplitScreenDecor(ev); + return splitTaskDecor == null ? getFocusedDecor() : splitTaskDecor; } else { return getFocusedDecor(); } @@ -942,6 +941,24 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } } + private RunningTaskInfo getOtherSplitTask(int taskId) { + @SplitPosition int remainingTaskPosition = mSplitScreenController + .getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT + ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT; + return mSplitScreenController.getTaskInfo(remainingTaskPosition); + } + + private void closeOtherSplitTask(int taskId) { + if (isTaskInSplitScreen(taskId)) { + mTaskOperations.closeTask(getOtherSplitTask(taskId).token); + } + } + + private boolean isTaskInSplitScreen(int taskId) { + return mSplitScreenController != null + && mSplitScreenController.isTaskInSplitScreen(taskId); + } + private class DragStartListenerImpl implements DragPositioningCallbackUtility.DragStartListener { @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 8d76fc6c542b..a26927586b61 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -17,6 +17,7 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; @@ -220,6 +221,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final Resources resources = mDecorWindowContext.getResources(); final Configuration taskConfig = mTaskInfo.getConfiguration(); final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); + final boolean isFullscreen = taskConfig.windowConfiguration.getWindowingMode() + == WINDOWING_MODE_FULLSCREEN; outResult.mWidth = taskBounds.width(); outResult.mHeight = taskBounds.height(); @@ -279,13 +282,24 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f; mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f; final Point taskPosition = mTaskInfo.positionInParent; - startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight) - .setShadowRadius(mTaskSurface, shadowRadius) + if (isFullscreen) { + // Setting the task crop to the width/height stops input events from being sent to + // some regions of the app window. See b/300324920 + // TODO(b/296921174): investigate whether crop/position needs to be set by window + // decorations at all when transition handlers are already taking ownership of the task + // surface placement/crop, especially when in fullscreen where tasks cannot be + // drag-resized by the window decoration. + startT.setWindowCrop(mTaskSurface, null); + finishT.setWindowCrop(mTaskSurface, null); + } else { + startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); + finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); + } + startT.setShadowRadius(mTaskSurface, shadowRadius) .setColor(mTaskSurface, mTmpColor) .show(mTaskSurface); finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y) - .setShadowRadius(mTaskSurface, shadowRadius) - .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); + .setShadowRadius(mTaskSurface, shadowRadius); if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { startT.setCornerRadius(mTaskSurface, params.mCornerRadius); finishT.setCornerRadius(mTaskSurface, params.mCornerRadius); diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt index fb6c09381d3b..c9a98c73e5e5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt @@ -33,14 +33,14 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran @Postsubmit @Test override fun pipAppWindowAlwaysVisible() { - flicker.assertWm { this.isAppWindowVisible(standardAppHelper) } + flicker.assertWm { this.isAppWindowVisible(standardAppHelper.packageNameMatcher) } } /** Checks [standardAppHelper] layer remains visible throughout the animation */ @Postsubmit @Test override fun pipAppLayerAlwaysVisible() { - flicker.assertLayers { this.isVisible(standardAppHelper) } + flicker.assertLayers { this.isVisible(standardAppHelper.packageNameMatcher) } } /** Checks the content overlay appears then disappears during the animation */ @@ -57,7 +57,9 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran @Postsubmit @Test override fun pipWindowRemainInsideVisibleBounds() { - flicker.assertWmVisibleRegion(standardAppHelper) { coversAtMost(displayBounds) } + flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) { + coversAtMost(displayBounds) + } } /** @@ -68,7 +70,7 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran @Test override fun pipLayerOrOverlayRemainInsideVisibleBounds() { flicker.assertLayersVisibleRegion( - standardAppHelper.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY) + standardAppHelper.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY) ) { coversAtMost(displayBounds) } @@ -93,9 +95,9 @@ abstract class AppsEnterPipTransition(flicker: LegacyFlickerTest) : EnterPipTran @Test override fun pipWindowBecomesPinned() { flicker.assertWm { - invoke("pipWindowIsNotPinned") { it.isNotPinned(standardAppHelper) } + invoke("pipWindowIsNotPinned") { it.isNotPinned(standardAppHelper.packageNameMatcher) } .then() - .invoke("pipWindowIsPinned") { it.isPinned(standardAppHelper) } + .invoke("pipWindowIsPinned") { it.isPinned(standardAppHelper.packageNameMatcher) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt index 54b3e2a8c8c0..d7ba3d57b548 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt @@ -16,19 +16,22 @@ package com.android.wm.shell.flicker.pip.apps -import android.os.Handler -import android.os.Looper -import android.os.SystemClock import android.content.Context import android.location.Criteria import android.location.Location import android.location.LocationManager +import android.os.Handler +import android.os.Looper +import android.os.SystemClock +import android.platform.test.annotations.Postsubmit import android.tools.device.apphelpers.MapsAppHelper import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import androidx.test.filters.RequiresDevice +import org.junit.Assume import org.junit.FixMethodOrder +import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -129,4 +132,12 @@ open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } } + + @Postsubmit + @Test + override fun focusChanges() { + // in gestural nav the focus goes to different activity on swipe up with auto enter PiP + Assume.assumeFalse(flicker.scenario.isGesturalNavigation) + super.focusChanges() + } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt new file mode 100644 index 000000000000..c370d91034fd --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip.apps + +import android.platform.test.annotations.Postsubmit +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.YouTubeAppHelper +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import androidx.test.filters.RequiresDevice +import org.junit.Assume +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test entering pip from YouTube app by interacting with the app UI + * + * To run this test: `atest WMShellFlickerTests:YouTubeEnterPipTest` + * + * Actions: + * ``` + * Launch YouTube and start playing a video + * Go home to enter PiP + * ``` + * + * Notes: + * ``` + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited from [PipTransition] + * 2. Part of the test setup occurs automatically via + * [android.tools.device.flicker.legacy.runner.TransitionRunner], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup + * ``` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class YouTubeEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) { + override val standardAppHelper: YouTubeAppHelper = YouTubeAppHelper(instrumentation) + + override val defaultEnterPip: FlickerBuilder.() -> Unit = { + setup { + standardAppHelper.launchViaIntent( + wmHelper, + YouTubeAppHelper.getYoutubeVideoIntent("HPcEAtoXXLA"), + ComponentNameMatcher(YouTubeAppHelper.PACKAGE_NAME, "") + ) + standardAppHelper.waitForVideoPlaying() + } + } + + override val defaultTeardown: FlickerBuilder.() -> Unit = { + teardown { + standardAppHelper.exit(wmHelper) + } + } + + override val thisTransition: FlickerBuilder.() -> Unit = { + transitions { tapl.goHome() } + } + + @Postsubmit + @Test + override fun pipOverlayLayerAppearThenDisappear() { + // YouTube uses source rect hint, so PiP overlay is never present + } + + @Postsubmit + @Test + override fun focusChanges() { + // in gestural nav the focus goes to different activity on swipe up with auto enter PiP + Assume.assumeFalse(flicker.scenario.isGesturalNavigation) + super.focusChanges() + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt index fa1be63296e0..2539fd50d742 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt @@ -31,7 +31,8 @@ import org.junit.runner.RunWith class DismissSplitScreenByDividerGesturalNavPortrait : DismissSplitScreenByDivider(Rotation.ROTATION_0) { - @ExpectedScenarios(["SPLIT_SCREEN_EXIT"]) + // TODO(b/300260196): Not detecting this scenario right now + @ExpectedScenarios(["ENTIRE_TRACE"]) @Test override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt index 17ed396987af..e7274918fa2b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt @@ -424,6 +424,7 @@ class PhysicsAnimatorTest : ShellTestCase() { eq(-5f), anyFloat(), eq(true)) } + @Ignore("Started flaking despite no changes, tracking in b/299636216") @Test fun testIsPropertyAnimating() { PhysicsAnimatorTestUtils.setAllAnimationsBlock(false) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt index 9f0d89bc3128..52375850b9a5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt @@ -27,15 +27,13 @@ import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import org.junit.Before import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.ArgumentMatchers.anyString -import org.mockito.ArgumentMatchers.eq -import org.mockito.Mockito -import org.mockito.Mockito.mock -import org.mockito.Mockito.never -import org.mockito.Mockito.reset -import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.reset +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoMoreInteractions @SmallTest @RunWith(AndroidTestingRunner::class) @@ -66,7 +64,7 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { @Before fun setup() { - launcherApps = mock(LauncherApps::class.java) + launcherApps = mock<LauncherApps>() repository = BubbleVolatileRepository(launcherApps) } @@ -98,7 +96,7 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { repository.addBubbles(user11.identifier, listOf(bubble12)) assertEquals(listOf(bubble11, bubble12), repository.getEntities(user11.identifier)) - Mockito.verifyNoMoreInteractions(launcherApps) + verifyNoMoreInteractions(launcherApps) } @Test @@ -167,9 +165,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { assertThat(ret).isTrue() // bubbles were removed assertThat(repository.getEntities(user0.identifier).toList()).isEmpty() - verify(launcherApps, never()).uncacheShortcuts(anyString(), - any(), - any(UserHandle::class.java), anyInt()) + verify(launcherApps, never()) + .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>()) } @Test @@ -184,9 +181,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { assertThat(repository.getEntities(user0.identifier).toList()) .isEqualTo(listOf(bubble1, bubble3)) - verify(launcherApps, never()).uncacheShortcuts(anyString(), - any(), - any(UserHandle::class.java), anyInt()) + verify(launcherApps, never()) + .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>()) } @Test @@ -200,9 +196,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { assertThat(repository.getEntities(user0.identifier).toList()) .isEqualTo(listOf(bubble1, bubble2, bubble3)) - verify(launcherApps, never()).uncacheShortcuts(anyString(), - any(), - any(UserHandle::class.java), anyInt()) + verify(launcherApps, never()) + .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>()) } @Test @@ -219,9 +214,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { user11.identifier)) assertThat(ret).isFalse() // bubbles were NOT removed - verify(launcherApps, never()).uncacheShortcuts(anyString(), - any(), - any(UserHandle::class.java), anyInt()) + verify(launcherApps, never()) + .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>()) } @Test @@ -237,9 +231,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { assertThat(ret).isTrue() // bubbles were removed assertThat(repository.getEntities(user0.identifier).toList()).isEmpty() - verify(launcherApps, never()).uncacheShortcuts(anyString(), - any(), - any(UserHandle::class.java), anyInt()) + verify(launcherApps, never()) + .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>()) // User 11 bubbles should still be here assertThat(repository.getEntities(user11.identifier).toList()) @@ -261,9 +254,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { // bubble2 is the work profile bubble and should be removed assertThat(repository.getEntities(user0.identifier).toList()) .isEqualTo(listOf(bubble1, bubble3)) - verify(launcherApps, never()).uncacheShortcuts(anyString(), - any(), - any(UserHandle::class.java), anyInt()) + verify(launcherApps, never()) + .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>()) // User 11 bubbles should still be here assertThat(repository.getEntities(user11.identifier).toList()) 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 be4a287bff9d..c6cccc059e89 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 @@ -52,6 +52,8 @@ import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask +import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask +import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit @@ -94,6 +96,7 @@ class DesktopTasksControllerTest : ShellTestCase() { ToggleResizeDesktopTaskTransitionHandler @Mock lateinit var launchAdjacentController: LaunchAdjacentController @Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration + @Mock lateinit var splitScreenController: SplitScreenController private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController @@ -116,6 +119,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } controller = createController() + controller.splitScreenController = splitScreenController shellInit.init() } @@ -341,6 +345,30 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveToDesktop_splitTaskExitsSplit() { + var task = setUpSplitScreenTask() + controller.moveToDesktop(desktopModeWindowDecoration, task) + val wct = getLatestMoveToDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(), + eq(SplitScreenController.EXIT_REASON_ENTER_DESKTOP) + ) + } + + @Test + fun moveToDesktop_fullscreenTaskDoesNotExitSplit() { + var task = setUpFullscreenTask() + controller.moveToDesktop(desktopModeWindowDecoration, task) + val wct = getLatestMoveToDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + verify(splitScreenController, never()).prepareExitSplitScreen(any(), anyInt(), + eq(SplitScreenController.EXIT_REASON_ENTER_DESKTOP) + ) + } + + @Test fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() { val task = setUpFreeformTask() task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN @@ -695,6 +723,13 @@ class DesktopTasksControllerTest : ShellTestCase() { return task } + private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { + val task = createSplitScreenTask(displayId) + whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) + runningTasks.add(task) + return task + } + private fun markTaskVisible(task: RunningTaskInfo) { desktopModeTaskRepository.updateVisibleFreeformTasks( task.displayId, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt index 29a757c19d98..2f6f3207137d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt @@ -21,6 +21,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.view.Display.DEFAULT_DISPLAY import com.android.wm.shell.MockToken import com.android.wm.shell.TestRunningTaskInfoBuilder @@ -45,12 +46,25 @@ class DesktopTestHelpers { @JvmOverloads fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { return TestRunningTaskInfoBuilder() - .setDisplayId(displayId) - .setToken(MockToken().token()) - .setActivityType(ACTIVITY_TYPE_STANDARD) - .setWindowingMode(WINDOWING_MODE_FULLSCREEN) - .setLastActiveTime(100) - .build() + .setDisplayId(displayId) + .setToken(MockToken().token()) + .setActivityType(ACTIVITY_TYPE_STANDARD) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .setLastActiveTime(100) + .build() + } + + /** Create a task that has windowing mode set to [WINDOWING_MODE_MULTI_WINDOW] */ + @JvmStatic + @JvmOverloads + fun createSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { + return TestRunningTaskInfoBuilder() + .setDisplayId(displayId) + .setToken(MockToken().token()) + .setActivityType(ACTIVITY_TYPE_STANDARD) + .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW) + .setLastActiveTime(100) + .build() } /** Create a new home task */ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java index d098d332a376..0088051928fb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java @@ -221,6 +221,20 @@ public class TaskViewTest extends ShellTestCase { } @Test + public void testSurfaceDestroyed_withTask_shouldNotHideTask_legacyTransitions() { + assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); + mTaskViewTaskController.setHideTaskWithSurface(false); + + SurfaceHolder sh = mock(SurfaceHolder.class); + mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash); + mTaskView.surfaceCreated(sh); + reset(mViewListener); + mTaskView.surfaceDestroyed(sh); + + verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean()); + } + + @Test public void testSurfaceDestroyed_withTask_legacyTransitions() { assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS); SurfaceHolder sh = mock(SurfaceHolder.class); diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS index 436f10737cb4..ef4cc46cb1c8 100644 --- a/libs/androidfw/OWNERS +++ b/libs/androidfw/OWNERS @@ -1,5 +1,4 @@ set noparent -toddke@google.com zyy@google.com patb@google.com diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index 86e53f5bb2e2..83d2b613f80a 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -13,3 +13,10 @@ flag { description: "Gates whether to adjust local stream volume when the app in the foreground is the last app to play audio or adjust the volume of the last active media session that the user interacted with." bug: "275185436" } + +flag { + namespace: "media_solutions" + name: "enable_audio_policies_device_and_bluetooth_controller" + description: "Use Audio Policies implementation for device and Bluetooth route controllers." + bug: "280576228" +} diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml index 4161601e2317..d9ef3a2151ce 100644 --- a/packages/CredentialManager/AndroidManifest.xml +++ b/packages/CredentialManager/AndroidManifest.xml @@ -17,31 +17,42 @@ */ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.credentialmanager"> + package="com.android.credentialmanager"> <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/> <application - android:allowBackup="true" - android:dataExtractionRules="@xml/data_extraction_rules" - android:fullBackupContent="@xml/backup_rules" - android:icon="@mipmap/ic_launcher" - android:label="@string/app_name" - android:roundIcon="@mipmap/ic_launcher_round" - android:supportsRtl="true" - android:theme="@style/Theme.CredentialSelector"> - - <activity - android:name=".CredentialSelectorActivity" - android:exported="true" - android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR" - android:launchMode="singleTop" + android:allowBackup="true" + android:dataExtractionRules="@xml/data_extraction_rules" + android:fullBackupContent="@xml/backup_rules" + android:icon="@mipmap/ic_launcher" android:label="@string/app_name" - android:excludeFromRecents="true" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" android:theme="@style/Theme.CredentialSelector"> - </activity> - </application> + + <activity + android:name=".CredentialSelectorActivity" + android:exported="true" + android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR" + android:launchMode="singleTop" + android:label="@string/app_name" + android:excludeFromRecents="true" + android:theme="@style/Theme.CredentialSelector"> + </activity> + <service + android:name=".autofill.CredentialAutofillService" + android:exported="false" + android:permission="android.permission.BIND_AUTOFILL_SERVICE"> + <meta-data + android:name="android.autofill" + android:resource="@xml/autofill_service_configuration"/> + <intent-filter> + <action android:name="android.service.autofill.AutofillService"/> + </intent-filter> + </service> + </application> </manifest> diff --git a/packages/CredentialManager/horologist/README.md b/packages/CredentialManager/horologist/README.md new file mode 100644 index 000000000000..005ad2d36b77 --- /dev/null +++ b/packages/CredentialManager/horologist/README.md @@ -0,0 +1,3 @@ +This folder is to place the code from Horologist (go/horologist). +It should be removed once Horologist is imported to the platform and the dependencies to this +module are updated to point to the imported Horologist. diff --git a/packages/CredentialManager/res/xml/autofill_service_configuration.xml b/packages/CredentialManager/res/xml/autofill_service_configuration.xml new file mode 100644 index 000000000000..25cc094fa44e --- /dev/null +++ b/packages/CredentialManager/res/xml/autofill_service_configuration.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Sample backup rules file; uncomment and customize as necessary. + See https://developer.android.com/guide/topics/data/autobackup + for details. + Note: This file is ignored for devices older that API 31 + See https://developer.android.com/about/versions/12/backup-restore +--> +<autofill-service-configuration + xmlns:android="http://schemas.android.com/apk/res/android" + android:supportsInlineSuggestions="true"/>
\ No newline at end of file diff --git a/packages/CredentialManager/shared/README.md b/packages/CredentialManager/shared/README.md new file mode 100644 index 000000000000..d2375a0ccdf9 --- /dev/null +++ b/packages/CredentialManager/shared/README.md @@ -0,0 +1,2 @@ +This folder is to place the common code that will be shared between the phone project and the wear +project of the Credential Manager implementation. diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt new file mode 100644 index 000000000000..943c2b4b3a5b --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.autofill + +import android.os.CancellationSignal +import android.service.autofill.AutofillService +import android.service.autofill.FillCallback +import android.service.autofill.FillRequest +import android.service.autofill.SaveRequest +import android.service.autofill.SaveCallback + +class CredentialAutofillService : AutofillService() { + override fun onFillRequest( + request: FillRequest, + cancellationSignal: CancellationSignal, + callback: FillCallback + ) { + TODO("Not yet implemented") + } + + override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) { + TODO("Not yet implemented") + } +}
\ No newline at end of file diff --git a/packages/CredentialManager/wear/README.md b/packages/CredentialManager/wear/README.md new file mode 100644 index 000000000000..b9d27b9a98e5 --- /dev/null +++ b/packages/CredentialManager/wear/README.md @@ -0,0 +1 @@ +This project is the wear implementation of the Credential Manager feature.
\ No newline at end of file diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index 5bd422bf7894..a16f9f55b466 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -70,15 +70,18 @@ </intent-filter> </activity> + <!-- NOTE: the workaround to fix the screen flash problem. Remember to check the problem + is resolved for new implementation --> <activity android:name=".InstallStaging" - android:exported="false" /> + android:theme="@style/Theme.AlertDialogActivity.NoDim" + android:exported="false" /> <activity android:name=".DeleteStagedFileOnResult" android:theme="@style/Theme.AlertDialogActivity.NoActionBar" android:exported="false" /> <activity android:name=".PackageInstallerActivity" - android:exported="false" /> + android:exported="false" /> <activity android:name=".InstallInstalling" android:theme="@style/Theme.AlertDialogActivity.NoAnimation" diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml index 9a062295608d..aa1fa164b021 100644 --- a/packages/PackageInstaller/res/values/themes.xml +++ b/packages/PackageInstaller/res/values/themes.xml @@ -32,4 +32,9 @@ <item name="android:windowNoTitle">true</item> </style> + <style name="Theme.AlertDialogActivity.NoDim"> + <item name="android:windowNoTitle">true</item> + <item name="android:backgroundDimAmount">0</item> + </style> + </resources> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index 80e876196f7f..d97fb5440cbd 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -306,6 +306,7 @@ public class PackageInstallerActivity extends AlertActivity { } private void initiateInstall() { + bindUi(); String pkgName = mPkgInfo.packageName; // Check if there is already a package on the device with this name // but it has been renamed to something else. @@ -445,7 +446,6 @@ public class PackageInstallerActivity extends AlertActivity { if (mAppSnippet != null) { // load placeholder layout with OK button disabled until we override this layout in // startInstallConfirm - bindUi(); checkIfAllowedAndInitiateInstall(); } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index 471f3b93b8aa..cfd1a50abb4c 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -32,6 +32,7 @@ import com.android.settingslib.spa.gallery.itemList.ItemListPageProvider import com.android.settingslib.spa.gallery.itemList.ItemOperatePageProvider import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider import com.android.settingslib.spa.gallery.editor.SettingsOutlinedTextFieldPageProvider +import com.android.settingslib.spa.gallery.editor.SettingsTextFieldPasswordPageProvider import com.android.settingslib.spa.gallery.page.ArgumentPageProvider import com.android.settingslib.spa.gallery.page.FooterPageProvider import com.android.settingslib.spa.gallery.page.IllustrationPageProvider @@ -92,6 +93,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { SettingsOutlinedTextFieldPageProvider, SettingsExposedDropdownMenuBoxPageProvider, SettingsExposedDropdownMenuCheckBoxProvider, + SettingsTextFieldPasswordPageProvider, ), rootPages = listOf( HomePageProvider.createSettingsPage(), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt index b74af213a058..4875ea99d4d6 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt @@ -39,6 +39,8 @@ object EditorMainPageProvider : SettingsPageProvider { .build(), SettingsExposedDropdownMenuCheckBoxProvider.buildInjectEntry().setLink(fromPage = owner) .build(), + SettingsTextFieldPasswordPageProvider.buildInjectEntry().setLink(fromPage = owner) + .build(), ) } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt index 292e002779f5..37c8eef8a90d 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt @@ -46,21 +46,23 @@ object SettingsExposedDropdownMenuCheckBoxProvider : SettingsPageProvider { @Composable override fun Page(arguments: Bundle?) { RegularScaffold(title = TITLE) { - SettingsExposedDropdownMenuCheckBox(label = exposedDropdownMenuCheckBoxLabel, + SettingsExposedDropdownMenuCheckBox( + label = exposedDropdownMenuCheckBoxLabel, options = options, selectedOptionsState = remember { selectedOptionsState1 }, enabled = true, - onselectedOptionStateChange = {}) + onSelectedOptionStateChange = {}, + ) } } fun buildInjectEntry(): SettingsEntryBuilder { return SettingsEntryBuilder.createInject(owner = createSettingsPage()).setUiLayoutFn { - Preference(object : PreferenceModel { - override val title = TITLE - override val onClick = navigator(name) - }) - } + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsTextFieldPasswordPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsTextFieldPasswordPageProvider.kt new file mode 100644 index 000000000000..fc51466ee7e6 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsTextFieldPasswordPageProvider.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.gallery.editor + +import android.os.Bundle +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.tooling.preview.Preview +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.editor.SettingsTextFieldPassword +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold + +private const val TITLE = "Sample SettingsTextFieldPassword" + +object SettingsTextFieldPasswordPageProvider : SettingsPageProvider { + override val name = "SettingsTextFieldPassword" + + override fun getTitle(arguments: Bundle?): String { + return TITLE + } + + @Composable + override fun Page(arguments: Bundle?) { + var value by remember { mutableStateOf("value") } + RegularScaffold(title = TITLE) { + SettingsTextFieldPassword( + value = value, + label = "label", + onTextChange = { value = it }) + } + } + + fun buildInjectEntry(): SettingsEntryBuilder { + return SettingsEntryBuilder.createInject(owner = createSettingsPage()) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun SettingsTextFieldPasswordPagePreview() { + SettingsTheme { + SettingsTextFieldPasswordPageProvider.Page(null) + } +}
\ No newline at end of file diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt index 08e3a27da125..9f8c868f4aa4 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt @@ -22,6 +22,7 @@ import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.annotation.VisibleForTesting +import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect @@ -126,18 +127,22 @@ private fun NavControllerWrapperImpl.NavContent( allProvider: Collection<SettingsPageProvider>, content: @Composable (SettingsPage) -> Unit, ) { - NavHost( - navController = navController, - startDestination = NullPageProvider.name, - ) { - composable(NullPageProvider.name) {} - for (spp in allProvider) { - animatedComposable( - route = spp.name + spp.parameter.navRoute(), - arguments = spp.parameter, - ) { navBackStackEntry -> - val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) } - content(page) + // TODO(b/298520326): Remove Box after the issue is fixed. + // Wrap the top level node into a Box to workaround an issue of Compose 1.6.0-alpha03. + Box { + NavHost( + navController = navController, + startDestination = NullPageProvider.name, + ) { + composable(NullPageProvider.name) {} + for (spp in allProvider) { + animatedComposable( + route = spp.name + spp.parameter.navRoute(), + arguments = spp.parameter, + ) { navBackStackEntry -> + val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) } + content(page) + } } } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt index 459a78382f39..32600943ac80 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt @@ -52,7 +52,7 @@ fun SettingsExposedDropdownMenuCheckBox( options: List<String>, selectedOptionsState: SnapshotStateList<String>, enabled: Boolean, - onselectedOptionStateChange: (String) -> Unit, + onSelectedOptionStateChange: () -> Unit, ) { var dropDownWidth by remember { mutableStateOf(0) } var expanded by remember { mutableStateOf(false) } @@ -70,7 +70,7 @@ fun SettingsExposedDropdownMenuCheckBox( .menuAnchor() .fillMaxWidth(), value = selectedOptionsState.joinToString(", "), - onValueChange = onselectedOptionStateChange, + onValueChange = {}, label = { Text(text = label) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon( @@ -89,18 +89,21 @@ fun SettingsExposedDropdownMenuCheckBox( onDismissRequest = { expanded = false }, ) { options.forEach { option -> - TextButton(modifier = Modifier - .fillMaxHeight() - .fillMaxWidth(), onClick = { - if (selectedOptionsState.contains(option)) { - selectedOptionsState.remove( - option - ) - } else { - selectedOptionsState.add( - option - ) - } + TextButton( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth(), + onClick = { + if (selectedOptionsState.contains(option)) { + selectedOptionsState.remove( + option + ) + } else { + selectedOptionsState.add( + option + ) + } + onSelectedOptionStateChange() }) { Row( modifier = Modifier @@ -109,9 +112,10 @@ fun SettingsExposedDropdownMenuCheckBox( horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically ) { - Checkbox(checked = selectedOptionsState.contains( - option - ), onCheckedChange = {}) + Checkbox( + checked = selectedOptionsState.contains(option), + onCheckedChange = null, + ) Text(text = option) } } @@ -127,10 +131,11 @@ private fun ActionButtonsPreview() { val options = listOf("item1", "item2", "item3") val selectedOptionsState = remember { mutableStateListOf("item1", "item2") } SettingsTheme { - SettingsExposedDropdownMenuCheckBox(label = "label", + SettingsExposedDropdownMenuCheckBox( + label = "label", options = options, selectedOptionsState = selectedOptionsState, enabled = true, - onselectedOptionStateChange = {}) + onSelectedOptionStateChange = {}) } }
\ No newline at end of file diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt new file mode 100644 index 000000000000..d0a61882593c --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.editor + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.selection.toggleable +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Visibility +import androidx.compose.material.icons.outlined.VisibilityOff +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.tooling.preview.Preview +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsTheme + +@Composable +fun SettingsTextFieldPassword( + value: String, + label: String, + onTextChange: (String) -> Unit, +) { + var visibility by remember { mutableStateOf(false) } + OutlinedTextField( + modifier = Modifier + .padding(SettingsDimension.itemPadding) + .fillMaxWidth(), + value = value, + onValueChange = onTextChange, + label = { Text(text = label) }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Send + ), + trailingIcon = { + Icon( + imageVector = if (visibility) Icons.Outlined.VisibilityOff + else Icons.Outlined.Visibility, + contentDescription = "Visibility Icon", + modifier = Modifier + .testTag("Visibility Icon") + .size(SettingsDimension.itemIconSize) + .toggleable(visibility) { + visibility = !visibility + }, + ) + }, + visualTransformation = if (visibility) VisualTransformation.None + else PasswordVisualTransformation() + ) +} + +@Preview +@Composable +private fun SettingsTextFieldPasswordPreview() { + var value by remember { mutableStateOf("value") } + SettingsTheme { + SettingsTextFieldPassword( + value = value, + label = "label", + onTextChange = { value = it }, + ) + } +}
\ No newline at end of file diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt index b77368a429ae..3a0e51b24c2c 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt @@ -32,8 +32,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material3.ExperimentalMaterial3Api @@ -49,10 +49,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds @@ -73,7 +71,6 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp -import com.android.settingslib.spa.framework.compose.horizontalValues import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsTheme import kotlin.math.abs @@ -129,16 +126,10 @@ internal fun CustomizedLargeTopAppBar( private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) { Text( text = title, - modifier = Modifier - .padding( - WindowInsets.navigationBars - .asPaddingValues() - .horizontalValues() - ) - .padding( - start = SettingsDimension.itemPaddingAround, - end = SettingsDimension.itemPaddingEnd, - ), + modifier = Modifier.padding( + start = SettingsDimension.itemPaddingAround, + end = SettingsDimension.itemPaddingEnd, + ), overflow = TextOverflow.Ellipsis, maxLines = maxLines, ) @@ -157,6 +148,15 @@ private fun topAppBarColors() = TopAppBarColors( * Represents the colors used by a top app bar in different states. * This implementation animates the container color according to the top app bar scroll state. It * does not animate the leading, headline, or trailing colors. + * + * @constructor create an instance with arbitrary colors, see [TopAppBarColors] for a + * factory method using the default material3 spec + * @param containerColor the color used for the background of this BottomAppBar. Use + * [Color.Transparent] to have no color. + * @param scrolledContainerColor the container color when content is scrolled behind it + * @param navigationIconContentColor the content color used for the navigation icon + * @param titleContentColor the content color used for the title + * @param actionIconContentColor the content color used for actions */ @Stable private class TopAppBarColors( @@ -248,7 +248,6 @@ private fun SingleRowTopAppBar( titleTextStyle = titleTextStyle, titleAlpha = 1f, titleVerticalArrangement = Arrangement.Center, - titleHorizontalArrangement = Arrangement.Start, titleBottomPadding = 0, hideTitleSemantics = false, navigationIcon = navigationIcon, @@ -312,7 +311,7 @@ private fun TwoRowsTopAppBar( // This will potentially animate or interpolate a transition between the container color and the // container's scrolled color according to the app bar's scroll state. val colorTransitionFraction = scrollBehavior?.state?.collapsedFraction ?: 0f - val appBarContainerColor by rememberUpdatedState(colors.containerColor(colorTransitionFraction)) + val appBarContainerColor = colors.containerColor(colorTransitionFraction) // Wrap the given actions in a Row. val actionsRow = @Composable { @@ -364,14 +363,17 @@ private fun TwoRowsTopAppBar( titleTextStyle = smallTitleTextStyle, titleAlpha = topTitleAlpha, titleVerticalArrangement = Arrangement.Center, - titleHorizontalArrangement = Arrangement.Start, titleBottomPadding = 0, hideTitleSemantics = hideTopRowSemantics, navigationIcon = navigationIcon, actions = actionsRow, ) TopAppBarLayout( - modifier = Modifier.clipToBounds(), + modifier = Modifier + // only apply the horizontal sides of the window insets padding, since the top + // padding will always be applied by the layout above + .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Horizontal)) + .clipToBounds(), heightPx = maxHeightPx.floatValue - pinnedHeightPx + (scrollBehavior?.state?.heightOffset ?: 0f), navigationIconContentColor = colors.navigationIconContentColor, @@ -392,7 +394,6 @@ private fun TwoRowsTopAppBar( titleTextStyle = titleTextStyle, titleAlpha = bottomTitleAlpha, titleVerticalArrangement = Arrangement.Bottom, - titleHorizontalArrangement = Arrangement.Start, titleBottomPadding = titleBottomPaddingPx, hideTitleSemantics = hideBottomRowSemantics, navigationIcon = {}, @@ -419,7 +420,6 @@ private fun TwoRowsTopAppBar( * @param modifier a [Modifier] * @param titleAlpha the title's alpha * @param titleVerticalArrangement the title's vertical arrangement - * @param titleHorizontalArrangement the title's horizontal arrangement * @param titleBottomPadding the title's bottom padding * @param hideTitleSemantics hides the title node from the semantic tree. Apply this * boolean when this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics @@ -440,7 +440,6 @@ private fun TopAppBarLayout( titleTextStyle: TextStyle, titleAlpha: Float, titleVerticalArrangement: Arrangement.Vertical, - titleHorizontalArrangement: Arrangement.Horizontal, titleBottomPadding: Int, hideTitleSemantics: Boolean, navigationIcon: @Composable () -> Unit, @@ -470,10 +469,10 @@ private fun TopAppBarLayout( CompositionLocalProvider( LocalContentColor provides titleContentColor, LocalDensity provides with(LocalDensity.current) { - Density( - density = density, - fontScale = if (titleScaleDisabled) 1f else fontScale, - ) + Density( + density = density, + fontScale = if (titleScaleDisabled) 1f else fontScale, + ) }, content = title ) @@ -528,15 +527,7 @@ private fun TopAppBarLayout( // Title titlePlaceable.placeRelative( - x = when (titleHorizontalArrangement) { - Arrangement.Center -> (constraints.maxWidth - titlePlaceable.width) / 2 - Arrangement.End -> - constraints.maxWidth - titlePlaceable.width - actionIconsPlaceable.width - // Arrangement.Start. - // A TopAppBarTitleInset will make sure the title is offset in case the - // navigation icon is missing. - else -> max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width) - }, + x = max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width), y = when (titleVerticalArrangement) { Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2 // Apply bottom padding from the title's baseline only when the Arrangement is diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt index 58bc72213a1b..b0271ae1d98c 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt @@ -45,11 +45,12 @@ class SettingsExposedDropdownMenuCheckBoxTest { @Test fun exposedDropdownMenuCheckBox_displayed() { composeTestRule.setContent { - SettingsExposedDropdownMenuCheckBox(label = exposedDropdownMenuCheckBoxLabel, + SettingsExposedDropdownMenuCheckBox( + label = exposedDropdownMenuCheckBoxLabel, options = options, selectedOptionsState = remember { selectedOptionsState1 }, enabled = true, - onselectedOptionStateChange = {}) + ) {} } composeTestRule.onNodeWithText( exposedDropdownMenuCheckBoxLabel, substring = true @@ -59,11 +60,12 @@ class SettingsExposedDropdownMenuCheckBoxTest { @Test fun exposedDropdownMenuCheckBox_expanded() { composeTestRule.setContent { - SettingsExposedDropdownMenuCheckBox(label = exposedDropdownMenuCheckBoxLabel, + SettingsExposedDropdownMenuCheckBox( + label = exposedDropdownMenuCheckBoxLabel, options = options, selectedOptionsState = remember { selectedOptionsState1 }, enabled = true, - onselectedOptionStateChange = {}) + ) {} } composeTestRule.onNodeWithText(item3, substring = true).assertDoesNotExist() composeTestRule.onNodeWithText(exposedDropdownMenuCheckBoxLabel, substring = true) @@ -74,11 +76,12 @@ class SettingsExposedDropdownMenuCheckBoxTest { @Test fun exposedDropdownMenuCheckBox_valueAdded() { composeTestRule.setContent { - SettingsExposedDropdownMenuCheckBox(label = exposedDropdownMenuCheckBoxLabel, + SettingsExposedDropdownMenuCheckBox( + label = exposedDropdownMenuCheckBoxLabel, options = options, selectedOptionsState = remember { selectedOptionsState1 }, enabled = true, - onselectedOptionStateChange = {}) + ) {} } composeTestRule.onNodeWithText(item3, substring = true).assertDoesNotExist() composeTestRule.onNodeWithText(exposedDropdownMenuCheckBoxLabel, substring = true) @@ -90,11 +93,12 @@ class SettingsExposedDropdownMenuCheckBoxTest { @Test fun exposedDropdownMenuCheckBox_valueDeleted() { composeTestRule.setContent { - SettingsExposedDropdownMenuCheckBox(label = exposedDropdownMenuCheckBoxLabel, + SettingsExposedDropdownMenuCheckBox( + label = exposedDropdownMenuCheckBoxLabel, options = options, selectedOptionsState = remember { selectedOptionsState1 }, enabled = true, - onselectedOptionStateChange = {}) + ) {} } composeTestRule.onNodeWithText(item2, substring = true).assertIsDisplayed() composeTestRule.onNodeWithText(exposedDropdownMenuCheckBoxLabel, substring = true) diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPasswordTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPasswordTest.kt new file mode 100644 index 000000000000..6f2d1f98f6cf --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPasswordTest.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget.editor + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertTextContains +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextReplacement +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SettingsTextFieldPasswordTest { + @get:Rule + val composeTestRule = createComposeRule() + private val label = "label" + private val value = "value" + private val valueChanged = "Value Changed" + private val visibilityIconTag = "Visibility Icon" + + @Test + fun textFieldPassword_displayed() { + composeTestRule.setContent { + SettingsTextFieldPassword( + value = value, + label = label, + onTextChange = {}) + } + composeTestRule.onNodeWithText(label, substring = true) + .assertIsDisplayed() + } + + @Test + fun textFieldPassword_invisible() { + composeTestRule.setContent { + var value by remember { mutableStateOf(value) } + SettingsTextFieldPassword( + value = value, + label = label, + onTextChange = { value = it }) + } + composeTestRule.onNodeWithText(value, substring = true) + .assertDoesNotExist() + } + + @Test + fun textFieldPassword_visible_inputValue() { + composeTestRule.setContent { + var value by remember { mutableStateOf(value) } + SettingsTextFieldPassword( + value = value, + label = label, + onTextChange = { value = it }) + } + composeTestRule.onNodeWithTag(visibilityIconTag) + .performClick() + composeTestRule.onNodeWithText(label, substring = true) + .performTextReplacement(valueChanged) + composeTestRule.onNodeWithText(label, substring = true) + .assertTextContains(valueChanged) + } +}
\ No newline at end of file diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt index a6a5ed229756..3dac7dbc005d 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt @@ -22,7 +22,6 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -307,8 +306,8 @@ class CustomizedAppBarTest { } /** - * Checks the app bar's components positioning when it's a [CustomizedTopAppBar], a - * [CenterAlignedTopAppBar], or a larger app bar that is scrolled up and collapsed into a small + * Checks the app bar's components positioning when it's a [CustomizedTopAppBar] + * or a larger app bar that is scrolled up and collapsed into a small * configuration and there is no navigation icon. */ private fun assertSmallPositioningWithoutNavigation(isCenteredTitle: Boolean = false) { @@ -335,8 +334,7 @@ class CustomizedAppBarTest { } /** - * Checks the app bar's components positioning when it's a [CustomizedTopAppBar] or a - * [CenterAlignedTopAppBar]. + * Checks the app bar's components positioning when it's a [CustomizedTopAppBar]. */ private fun assertSmallDefaultPositioning(isCenteredTitle: Boolean = false) { val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot() diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index 91b852ab9f67..70126ace7d4c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -198,19 +198,19 @@ public class A2dpProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null) { return false; } if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } boolean isA2dpPlaying() { if (mService == null) return false; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java index c7a5bd86c1cd..38cd04e8a0a3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java @@ -140,19 +140,19 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null) { return false; } if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } boolean isAudioPlaying() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java index c3f845ca8c0a..45cafc6bf6cb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java @@ -178,19 +178,19 @@ public class CsipSetCoordinatorProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null || device == null) { return false; } if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java index 6b7f733b5413..660090ddef2d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java @@ -258,19 +258,19 @@ public class HapClientProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null || device == null) { return false; } if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index 7e5c1240cc63..b79f147c321f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -165,19 +165,19 @@ public class HeadsetProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null) { return false; } if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index 5fbb4c3e5712..14fab16de3e6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -231,19 +231,19 @@ public class HearingAidProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null || device == null) { return false; } if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java index 66225a2bffca..b0e0049a5ed4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java @@ -150,19 +150,19 @@ final class HfpClientProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null) { return false; } if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java index 8a2c4f8a1230..5468efbdbd3e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java @@ -123,13 +123,13 @@ public class HidDeviceProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; // if set preferred to false, then disconnect to the current device if (!enabled) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java index 3c24b4a095b9..5b91ac9d3dab 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java @@ -126,19 +126,19 @@ public class HidProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null) { return false; } if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java index e781f1307072..a93524f6d873 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java @@ -24,24 +24,18 @@ import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; -import android.bluetooth.BluetoothCodecConfig; -import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothUuid; import android.content.Context; import android.os.Build; -import android.os.ParcelUuid; import android.util.Log; import androidx.annotation.RequiresApi; -import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.R; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; public class LeAudioProfile implements LocalBluetoothProfile { @@ -233,19 +227,19 @@ public class LeAudioProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null || device == null) { return false; } if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java index 4881a86b398a..0a803bc7825d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java @@ -137,19 +137,19 @@ public final class MapClientProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null) { return false; } if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java index 75c1926683ef..db1ba5e3741f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java @@ -138,19 +138,19 @@ public class MapProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null) { return false; } if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java index 767df352b70f..dec862b07a6a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java @@ -105,7 +105,7 @@ public class PanProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null) { return false; } @@ -117,12 +117,12 @@ public class PanProfile implements LocalBluetoothProfile { mService.setConnectionPolicy(sink, CONNECTION_POLICY_FORBIDDEN); } } - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } else { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java index 0d11293a01b7..65b89baaea81 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java @@ -151,19 +151,19 @@ public final class PbapClientProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null) { return false; } if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java index 9e2e4a14124a..784b821745bd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java @@ -108,16 +108,16 @@ public class PbapServerProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null) { return false; } if (!enabled) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } public String toString() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java index 104f1d738000..0f8892f760c1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java @@ -136,19 +136,19 @@ final class SapProfile implements LocalBluetoothProfile { @Override public boolean setEnabled(BluetoothDevice device, boolean enabled) { - boolean isEnabled = false; + boolean isSuccessful = false; if (mService == null) { return false; } if (enabled) { if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); + isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } - return isEnabled; + return isSuccessful; } public List<BluetoothDevice> getConnectedDevices() { diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java deleted file mode 100644 index 54d5c3d63a5d..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.net; - -import android.app.usage.NetworkStats; -import android.app.usage.NetworkStatsManager; -import android.content.Context; -import android.net.NetworkTemplate; -import android.util.Log; - -import androidx.loader.content.AsyncTaskLoader; - -/** - * Loader for retrieving the network stats summary for all UIDs. - */ -public class NetworkStatsSummaryLoader extends AsyncTaskLoader<NetworkStats> { - - private static final String TAG = "NetworkDetailLoader"; - private final NetworkStatsManager mNetworkStatsManager; - private final long mStart; - private final long mEnd; - private final NetworkTemplate mNetworkTemplate; - - private NetworkStatsSummaryLoader(Builder builder) { - super(builder.mContext); - mStart = builder.mStart; - mEnd = builder.mEnd; - mNetworkTemplate = builder.mNetworkTemplate; - mNetworkStatsManager = (NetworkStatsManager) - builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE); - } - - @Override - protected void onStartLoading() { - super.onStartLoading(); - forceLoad(); - } - - @Override - public NetworkStats loadInBackground() { - try { - return mNetworkStatsManager.querySummary(mNetworkTemplate, mStart, mEnd); - } catch (RuntimeException e) { - Log.e(TAG, "Exception querying network detail.", e); - return null; - } - } - - @Override - protected void onStopLoading() { - super.onStopLoading(); - cancelLoad(); - } - - @Override - protected void onReset() { - super.onReset(); - cancelLoad(); - } - - public static class Builder { - private final Context mContext; - private long mStart; - private long mEnd; - private NetworkTemplate mNetworkTemplate; - - public Builder(Context context) { - mContext = context; - } - - public Builder setStartTime(long start) { - mStart = start; - return this; - } - - public Builder setEndTime(long end) { - mEnd = end; - return this; - } - - /** - * Set {@link NetworkTemplate} for builder - */ - public Builder setNetworkTemplate(NetworkTemplate template) { - mNetworkTemplate = template; - return this; - } - - public NetworkStatsSummaryLoader build() { - return new NetworkStatsSummaryLoader(this); - } - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/net/UidDetail.java b/packages/SettingsLib/src/com/android/settingslib/net/UidDetail.java index 6fba0a169219..77789c268f79 100644 --- a/packages/SettingsLib/src/com/android/settingslib/net/UidDetail.java +++ b/packages/SettingsLib/src/com/android/settingslib/net/UidDetail.java @@ -24,5 +24,5 @@ public class UidDetail { public CharSequence[] detailLabels; public CharSequence[] detailContentDescriptions; public Drawable icon; - public CharSequence packageName; + public String packageName; } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index ffe28a6a5202..15620b7023e2 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -23,6 +23,7 @@ > <!-- Standard permissions granted to the shell. --> + <uses-permission android:name="android.permission.CAMERA_HEADLESS_SYSTEM_USER" /> <uses-permission android:name="android.permission.MANAGE_HEALTH_PERMISSIONS" /> <uses-permission android:name="android.permission.MANAGE_HEALTH_DATA" /> <uses-permission android:name="android.permission.health.READ_EXERCISE_ROUTE" /> @@ -852,6 +853,9 @@ <!-- Permission required for accessing all content provider mime types --> <uses-permission android:name="android.permission.GET_ANY_PROVIDER_TYPE" /> + <!-- Permission required for CTS-in-sandbox tests --> + <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX" /> + <!-- Permission required for CTS test - CtsWallpaperTestCases --> <uses-permission android:name="android.permission.ALWAYS_UPDATE_WALLPAPER" /> diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 78da5a699759..8f329b3dddd8 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -4,7 +4,6 @@ set noparent dsandler@android.com -aaliomer@google.com aaronjli@google.com achalke@google.com acul@google.com @@ -36,6 +35,7 @@ gallmann@google.com gwasserman@google.com hwwang@google.com hyunyoungs@google.com +ikateryna@google.com jaggies@google.com jamesoleary@google.com jbolinger@google.com diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig index 03cbc169a90f..0f55f35adc4e 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig +++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig @@ -5,4 +5,11 @@ flag { namespace: "accessibility" description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons." bug: "298467628" +} + +flag { + name: "a11y_menu_hide_before_taking_action" + namespace: "accessibility" + description: "Hides the AccessibilityMenuService UI before taking action instead of after." + bug: "292020123" }
\ No newline at end of file diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml index 587395d4f636..fd9a9c634a36 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml @@ -15,7 +15,7 @@ android:layout_centerHorizontal="true" android:scaleType="fitCenter"/> -<TextView + <TextView android:id="@+id/shortcutLabel" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -27,6 +27,6 @@ android:lines="2" android:textSize="@dimen/label_text_size" android:textAlignment="center" - android:textAppearance="@android:style/TextAppearance.DeviceDefault.Widget.Button"/> + android:textColor="@color/grid_item_text_color"/> </RelativeLayout> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml index fbf2b071ea3c..f961bdbac0aa 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml @@ -20,5 +20,6 @@ <color name="footer_icon_disabled_color">#5F6368</color> <color name="colorControlNormal">#202124</color> <color name="snackbar_bg_color">@android:color/white</color> + <color name="grid_item_text_color">@android:color/white</color> </resources> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml index d81d0d511a87..9722eb04130f 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml @@ -21,6 +21,7 @@ <color name="footer_icon_disabled_color">#ddd</color> <color name="colorControlNormal">@android:color/white</color> <color name="snackbar_bg_color">#313235</color> + <color name="grid_item_text_color">@android:color/black</color> <color name="colorAccessibilityMenuIcon">#3AA757</color> </resources> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java index 27aade5e6bf8..008732e787f1 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java @@ -260,6 +260,27 @@ public class AccessibilityMenuService extends AccessibilityService // Shortcuts are repeatable in a11y menu rather than unique, so use tag ID to handle. int viewTag = (int) view.getTag(); + // First check if this was a shortcut which should keep a11y menu visible. If so, + // perform the shortcut and return without hiding the UI. + if (viewTag == ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()) { + adjustBrightness(BRIGHTNESS_UP_INCREMENT_GAMMA); + return; + } else if (viewTag == ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()) { + adjustBrightness(BRIGHTNESS_DOWN_INCREMENT_GAMMA); + return; + } else if (viewTag == ShortcutId.ID_VOLUME_UP_VALUE.ordinal()) { + adjustVolume(AudioManager.ADJUST_RAISE); + return; + } else if (viewTag == ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal()) { + adjustVolume(AudioManager.ADJUST_LOWER); + return; + } + + if (Flags.a11yMenuHideBeforeTakingAction()) { + // Hide the a11y menu UI before performing the following shortcut actions. + mA11yMenuLayout.hideMenu(); + } + if (viewTag == ShortcutId.ID_ASSISTANT_VALUE.ordinal()) { // Always restart the voice command activity, so that the UI is reloaded. startActivityIfIntentIsSafe( @@ -281,21 +302,11 @@ public class AccessibilityMenuService extends AccessibilityService performGlobalActionInternal(GLOBAL_ACTION_NOTIFICATIONS); } else if (viewTag == ShortcutId.ID_SCREENSHOT_VALUE.ordinal()) { performGlobalActionInternal(GLOBAL_ACTION_TAKE_SCREENSHOT); - } else if (viewTag == ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()) { - adjustBrightness(BRIGHTNESS_UP_INCREMENT_GAMMA); - return; - } else if (viewTag == ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()) { - adjustBrightness(BRIGHTNESS_DOWN_INCREMENT_GAMMA); - return; - } else if (viewTag == ShortcutId.ID_VOLUME_UP_VALUE.ordinal()) { - adjustVolume(AudioManager.ADJUST_RAISE); - return; - } else if (viewTag == ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal()) { - adjustVolume(AudioManager.ADJUST_LOWER); - return; } - mA11yMenuLayout.hideMenu(); + if (!Flags.a11yMenuHideBeforeTakingAction()) { + mA11yMenuLayout.hideMenu(); + } } /** diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt index 767756e17747..17c74ba7b12f 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt @@ -263,9 +263,11 @@ internal class ExpandableControllerImpl( override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { delegate.onLaunchAnimationStart(isExpandingFullyAbove) overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay + cujType?.let { InteractionJankMonitor.getInstance().begin(composeViewRoot, it) } } override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + cujType?.let { InteractionJankMonitor.getInstance().end(it) } delegate.onLaunchAnimationEnd(isExpandingFullyAbove) overlay.value = null } @@ -320,9 +322,8 @@ internal class ExpandableControllerImpl( } override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? { - // TODO(b/252723237): Add support for jank monitoring when animating from a - // Composable. - return null + val type = cuj?.cujType ?: return null + return InteractionJankMonitor.Configuration.Builder.withView(type, composeViewRoot) } } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt index 31e7d7c7c6ba..73b366ef57bc 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt @@ -72,15 +72,16 @@ internal class PunchHole( } private fun DrawScope.drawHole(bounds: Element) { + val boundsSize = bounds.lastSize.toSize() if (shape == RectangleShape) { - drawRect(Color.Black, blendMode = BlendMode.DstOut) + drawRect(Color.Black, size = boundsSize, blendMode = BlendMode.DstOut) return } // TODO(b/290184746): Cache outline if the size of bounds does not change. drawOutline( shape.createOutline( - bounds.lastSize.toSize(), + boundsSize, layoutDirection, this, ), diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt index 92d2bd253a83..9d62e387500f 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt @@ -163,9 +163,6 @@ object CustomizationProviderContract { const val TABLE_NAME = "flags" val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build() - /** Flag denoting whether the Wallpaper Picker should use the new, revamped UI. */ - const val FLAG_NAME_REVAMPED_WALLPAPER_UI = "revamped_wallpaper_ui" - /** * Flag denoting whether the customizable lock screen quick affordances feature is enabled. */ diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags index 75de94321bfe..e2d889168516 100644 --- a/packages/SystemUI/proguard_common.flags +++ b/packages/SystemUI/proguard_common.flags @@ -1,16 +1,8 @@ -# Preserve line number information for debugging stack traces. --keepattributes SourceFile,LineNumberTable - -keep class com.android.systemui.VendorServices # the `#inject` methods are accessed via reflection to work on ContentProviders -keepclassmembers class * extends com.android.systemui.dagger.SysUIComponent { void inject(***); } -# Needed for builds to properly initialize KeyFrames from xml scene --keepclassmembers class * extends androidx.constraintlayout.motion.widget.Key { - public <init>(); -} - # Needed to ensure callback field references are kept in their respective # owning classes when the downstream callback registrars only store weak refs. # TODO(b/264686688): Handle these cases with more targeted annotations. @@ -59,7 +51,6 @@ public <init>(android.content.Context, android.util.AttributeSet); } --keep class ** extends androidx.preference.PreferenceFragment -keep class com.android.systemui.tuner.* # The plugins and core log subpackages act as shared libraries that might be referenced in diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml new file mode 100644 index 000000000000..173d57b335f0 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml @@ -0,0 +1,129 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** 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. +*/ +--> + +<!-- This file is needed when flag lockscreen.enable_landscape is on + Required for landscape lockscreen on small screens. --> +<com.android.keyguard.KeyguardPasswordView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/res-auto" + android:id="@+id/keyguard_password_view" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center_horizontal|bottom" + android:gravity="bottom"> + + <!-- Layout here is visually identical to the previous keyguard_password_view. + I.E., 'constraints here effectively the same as the previous linear layout' --> + <androidx.constraintlayout.motion.widget.MotionLayout + android:id="@+id/password_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:maxWidth="@dimen/keyguard_security_width" + android:layout_gravity="center_horizontal" + android:clipChildren="false" + android:clipToPadding="false" + android:layoutDirection="ltr" + android:orientation="vertical" + androidprv:layoutDescription="@xml/keyguard_password_scene"> + + <!-- Guideline need to align password right of centre, + when on small screen landscape layout --> + <androidx.constraintlayout.widget.Guideline + android:id="@+id/password_center_guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + androidprv:layout_constraintGuide_percent="0.5" /> + + <LinearLayout + android:id="@+id/keyguard_bouncer_message_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:clipToPadding="false" + android:layoutDirection="ltr" + android:orientation="vertical" + androidprv:layout_constraintTop_toTopOf="parent"> + + <include layout="@layout/keyguard_bouncer_message_area" /> + + <com.android.systemui.bouncer.ui.BouncerMessageView + android:id="@+id/bouncer_message_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" /> + + </LinearLayout> + + <FrameLayout + android:id="@+id/passwordEntry_container" + android:layout_width="280dp" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:theme="?attr/passwordStyle" + androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container" + androidprv:layout_constraintEnd_toEndOf="parent" + androidprv:layout_constraintHorizontal_bias="0.5" + androidprv:layout_constraintStart_toStartOf="parent" + androidprv:layout_constraintTop_toBottomOf="@+id/keyguard_bouncer_message_container" + androidprv:layout_constraintVertical_bias="0.7777"> + + <EditText + android:id="@+id/passwordEntry" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:contentDescription="@string/keyguard_accessibility_password" + android:gravity="center_horizontal" + android:imeOptions="flagForceAscii|actionDone" + android:inputType="textPassword" + android:maxLength="500" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textCursorDrawable="@null" + android:textSize="16sp" + android:textStyle="normal" /> + + <ImageView + android:id="@+id/switch_ime_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end|center_vertical" + android:layout_marginBottom="12dp" + android:background="?android:attr/selectableItemBackground" + android:clickable="true" + android:contentDescription="@string/accessibility_ime_switch_button" + android:padding="8dip" + android:src="@drawable/ic_lockscreen_ime" + android:tint="?android:attr/textColorPrimary" + android:visibility="gone" /> + </FrameLayout> + + <include + android:id="@+id/keyguard_selector_fade_container" + layout="@layout/keyguard_eca" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="12dp" + android:orientation="vertical" + androidprv:layout_constraintBottom_toBottomOf="parent" /> + + </androidx.constraintlayout.motion.widget.MotionLayout> + +</com.android.keyguard.KeyguardPasswordView>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml new file mode 100644 index 000000000000..b562d7b5ee48 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** 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. +*/ +--> + +<!-- This file is needed when flag lockscreen.enable_landscape is on + Required for landscape lockscreen on small screens. --> +<com.android.keyguard.KeyguardPatternView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/res-auto" + android:id="@+id/keyguard_pattern_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center_horizontal|bottom" + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="vertical"> + + <!-- Layout here is visually identical to the previous keyguard_pattern_view. + I.E., 'constraints here effectively the same as the previous linear layout' --> + <androidx.constraintlayout.motion.widget.MotionLayout + android:id="@+id/pattern_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center_horizontal" + android:clipChildren="false" + android:clipToPadding="false" + android:layoutDirection="ltr" + android:orientation="vertical" + android:maxWidth = "@dimen/biometric_auth_pattern_view_max_size" + androidprv:layoutDescription="@xml/keyguard_pattern_scene"> + + <!-- Guideline need to align pattern right of centre, + when on small screen landscape layout --> + <androidx.constraintlayout.widget.Guideline + android:id="@+id/pattern_center_guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + androidprv:layout_constraintGuide_percent="0.5" /> + + <LinearLayout + android:id="@+id/keyguard_bouncer_message_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:clipToPadding="false" + android:layoutDirection="ltr" + android:orientation="vertical" + androidprv:layout_constraintTop_toTopOf="parent"> + + <include layout="@layout/keyguard_bouncer_message_area" /> + + <com.android.systemui.bouncer.ui.BouncerMessageView + android:id="@+id/bouncer_message_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" /> + + </LinearLayout> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/pattern_top_guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + androidprv:layout_constraintGuide_percent="0" /> + + <com.android.internal.widget.LockPatternView + android:id="@+id/lockPatternView" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginBottom="8dp" + androidprv:layout_constraintVertical_bias="1.0" + androidprv:layout_constraintDimensionRatio="1.0" + androidprv:layout_constraintStart_toStartOf="parent" + androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container" + androidprv:layout_constraintEnd_toEndOf="parent" + androidprv:layout_constraintVertical_chainStyle="packed" + androidprv:layout_constraintTop_toBottomOf="@id/pattern_top_guideline"/> + + <include + android:id="@+id/keyguard_selector_fade_container" + layout="@layout/keyguard_eca" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin" + android:layout_marginTop="@dimen/keyguard_eca_top_margin" + android:orientation="vertical" + androidprv:layout_constraintBottom_toBottomOf="parent" + androidprv:layout_constraintTop_toBottomOf="@+id/lockPatternView" /> + + </androidx.constraintlayout.motion.widget.MotionLayout> + +</com.android.keyguard.KeyguardPatternView>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml index 6835d59658d4..6c79d5a6095a 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml @@ -34,6 +34,7 @@ android:id="@+id/pin_container" android:layout_width="match_parent" android:layout_height="match_parent" + android:maxWidth="@dimen/keyguard_security_width" android:clipChildren="false" android:clipToPadding="false" android:layoutDirection="ltr" diff --git a/packages/SystemUI/res-keyguard/xml/keyguard_password_scene.xml b/packages/SystemUI/res-keyguard/xml/keyguard_password_scene.xml new file mode 100644 index 000000000000..092e10d25c00 --- /dev/null +++ b/packages/SystemUI/res-keyguard/xml/keyguard_password_scene.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<MotionScene + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:motion="http://schemas.android.com/apk/res-auto" + xmlns:androidprv="http://schemas.android.com/apk/res-auto"> + + <Transition + motion:constraintSetStart="@id/single_constraints" + motion:constraintSetEnd="@+id/split_constraints" + motion:duration="0" + motion:autoTransition="none" /> + + <!-- No changes to default layout --> + <ConstraintSet android:id="@+id/single_constraints" /> + + <ConstraintSet android:id="@+id/split_constraints"> + + <Constraint + android:id="@+id/keyguard_bouncer_message_container" + android:layout_width="0dp" + android:layout_height="wrap_content" + androidprv:layout_constraintEnd_toStartOf="@+id/password_center_guideline" + androidprv:layout_constraintStart_toStartOf="parent" + androidprv:layout_constraintTop_toTopOf="parent" + androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container" + androidprv:layout_constraintVertical_chainStyle="spread_inside" /> + <Constraint + android:id="@+id/passwordEntry_container" + android:layout_width="280dp" + android:layout_height="wrap_content" + androidprv:layout_constraintVertical_bias="0.5" + androidprv:layout_constraintHorizontal_bias="0.5" + androidprv:layout_constraintEnd_toEndOf="parent" + androidprv:layout_constraintStart_toStartOf="@+id/password_center_guideline" + androidprv:layout_constraintBottom_toBottomOf="parent" + androidprv:layout_constraintTop_toTopOf="parent"/> + <Constraint + android:id="@+id/keyguard_selector_fade_container" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/keyguard_eca_top_margin" + android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin" + androidprv:layout_constraintBottom_toBottomOf="parent" + androidprv:layout_constraintEnd_toStartOf="@+id/password_center_guideline" + androidprv:layout_constraintStart_toStartOf="parent" + androidprv:layout_constraintTop_toBottomOf="@+id/keyguard_bouncer_message_container" /> + + </ConstraintSet> +</MotionScene>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/xml/keyguard_pattern_scene.xml b/packages/SystemUI/res-keyguard/xml/keyguard_pattern_scene.xml new file mode 100644 index 000000000000..6112411402c4 --- /dev/null +++ b/packages/SystemUI/res-keyguard/xml/keyguard_pattern_scene.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<MotionScene + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:motion="http://schemas.android.com/apk/res-auto" + xmlns:androidprv="http://schemas.android.com/apk/res-auto"> + + <Transition + motion:constraintSetStart="@id/single_constraints" + motion:constraintSetEnd="@+id/split_constraints" + motion:duration="0" + motion:autoTransition="none"/> + + <!-- No changes to default layout --> + <ConstraintSet android:id="@+id/single_constraints"/> + + <ConstraintSet android:id="@+id/split_constraints"> + + <Constraint + android:id="@+id/keyguard_bouncer_message_container" + android:layout_width="0dp" + android:layout_height="wrap_content" + androidprv:layout_constraintEnd_toStartOf="@+id/pattern_center_guideline" + androidprv:layout_constraintStart_toStartOf="parent" + androidprv:layout_constraintTop_toTopOf="parent" /> + <Constraint + android:id="@+id/lockPatternView" + android:layout_width="0dp" + android:layout_height="0dp" + androidprv:layout_constraintDimensionRatio="1.0" + androidprv:layout_constraintVertical_bias="0.5" + androidprv:layout_constraintBottom_toBottomOf="parent" + androidprv:layout_constraintEnd_toEndOf="parent" + androidprv:layout_constraintStart_toStartOf="@+id/pattern_center_guideline" + androidprv:layout_constraintTop_toTopOf="parent" + android:layout_marginBottom="0dp" /> + <Constraint + android:id="@+id/keyguard_selector_fade_container" + android:layout_width="0dp" + android:layout_height="wrap_content" + androidprv:layout_constraintBottom_toBottomOf="parent" + androidprv:layout_constraintEnd_toStartOf="@+id/pattern_center_guideline" + androidprv:layout_constraintStart_toStartOf="parent" + android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin" + android:layout_marginTop="@dimen/keyguard_eca_top_margin" /> + + </ConstraintSet> +</MotionScene>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml b/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml index 44af9efffbf8..2a1270c80b75 100644 --- a/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml +++ b/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml @@ -26,12 +26,10 @@ motion:constraintSetStart="@id/single_constraints" motion:constraintSetEnd="@+id/split_constraints" motion:duration="0" - motion:autoTransition="none"> - </Transition> + motion:autoTransition="none"/> - <ConstraintSet android:id="@+id/single_constraints"> - <!-- No changes to default layout --> - </ConstraintSet> + <!-- No changes to default layout --> + <ConstraintSet android:id="@+id/single_constraints"/> <ConstraintSet android:id="@+id/split_constraints"> diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml index 3a15ae4f17ff..60a78d6346f1 100644 --- a/packages/SystemUI/res/layout/combined_qs_header.xml +++ b/packages/SystemUI/res/layout/combined_qs_header.xml @@ -118,34 +118,37 @@ frame when animating QS <-> QQS transition app:layout_constraintStart_toEndOf="@id/date" app:layout_constraintTop_toTopOf="@id/clock" /> - <LinearLayout + <FrameLayout android:id="@+id/shade_header_system_icons" android:layout_width="wrap_content" android:layout_height="@dimen/shade_header_system_icons_height" - android:clickable="true" - android:orientation="horizontal" - android:gravity="center_vertical" - android:paddingStart="@dimen/shade_header_system_icons_padding_start" - android:paddingEnd="@dimen/shade_header_system_icons_padding_end" - android:paddingTop="@dimen/shade_header_system_icons_padding_top" - android:paddingBottom="@dimen/shade_header_system_icons_padding_bottom" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/privacy_container" app:layout_constraintTop_toTopOf="@id/clock"> - - <com.android.systemui.statusbar.phone.StatusIconContainer - android:id="@+id/statusIcons" - android:layout_width="0dp" - android:layout_weight="1" - android:layout_height="wrap_content" - android:paddingEnd="@dimen/signal_cluster_battery_padding" /> - - <com.android.systemui.battery.BatteryMeterView - android:id="@+id/batteryRemainingIcon" + <LinearLayout + android:id="@+id/hover_system_icons_container" android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:textAppearance="@style/TextAppearance.QS.Status" /> - </LinearLayout> + android:layout_height="match_parent" + android:layout_gravity="right|center_vertical" + android:paddingStart="@dimen/hover_system_icons_container_padding_start" + android:paddingEnd="@dimen/hover_system_icons_container_padding_end" + android:paddingTop="@dimen/hover_system_icons_container_padding_top" + android:paddingBottom="@dimen/hover_system_icons_container_padding_bottom"> + + <com.android.systemui.statusbar.phone.StatusIconContainer + android:id="@+id/statusIcons" + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="wrap_content" + android:paddingEnd="@dimen/signal_cluster_battery_padding" /> + + <com.android.systemui.battery.BatteryMeterView + android:id="@+id/batteryRemainingIcon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:textAppearance="@style/TextAppearance.QS.Status" /> + </LinearLayout> + </FrameLayout> <FrameLayout android:id="@+id/privacy_container" diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml index d800d49634d2..85fb3ac577bc 100644 --- a/packages/SystemUI/res/values-land/config.xml +++ b/packages/SystemUI/res/values-land/config.xml @@ -42,4 +42,8 @@ <!-- Whether we use large screen shade header which takes only one row compared to QS header --> <bool name="config_use_large_screen_shade_header">true</bool> + <!-- Whether to force split shade. + For now, this value has effect only when flag lockscreen.enable_landscape is enabled. + TODO (b/293290851) - change this comment/resource when flag is enabled --> + <bool name="force_config_use_split_notification_shade">true</bool> </resources> diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index 0667cd8b12d6..259b9adf0209 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -88,4 +88,7 @@ overlaid --> <dimen name="global_actions_button_size">72dp</dimen> <dimen name="global_actions_button_padding">26dp</dimen> + + <dimen name="keyguard_indication_margin_bottom">8dp</dimen> + <dimen name="lock_icon_margin_bottom">24dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index 588638f3dea5..e63229aea70b 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -36,4 +36,8 @@ <integer name="power_menu_lite_max_columns">3</integer> <integer name="power_menu_lite_max_rows">2</integer> + <!-- Whether to force split shade. + For now, this value has effect only when flag lockscreen.enable_landscape is enabled. + TODO (b/293290851) - change this comment/resource when flag is enabled --> + <bool name="force_config_use_split_notification_shade">false</bool> </resources> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 915dcdb9755f..1e54fc9e1445 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -80,10 +80,10 @@ <dimen name="large_screen_shade_header_height">42dp</dimen> <!-- start padding is smaller to account for status icon margins coming from drawable itself --> - <dimen name="shade_header_system_icons_padding_start">3dp</dimen> - <dimen name="shade_header_system_icons_padding_end">4dp</dimen> - <dimen name="shade_header_system_icons_padding_top">2dp</dimen> - <dimen name="shade_header_system_icons_padding_bottom">2dp</dimen> + <dimen name="hover_system_icons_container_padding_start">3dp</dimen> + <dimen name="hover_system_icons_container_padding_end">4dp</dimen> + <dimen name="hover_system_icons_container_padding_top">2dp</dimen> + <dimen name="hover_system_icons_container_padding_bottom">2dp</dimen> <!-- Lockscreen shade transition values --> <dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index c13480672102..c72f5657176b 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -592,6 +592,11 @@ <!-- Whether to use the split 2-column notification shade --> <bool name="config_use_split_notification_shade">false</bool> + <!-- Whether to force split shade. + For now, this value has effect only when flag lockscreen.enable_landscape is enabled. + TODO (b/293290851) - change this comment/resource when flag is enabled --> + <bool name="force_config_use_split_notification_shade">false</bool> + <!-- Whether we use large screen shade header which takes only one row compared to QS header --> <bool name="config_use_large_screen_shade_header">false</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index b086ed8960b1..ae3138ef0c52 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -496,10 +496,10 @@ <dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen> <dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen> <dimen name="shade_header_system_icons_height">@dimen/large_screen_shade_header_min_height</dimen> - <dimen name="shade_header_system_icons_padding_start">0dp</dimen> - <dimen name="shade_header_system_icons_padding_end">0dp</dimen> - <dimen name="shade_header_system_icons_padding_top">0dp</dimen> - <dimen name="shade_header_system_icons_padding_bottom">0dp</dimen> + <dimen name="hover_system_icons_container_padding_start">0dp</dimen> + <dimen name="hover_system_icons_container_padding_end">0dp</dimen> + <dimen name="hover_system_icons_container_padding_top">0dp</dimen> + <dimen name="hover_system_icons_container_padding_bottom">0dp</dimen> <!-- The top margin of the panel that holds the list of notifications. On phones it's always 0dp but it's overridden in Car UI diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java index 0c2341f9480c..3cb5bc745ec7 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java @@ -59,9 +59,8 @@ public class PreviewPositionHelper { * Updates the matrix based on the provided parameters */ public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData, - int canvasWidth, int canvasHeight, int screenWidthPx, int screenHeightPx, - int taskbarSize, boolean isLargeScreen, - int currentRotation, boolean isRtl) { + int canvasWidth, int canvasHeight, boolean isLargeScreen, int currentRotation, + boolean isRtl) { boolean isRotated = false; boolean isOrientationDifferent; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java index 8200e5c84186..905923039f8b 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java @@ -17,6 +17,7 @@ package com.android.systemui.shared.rotation; import static android.content.pm.PackageManager.FEATURE_PC; +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION; @@ -37,6 +38,8 @@ import android.content.IntentFilter; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; import android.os.SystemProperties; @@ -67,6 +70,7 @@ import com.android.systemui.shared.system.TaskStackChangeListeners; import java.io.PrintWriter; import java.util.Optional; +import java.util.concurrent.Executor; import java.util.function.Supplier; /** @@ -119,6 +123,8 @@ public class RotationButtonController { private final int mIconCwStart0ResId; @DrawableRes private final int mIconCwStart90ResId; + /** Defaults to mainExecutor if not set via {@link #setBgExecutor(Executor)}. */ + private Executor mBgExecutor; @DrawableRes private int mIconResId; @@ -178,6 +184,8 @@ public class RotationButtonController { mAccessibilityManager = AccessibilityManager.getInstance(context); mTaskStackListener = new TaskStackListenerImpl(); mWindowRotationProvider = windowRotationProvider; + + mBgExecutor = context.getMainExecutor(); } public void setRotationButton(RotationButton rotationButton, @@ -193,6 +201,10 @@ public class RotationButtonController { return mContext; } + public void setBgExecutor(Executor bgExecutor) { + mBgExecutor = bgExecutor; + } + /** * Called during Taskbar initialization. */ @@ -219,8 +231,11 @@ public class RotationButtonController { mListenersRegistered = true; - updateDockedState(mContext.registerReceiver(mDockedReceiver, - new IntentFilter(Intent.ACTION_DOCK_EVENT))); + mBgExecutor.execute(() -> { + final Intent intent = mContext.registerReceiver(mDockedReceiver, + new IntentFilter(Intent.ACTION_DOCK_EVENT)); + mContext.getMainExecutor().execute(() -> updateDockedState(intent)); + }); if (registerRotationWatcher) { try { @@ -246,11 +261,13 @@ public class RotationButtonController { mListenersRegistered = false; - try { - mContext.unregisterReceiver(mDockedReceiver); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Docked receiver already unregistered", e); - } + mBgExecutor.execute(() -> { + try { + mContext.unregisterReceiver(mDockedReceiver); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Docked receiver already unregistered", e); + } + }); if (mRotationWatcherRegistered) { try { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 8738d3300e93..abd1563ecc43 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -16,8 +16,6 @@ package com.android.keyguard; -import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; - import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -257,10 +255,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mInputMethodManager, emergencyButtonController, mMainExecutor, mResources, mFalsingCollector, mKeyguardViewController, - mFeatureFlags); + mDevicePostureController, mFeatureFlags); } else if (keyguardInputView instanceof KeyguardPINView) { - ((KeyguardPINView) keyguardInputView).setIsLockScreenLandscapeEnabled( - mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)); return new KeyguardPinViewController((KeyguardPINView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java index bb3e759dc157..72b4ae568397 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java @@ -17,6 +17,7 @@ package com.android.keyguard; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; + import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_APPEAR; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED; @@ -105,21 +106,21 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { } void onDevicePostureChanged(@DevicePostureInt int posture) { - if (mLastDevicePosture != posture) { - mLastDevicePosture = posture; + if (mLastDevicePosture == posture) return; + mLastDevicePosture = posture; - if (mIsLockScreenLandscapeEnabled) { - boolean useSplitBouncerAfterFold = - mLastDevicePosture == DEVICE_POSTURE_CLOSED - && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE; + if (mIsLockScreenLandscapeEnabled) { + boolean useSplitBouncerAfterFold = + mLastDevicePosture == DEVICE_POSTURE_CLOSED + && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE + && getResources().getBoolean(R.bool.update_bouncer_constraints); - if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) { - updateConstraints(useSplitBouncerAfterFold); - } + if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) { + updateConstraints(useSplitBouncerAfterFold); } - - updateMargins(); } + + updateMargins(); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 59ee0d817ef3..8d5fc0467e3b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -16,6 +16,7 @@ package com.android.keyguard; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.WindowInsets.Type.ime; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; @@ -27,10 +28,13 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART_FO import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; +import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED; +import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.app.ActivityManager; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Insets; @@ -46,12 +50,16 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.constraintlayout.motion.widget.MotionLayout; import com.android.app.animation.Interpolators; import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.TextViewInputDisabler; import com.android.systemui.DejankUtils; import com.android.systemui.R; +import com.android.systemui.statusbar.policy.DevicePostureController; + + /** * Displays an alphanumeric (latin-1) key entry for the user to enter * an unlock password @@ -68,10 +76,14 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { private TextView mPasswordEntry; private TextViewInputDisabler mPasswordEntryDisabler; - private Interpolator mLinearOutSlowInInterpolator; private Interpolator mFastOutLinearInInterpolator; private DisappearAnimationListener mDisappearAnimationListener; + @Nullable private MotionLayout mContainerMotionLayout; + private boolean mAlreadyUsingSplitBouncer = false; + private boolean mIsLockScreenLandscapeEnabled = false; + @DevicePostureController.DevicePostureInt + private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN; private static final int[] DISABLE_STATE_SET = {-android.R.attr.state_enabled}; private static final int[] ENABLE_STATE_SET = {android.R.attr.state_enabled}; @@ -89,6 +101,21 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { context, android.R.interpolator.fast_out_linear_in); } + /** + * Use motion layout (new bouncer implementation) if LOCKSCREEN_ENABLE_LANDSCAPE flag is + * enabled + */ + public void setIsLockScreenLandscapeEnabled() { + mIsLockScreenLandscapeEnabled = true; + findContainerLayout(); + } + + private void findContainerLayout() { + if (mIsLockScreenLandscapeEnabled) { + mContainerMotionLayout = findViewById(R.id.password_container); + } + } + @Override protected void resetState() { } @@ -124,6 +151,35 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { } } + void onDevicePostureChanged(@DevicePostureController.DevicePostureInt int posture) { + if (mLastDevicePosture == posture) return; + mLastDevicePosture = posture; + + if (mIsLockScreenLandscapeEnabled) { + boolean useSplitBouncerAfterFold = + mLastDevicePosture == DEVICE_POSTURE_CLOSED + && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE + && getResources().getBoolean(R.bool.update_bouncer_constraints); + + if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) { + updateConstraints(useSplitBouncerAfterFold); + } + } + + } + + @Override + protected void updateConstraints(boolean useSplitBouncer) { + mAlreadyUsingSplitBouncer = useSplitBouncer; + if (useSplitBouncer) { + mContainerMotionLayout.jumpToState(R.id.split_constraints); + mContainerMotionLayout.setMaxWidth(Integer.MAX_VALUE); + } else { + mContainerMotionLayout.jumpToState(R.id.single_constraints); + mContainerMotionLayout.setMaxWidth(getResources() + .getDimensionPixelSize(R.dimen.keyguard_security_width)); + } + } @Override protected void onFinishInflate() { @@ -131,6 +187,11 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { mPasswordEntry = findViewById(getPasswordTextViewId()); mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry); + + // EditText cursor can fail screenshot tests, so disable it when testing + if (ActivityManager.isRunningInTestHarness()) { + mPasswordEntry.setCursorVisible(false); + } } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 5dbd01407a43..ab8cd531c307 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; + import android.content.res.Resources; import android.os.UserHandle; import android.text.Editable; @@ -41,6 +43,7 @@ import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.List; @@ -49,8 +52,10 @@ public class KeyguardPasswordViewController extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> { private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms - private final KeyguardSecurityCallback mKeyguardSecurityCallback; + private final DevicePostureController mPostureController; + private final DevicePostureController.Callback mPostureCallback = posture -> + mView.onDevicePostureChanged(posture); private final InputMethodManager mInputMethodManager; private final DelayableExecutor mMainExecutor; private final KeyguardViewController mKeyguardViewController; @@ -106,14 +111,19 @@ public class KeyguardPasswordViewController @Main Resources resources, FalsingCollector falsingCollector, KeyguardViewController keyguardViewController, + DevicePostureController postureController, FeatureFlags featureFlags) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, falsingCollector, emergencyButtonController, featureFlags); mKeyguardSecurityCallback = keyguardSecurityCallback; mInputMethodManager = inputMethodManager; + mPostureController = postureController; mMainExecutor = mainExecutor; mKeyguardViewController = keyguardViewController; + if (featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) { + view.setIsLockScreenLandscapeEnabled(); + } mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on); mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); mSwitchImeButton = mView.findViewById(R.id.switch_ime_button); @@ -127,6 +137,9 @@ public class KeyguardPasswordViewController mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + mView.onDevicePostureChanged(mPostureController.getDevicePosture()); + mPostureController.addCallback(mPostureCallback); + // Set selected property on so the view can send accessibility events. mPasswordEntry.setSelected(true); mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener); @@ -164,6 +177,7 @@ public class KeyguardPasswordViewController protected void onViewDetached() { super.onViewDetached(); mPasswordEntry.setOnEditorActionListener(null); + mPostureController.removeCallback(mPostureCallback); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index 2bdf46e1309d..56706b5a7135 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -15,9 +15,13 @@ */ package com.android.keyguard; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; + +import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; @@ -29,6 +33,7 @@ import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; +import androidx.constraintlayout.motion.widget.MotionLayout; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; @@ -75,7 +80,10 @@ public class KeyguardPatternView extends KeyguardInputView BouncerKeyguardMessageArea mSecurityMessageDisplay; private View mEcaView; - private ConstraintLayout mContainer; + @Nullable private MotionLayout mContainerMotionLayout; + @Nullable private ConstraintLayout mContainerConstraintLayout; + private boolean mAlreadyUsingSplitBouncer = false; + private boolean mIsLockScreenLandscapeEnabled = false; @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN; public KeyguardPatternView(Context context) { @@ -98,16 +106,44 @@ public class KeyguardPatternView extends KeyguardInputView mContext, android.R.interpolator.fast_out_linear_in)); } + /** + * Use motion layout (new bouncer implementation) if LOCKSCREEN_ENABLE_LANDSCAPE flag is + * enabled, instead of constraint layout (old bouncer implementation) + */ + public void setIsLockScreenLandscapeEnabled(boolean isLockScreenLandscapeEnabled) { + mIsLockScreenLandscapeEnabled = isLockScreenLandscapeEnabled; + findContainerLayout(); + } + + private void findContainerLayout() { + if (mIsLockScreenLandscapeEnabled) { + mContainerMotionLayout = findViewById(R.id.pattern_container); + } else { + mContainerConstraintLayout = findViewById(R.id.pattern_container); + } + } + @Override protected void onConfigurationChanged(Configuration newConfig) { updateMargins(); } void onDevicePostureChanged(@DevicePostureInt int posture) { - if (mLastDevicePosture != posture) { - mLastDevicePosture = posture; - updateMargins(); + if (mLastDevicePosture == posture) return; + mLastDevicePosture = posture; + + if (mIsLockScreenLandscapeEnabled) { + boolean useSplitBouncerAfterFold = + mLastDevicePosture == DEVICE_POSTURE_CLOSED + && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE + && getResources().getBoolean(R.bool.update_bouncer_constraints); + + if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) { + updateConstraints(useSplitBouncerAfterFold); + } } + + updateMargins(); } private void updateMargins() { @@ -115,12 +151,36 @@ public class KeyguardPatternView extends KeyguardInputView float halfOpenPercentage = mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio); - ConstraintSet cs = new ConstraintSet(); - cs.clone(mContainer); - cs.setGuidelinePercent(R.id.pattern_top_guideline, - mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED - ? halfOpenPercentage : 0.0f); - cs.applyTo(mContainer); + if (mIsLockScreenLandscapeEnabled) { + ConstraintSet cs = mContainerMotionLayout.getConstraintSet(R.id.single_constraints); + cs.setGuidelinePercent(R.id.pattern_top_guideline, + mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f); + cs.applyTo(mContainerMotionLayout); + } else { + ConstraintSet cs = new ConstraintSet(); + cs.clone(mContainerConstraintLayout); + cs.setGuidelinePercent(R.id.pattern_top_guideline, + mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f); + cs.applyTo(mContainerConstraintLayout); + } + } + + /** + * Updates the keyguard view's constraints (single or split constraints). + * Split constraints are only used for small landscape screens. + * Only called when flag LANDSCAPE_ENABLE_LOCKSCREEN is enabled. + */ + @Override + protected void updateConstraints(boolean useSplitBouncer) { + mAlreadyUsingSplitBouncer = useSplitBouncer; + if (useSplitBouncer) { + mContainerMotionLayout.jumpToState(R.id.split_constraints); + mContainerMotionLayout.setMaxWidth(Integer.MAX_VALUE); + } else { + mContainerMotionLayout.jumpToState(R.id.single_constraints); + mContainerMotionLayout.setMaxWidth(getResources() + .getDimensionPixelSize(R.dimen.biometric_auth_pattern_view_max_size)); + } } @Override @@ -130,7 +190,6 @@ public class KeyguardPatternView extends KeyguardInputView mLockPatternView = findViewById(R.id.lockPatternView); mEcaView = findViewById(R.id.keyguard_selector_fade_container); - mContainer = findViewById(R.id.pattern_container); } @Override @@ -209,7 +268,7 @@ public class KeyguardPatternView extends KeyguardInputView getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR)); DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition - ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils; + ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils; disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(), () -> { enableClipping(true); @@ -220,7 +279,7 @@ public class KeyguardPatternView extends KeyguardInputView if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) { mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0, (long) (200 * durationMultiplier), - - mDisappearAnimationUtils.getStartTranslation() * 3, + -mDisappearAnimationUtils.getStartTranslation() * 3, false /* appearing */, mDisappearAnimationUtils.getInterpolator(), null /* finishRunnable */); @@ -229,9 +288,16 @@ public class KeyguardPatternView extends KeyguardInputView } private void enableClipping(boolean enable) { - setClipChildren(enable); - mContainer.setClipToPadding(enable); - mContainer.setClipChildren(enable); + if (mContainerConstraintLayout != null) { + setClipChildren(enable); + mContainerConstraintLayout.setClipToPadding(enable); + mContainerConstraintLayout.setClipChildren(enable); + } + if (mContainerMotionLayout != null) { + setClipChildren(enable); + mContainerMotionLayout.setClipToPadding(enable); + mContainerMotionLayout.setClipChildren(enable); + } } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index a30b4479fe95..98312b11e9d6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -18,6 +18,7 @@ package com.android.keyguard; import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; +import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; import android.content.res.ColorStateList; import android.os.AsyncTask; @@ -205,6 +206,8 @@ public class KeyguardPatternViewController mLatencyTracker = latencyTracker; mFalsingCollector = falsingCollector; mEmergencyButtonController = emergencyButtonController; + view.setIsLockScreenLandscapeEnabled( + featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)); mLockPatternView = mView.findViewById(R.id.lockPatternView); mPostureController = postureController; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 574a0591bd51..0af803fc84fe 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; + import android.view.View; import com.android.internal.util.LatencyTracker; @@ -61,6 +63,7 @@ public class KeyguardPinViewController mPostureController = postureController; mLockPatternUtils = lockPatternUtils; mFeatureFlags = featureFlags; + view.setIsLockScreenLandscapeEnabled(mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)); mBackspaceKey = view.findViewById(R.id.delete_button); mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser()); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java index 4cc90c244cf2..f18504c28609 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java @@ -17,6 +17,7 @@ package com.android.keyguard; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; + import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; import android.util.Log; @@ -154,9 +155,9 @@ public class KeyguardSecurityViewFlipperController private int getLayoutIdFor(SecurityMode securityMode) { // TODO (b/297863911, b/297864907) - implement motion layout for other bouncers switch (securityMode) { - case Pattern: return R.layout.keyguard_pattern_view; + case Pattern: return R.layout.keyguard_pattern_motion_layout; case PIN: return R.layout.keyguard_pin_motion_layout; - case Password: return R.layout.keyguard_password_view; + case Password: return R.layout.keyguard_password_motion_layout; case SimPin: return R.layout.keyguard_sim_pin_view; case SimPuk: return R.layout.keyguard_sim_puk_view; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index 3990b10267e1..c64ae0106b4a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -17,6 +17,7 @@ package com.android.keyguard; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; +import static com.android.systemui.statusbar.StatusBarState.SHADE; import android.util.Property; import android.view.View; @@ -124,7 +125,16 @@ public class KeyguardVisibilityHelper { true /* animate */); log("keyguardFadingAway transition w/ Y Aniamtion"); } else if (statusBarState == KEYGUARD) { - if (keyguardFadingAway) { + // Sometimes, device will be unlocked and then locked very quickly. + // keyguardFadingAway hasn't been set to false cause unlock animation hasn't finished + // So we should not animate keyguard fading away in this case (when oldState is SHADE) + if (oldStatusBarState != SHADE) { + log("statusBarState == KEYGUARD && oldStatusBarState != SHADE"); + } else { + log("statusBarState == KEYGUARD && oldStatusBarState == SHADE"); + } + + if (keyguardFadingAway && oldStatusBarState != SHADE) { mKeyguardViewVisibilityAnimating = true; AnimationProperties animProps = new AnimationProperties() .setDelay(0) diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index 1d37809a382e..c52288190a7c 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -134,8 +134,6 @@ public class LockIconView extends FrameLayout implements Dumpable { mLockIcon.setPadding(mLockIconPadding, mLockIconPadding, mLockIconPadding, mLockIconPadding); - // mSensorProps coordinates assume portrait mode which is OK b/c the keyguard is always in - // portrait. mSensorRect.set(mLockIconCenter.x - mRadius, mLockIconCenter.y - mRadius, mLockIconCenter.x + mRadius, diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 464909127277..214b1228b87a 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -18,6 +18,7 @@ package com.android.keyguard; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; + import static com.android.keyguard.LockIconView.ICON_FINGERPRINT; import static com.android.keyguard.LockIconView.ICON_LOCK; import static com.android.keyguard.LockIconView.ICON_UNLOCK; @@ -44,6 +45,7 @@ import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -422,7 +424,13 @@ public class LockIconViewController implements Dumpable { private void updateConfiguration() { WindowManager windowManager = mContext.getSystemService(WindowManager.class); Rect bounds = windowManager.getCurrentWindowMetrics().getBounds(); + WindowInsets insets = windowManager.getCurrentWindowMetrics().getWindowInsets(); mWidthPixels = bounds.right; + if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) { + // Assumed to be initially neglected as there are no left or right insets in portrait + // However, on landscape, these insets need to included when calculating the midpoint + mWidthPixels -= insets.getSystemWindowInsetLeft() + insets.getSystemWindowInsetRight(); + } mHeightPixels = bounds.bottom; mBottomPaddingPx = mResources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom); mDefaultPaddingPx = mResources.getDimensionPixelSize(R.dimen.lock_icon_padding); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt index 97b06170e770..054bd082edb7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt @@ -25,7 +25,7 @@ constructor( shadeExpansionCollectorJob = scope.launch { // wait for it to emit true once - shadeInteractorLazy.get().anyExpanding.first { it } + shadeInteractorLazy.get().isAnyExpanding.first { it } onShadeInteraction.run() } shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 9ffb770378c5..026435623042 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -23,6 +23,7 @@ import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_ import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD; import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING; import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR; + import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; @@ -104,6 +105,8 @@ import com.android.systemui.util.concurrency.Execution; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.SystemClock; +import kotlin.Unit; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; @@ -114,8 +117,6 @@ import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Provider; -import kotlin.Unit; - import kotlinx.coroutines.ExperimentalCoroutinesApi; /** @@ -255,11 +256,12 @@ public class UdfpsController implements DozeReceiver, Dumpable { @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { + final int touchConfigId = mContext.getResources().getInteger( + com.android.internal.R.integer.config_selected_udfps_touch_detection); pw.println("mSensorProps=(" + mSensorProps + ")"); pw.println("Using new touch detection framework: " + mFeatureFlags.isEnabled( Flags.UDFPS_NEW_TOUCH_DETECTION)); - pw.println("Using ellipse touch detection: " + mFeatureFlags.isEnabled( - Flags.UDFPS_ELLIPSE_DETECTION)); + pw.println("touchConfigId: " + touchConfigId); } public class UdfpsOverlayController extends IUdfpsOverlayController.Stub { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt index f43285f9004a..f2d4f8912c3f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt @@ -17,6 +17,9 @@ package com.android.systemui.biometrics.dagger import com.android.systemui.biometrics.UdfpsUtils +import android.content.res.Resources +import com.android.internal.R +import com.android.systemui.biometrics.EllipseOverlapDetectorParams import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.biometrics.data.repository.FacePropertyRepositoryImpl import com.android.systemui.biometrics.data.repository.FaceSettingsRepository @@ -25,8 +28,8 @@ import com.android.systemui.biometrics.data.repository.FingerprintPropertyReposi import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl import com.android.systemui.biometrics.data.repository.PromptRepository import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl -import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository -import com.android.systemui.biometrics.data.repository.RearDisplayStateRepositoryImpl +import com.android.systemui.biometrics.data.repository.DisplayStateRepository +import com.android.systemui.biometrics.data.repository.DisplayStateRepositoryImpl import com.android.systemui.biometrics.domain.interactor.CredentialInteractor import com.android.systemui.biometrics.domain.interactor.CredentialInteractorImpl import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor @@ -37,6 +40,9 @@ import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteracto import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl +import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector +import com.android.systemui.biometrics.udfps.EllipseOverlapDetector +import com.android.systemui.biometrics.udfps.OverlapDetector import com.android.systemui.dagger.SysUISingleton import com.android.systemui.util.concurrency.ThreadFactory import dagger.Binds @@ -63,16 +69,19 @@ interface BiometricsModule { @Binds @SysUISingleton - fun fingerprintRepository(impl: FingerprintPropertyRepositoryImpl): - FingerprintPropertyRepository + fun fingerprintRepository( + impl: FingerprintPropertyRepositoryImpl + ): FingerprintPropertyRepository + @Binds @SysUISingleton - fun rearDisplayStateRepository(impl: RearDisplayStateRepositoryImpl): RearDisplayStateRepository + fun displayStateRepository(impl: DisplayStateRepositoryImpl): DisplayStateRepository @Binds @SysUISingleton - fun providesPromptSelectorInteractor(impl: PromptSelectorInteractorImpl): - PromptSelectorInteractor + fun providesPromptSelectorInteractor( + impl: PromptSelectorInteractorImpl + ): PromptSelectorInteractor @Binds @SysUISingleton @@ -88,8 +97,9 @@ interface BiometricsModule { @Binds @SysUISingleton - fun providesSideFpsOverlayInteractor(impl: SideFpsOverlayInteractorImpl): - SideFpsOverlayInteractor + fun providesSideFpsOverlayInteractor( + impl: SideFpsOverlayInteractorImpl + ): SideFpsOverlayInteractor companion object { /** Background [Executor] for HAL related operations. */ @@ -102,6 +112,30 @@ interface BiometricsModule { @Provides fun providesUdfpsUtils(): UdfpsUtils = UdfpsUtils() + + @Provides + @SysUISingleton + fun providesOverlapDetector(): OverlapDetector { + val selectedOption = + Resources.getSystem().getInteger(R.integer.config_selected_udfps_touch_detection) + val values = + Resources.getSystem() + .getStringArray(R.array.config_udfps_touch_detection_options)[selectedOption] + .split(",") + .map { it.toFloat() } + + return if (values[0] == 1f) { + EllipseOverlapDetector( + EllipseOverlapDetectorParams( + minOverlap = values[3], + targetSize = values[2], + stepSize = values[4].toInt() + ) + ) + } else { + BoundingBoxOverlapDetector(values[2]) + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt deleted file mode 100644 index f7f910391566..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics.dagger - -import android.content.res.Resources -import com.android.internal.R -import com.android.systemui.biometrics.EllipseOverlapDetectorParams -import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector -import com.android.systemui.biometrics.udfps.EllipseOverlapDetector -import com.android.systemui.biometrics.udfps.OverlapDetector -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import dagger.Module -import dagger.Provides - -/** Dagger module for all things UDFPS. TODO(b/260558624): Move to BiometricsModule. */ -@Module -interface UdfpsModule { - companion object { - - @Provides - @SysUISingleton - fun providesOverlapDetector(featureFlags: FeatureFlags): OverlapDetector { - if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) { - val selectedOption = - Resources.getSystem() - .getInteger(R.integer.config_selected_udfps_touch_detection) - val values = - Resources.getSystem() - .getStringArray(R.array.config_udfps_touch_detection_options)[ - selectedOption] - .split(",") - .map { it.toFloat() } - - return if (values[0] == 1f) { - EllipseOverlapDetector( - EllipseOverlapDetectorParams( - minOverlap = values[3], - targetSize = values[2], - stepSize = values[4].toInt() - ) - ) - } else { - BoundingBoxOverlapDetector(values[2]) - } - } else { - return BoundingBoxOverlapDetector(1f) - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt index d17d961b5279..7a9efcf78999 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt @@ -18,7 +18,14 @@ package com.android.systemui.biometrics.data.repository import android.content.Context import android.hardware.devicestate.DeviceStateManager +import android.hardware.display.DisplayManager +import android.hardware.display.DisplayManager.DisplayListener +import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED +import android.os.Handler +import android.view.DisplayInfo import com.android.internal.util.ArrayUtils +import com.android.systemui.biometrics.shared.model.DisplayRotation +import com.android.systemui.biometrics.shared.model.toDisplayRotation import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -32,21 +39,26 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.stateIn -/** Provide current rear display state. */ -interface RearDisplayStateRepository { +/** Repository for the current state of the display */ +interface DisplayStateRepository { /** Provides the current rear display state. */ val isInRearDisplayMode: StateFlow<Boolean> + + /** Provides the current display rotation */ + val currentRotation: StateFlow<DisplayRotation> } @SysUISingleton -class RearDisplayStateRepositoryImpl +class DisplayStateRepositoryImpl @Inject constructor( @Application applicationScope: CoroutineScope, - @Application context: Context, + @Application val context: Context, deviceStateManager: DeviceStateManager, + displayManager: DisplayManager, + @Main handler: Handler, @Main mainExecutor: Executor -) : RearDisplayStateRepository { +) : DisplayStateRepository { override val isInRearDisplayMode: StateFlow<Boolean> = conflatedCallbackFlow { val sendRearDisplayStateUpdate = { state: Boolean -> @@ -79,7 +91,43 @@ constructor( initialValue = false, ) + private fun getDisplayRotation(): DisplayRotation { + val cachedDisplayInfo = DisplayInfo() + context.display?.getDisplayInfo(cachedDisplayInfo) + return cachedDisplayInfo.rotation.toDisplayRotation() + } + + override val currentRotation: StateFlow<DisplayRotation> = + conflatedCallbackFlow { + val callback = + object : DisplayListener { + override fun onDisplayRemoved(displayId: Int) {} + + override fun onDisplayAdded(displayId: Int) {} + + override fun onDisplayChanged(displayId: Int) { + val rotation = getDisplayRotation() + trySendWithFailureLogging( + rotation, + TAG, + "Error sending display rotation to $rotation" + ) + } + } + displayManager.registerDisplayListener( + callback, + handler, + EVENT_FLAG_DISPLAY_CHANGED + ) + awaitClose { displayManager.unregisterDisplayListener(callback) } + } + .stateIn( + applicationScope, + started = SharingStarted.Eagerly, + initialValue = getDisplayRotation(), + ) + companion object { - const val TAG = "RearDisplayStateRepositoryImpl" + const val TAG = "DisplayStateRepositoryImpl" } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt index 2e734a315194..f36a3ec89e3f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt @@ -19,7 +19,8 @@ package com.android.systemui.biometrics.domain.interactor import android.content.Context import android.content.res.Configuration import android.view.Display -import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository +import com.android.systemui.biometrics.data.repository.DisplayStateRepository +import com.android.systemui.biometrics.shared.model.DisplayRotation import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.qualifiers.Application @@ -50,6 +51,9 @@ interface DisplayStateInteractor { /** Whether the device is currently folded. */ val isFolded: Flow<Boolean> + /** Current rotation of the display */ + val currentRotation: StateFlow<DisplayRotation> + /** Called on configuration changes, used to keep the display state in sync */ fun onConfigurationChanged(newConfig: Configuration) } @@ -61,7 +65,7 @@ constructor( @Application applicationScope: CoroutineScope, @Application context: Context, @Main mainExecutor: Executor, - rearDisplayStateRepository: RearDisplayStateRepository, + displayStateRepository: DisplayStateRepository, displayRepository: DisplayRepository, ) : DisplayStateInteractor { private var screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context) @@ -98,7 +102,10 @@ constructor( ) override val isInRearDisplayMode: StateFlow<Boolean> = - rearDisplayStateRepository.isInRearDisplayMode + displayStateRepository.isInRearDisplayMode + + override val currentRotation: StateFlow<DisplayRotation> = + displayStateRepository.currentRotation override fun onConfigurationChanged(newConfig: Configuration) { screenSizeFoldProvider.onConfigurationChange(newConfig) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt new file mode 100644 index 000000000000..10a3e915fe80 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt @@ -0,0 +1,21 @@ +package com.android.systemui.biometrics.shared.model + +import android.view.Surface + +/** Shadows [Surface.Rotation] for kotlin use within SysUI. */ +enum class DisplayRotation { + ROTATION_0, + ROTATION_90, + ROTATION_180, + ROTATION_270, +} + +/** Converts [Surface.Rotation] to corresponding [DisplayRotation] */ +fun Int.toDisplayRotation(): DisplayRotation = + when (this) { + Surface.ROTATION_0 -> DisplayRotation.ROTATION_0 + Surface.ROTATION_90 -> DisplayRotation.ROTATION_90 + Surface.ROTATION_180 -> DisplayRotation.ROTATION_180 + Surface.ROTATION_270 -> DisplayRotation.ROTATION_270 + else -> throw IllegalArgumentException("Invalid DisplayRotation value: $this") + } 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 d616dcca338f..02847c283738 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 @@ -252,6 +252,18 @@ object BiometricViewBinder { } } + // set padding + launch { + viewModel.promptPadding.collect { promptPadding -> + view.setPadding( + promptPadding.left, + promptPadding.top, + promptPadding.right, + promptPadding.bottom + ) + } + } + // configure & hide/disable buttons launch { viewModel.credentialKind 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 6269700d5419..267afae118e6 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 @@ -15,16 +15,21 @@ */ package com.android.systemui.biometrics.ui.viewmodel +import android.content.Context +import android.graphics.Rect import android.hardware.biometrics.BiometricPrompt import android.util.Log import android.view.HapticFeedbackConstants import android.view.MotionEvent +import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.BiometricModality +import com.android.systemui.biometrics.shared.model.DisplayRotation import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.biometrics.ui.binder.Spaghetti +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION import com.android.systemui.statusbar.VibratorHelper @@ -49,6 +54,7 @@ constructor( private val displayStateInteractor: DisplayStateInteractor, private val promptSelectorInteractor: PromptSelectorInteractor, private val vibrator: VibratorHelper, + @Application context: Context, private val featureFlags: FeatureFlags, ) { /** Models UI of [BiometricPromptLayout.iconView] */ @@ -135,6 +141,23 @@ constructor( !isOverlayTouched && size.isNotSmall } + /** Padding for prompt UI elements */ + val promptPadding: Flow<Rect> = + combine(size, displayStateInteractor.currentRotation) { size, rotation -> + if (size != PromptSize.LARGE) { + val navBarInsets = Utils.getNavbarInsets(context) + if (rotation == DisplayRotation.ROTATION_90) { + Rect(0, 0, navBarInsets.right, 0) + } else if (rotation == DisplayRotation.ROTATION_270) { + Rect(navBarInsets.left, 0, 0, 0) + } else { + Rect(0, 0, 0, navBarInsets.bottom) + } + } else { + Rect(0, 0, 0, 0) + } + } + /** Title for the prompt. */ val title: Flow<String> = promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index 364b5e78a9b0..1985c37e1d5d 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -129,7 +129,12 @@ class PatternBouncerViewModel( buildList { var dot = previousDot while (dot != hitDot) { - add(dot) + // Move along the direction of the line connecting the previously + // selected dot and current hit dot, and see if they were skipped over + // but fall on that line. + if (dot.isOnLineSegment(previousDot, hitDot)) { + add(dot) + } dot = PatternDotViewModel( x = @@ -208,6 +213,34 @@ class PatternBouncerViewModel( } } +/** + * Determines whether [this] dot is present on the line segment connecting [first] and [second] + * dots. + */ +private fun PatternDotViewModel.isOnLineSegment( + first: PatternDotViewModel, + second: PatternDotViewModel +): Boolean { + val anotherPoint = this + // No need to consider any points outside the bounds of two end points + val isWithinBounds = + anotherPoint.x.isBetween(first.x, second.x) && anotherPoint.y.isBetween(first.y, second.y) + if (!isWithinBounds) { + return false + } + + // Uses the 2 point line equation: (y-y1)/(x-x1) = (y2-y1)/(x2-x1) + // which can be rewritten as: (y-y1)*(x2-x1) = (x-x1)*(y2-y1) + // This is true for any point on the line passing through these two points + return (anotherPoint.y - first.y) * (second.x - first.x) == + (anotherPoint.x - first.x) * (second.y - first.y) +} + +/** Is [this] Int between [a] and [b] */ +private fun Int.isBetween(a: Int, b: Int): Boolean { + return (this in a..b) || (this in b..a) +} + data class PatternDotViewModel( val x: Int, val y: Int, diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index f12b91956c80..d89332d6d686 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -49,6 +49,7 @@ import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwit import com.android.systemui.power.PowerUI import com.android.systemui.reardisplay.RearDisplayDialogController import com.android.systemui.recents.Recents +import com.android.systemui.recents.ScreenPinningRequest import com.android.systemui.settings.dagger.MultiUserUtilsModule import com.android.systemui.shortcut.ShortcutKeyDispatcher import com.android.systemui.statusbar.ImmersiveModeConfirmation @@ -366,4 +367,9 @@ abstract class SystemUICoreStartableModule { @IntoMap @ClassKey(KeyguardDismissBinder::class) abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable + + @Binds + @IntoMap + @ClassKey(ScreenPinningRequest::class) + abstract fun bindScreenPinningRequest(impl: ScreenPinningRequest): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 7ee0ff4568be..3a942bd8203f 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -37,7 +37,6 @@ import com.android.systemui.biometrics.AlternateUdfpsTouchProvider; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.biometrics.UdfpsDisplayModeProvider; import com.android.systemui.biometrics.dagger.BiometricsModule; -import com.android.systemui.biometrics.dagger.UdfpsModule; import com.android.systemui.bouncer.ui.BouncerViewModule; import com.android.systemui.classifier.FalsingModule; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule; @@ -207,7 +206,6 @@ import javax.inject.Named; TelephonyRepositoryModule.class, TemporaryDisplayModule.class, TunerModule.class, - UdfpsModule.class, UserModule.class, UtilModule.class, NoteTaskModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt index 17513589fa82..4f166858faf8 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.display.data.repository import android.hardware.display.DisplayManager +import android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED import android.hardware.display.DisplayManager.DisplayListener import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_ADDED import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED @@ -95,28 +96,36 @@ constructor( @Background backgroundCoroutineDispatcher: CoroutineDispatcher ) : DisplayRepository { // Displays are enabled only after receiving them in [onDisplayAdded] - private val allDisplayEvents: Flow<DisplayEvent> = conflatedCallbackFlow { - val callback = - object : DisplayListener { - override fun onDisplayAdded(displayId: Int) { - trySend(DisplayEvent.Added(displayId)) - } + private val allDisplayEvents: Flow<DisplayEvent> = + conflatedCallbackFlow { + val callback = + object : DisplayListener { + override fun onDisplayAdded(displayId: Int) { + trySend(DisplayEvent.Added(displayId)) + } - override fun onDisplayRemoved(displayId: Int) { - trySend(DisplayEvent.Removed(displayId)) - } + override fun onDisplayRemoved(displayId: Int) { + trySend(DisplayEvent.Removed(displayId)) + } - override fun onDisplayChanged(displayId: Int) { - trySend(DisplayEvent.Changed(displayId)) - } + override fun onDisplayChanged(displayId: Int) { + trySend(DisplayEvent.Changed(displayId)) + } + } + // Triggers an initial event when subscribed. This is needed to avoid getDisplays to + // be called when this class is constructed, but only when someone subscribes to + // this flow. + trySend(DisplayEvent.Changed(Display.DEFAULT_DISPLAY)) + displayManager.registerDisplayListener( + callback, + backgroundHandler, + EVENT_FLAG_DISPLAY_ADDED or + EVENT_FLAG_DISPLAY_CHANGED or + EVENT_FLAG_DISPLAY_REMOVED, + ) + awaitClose { displayManager.unregisterDisplayListener(callback) } } - displayManager.registerDisplayListener( - callback, - backgroundHandler, - EVENT_FLAG_DISPLAY_ADDED or EVENT_FLAG_DISPLAY_CHANGED or EVENT_FLAG_DISPLAY_REMOVED, - ) - awaitClose { displayManager.unregisterDisplayListener(callback) } - } + .flowOn(backgroundCoroutineDispatcher) override val displayChangeEvent: Flow<Int> = allDisplayEvents.filter { it is DisplayEvent.Changed }.map { it.displayId } @@ -128,7 +137,9 @@ constructor( .stateIn( applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = getDisplays() + // To avoid getting displays on this object construction, they are get after the + // first event. allDisplayEvents emits a changed event when we subscribe to it. + initialValue = emptySet() ) private fun getDisplays(): Set<Display> = @@ -146,12 +157,23 @@ constructor( private val ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet()) + private fun getInitialConnectedDisplays(): Set<Int> = + displayManager + .getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED) + .map { it.displayId } + .toSet() + .also { + if (DEBUG) { + Log.d(TAG, "getInitialConnectedDisplays: $it") + } + } + /* keeps connected displays until they are disconnected. */ private val connectedDisplayIds: StateFlow<Set<Int>> = conflatedCallbackFlow { + val connectedIds = getInitialConnectedDisplays().toMutableSet() val callback = object : DisplayConnectionListener { - private val connectedIds = mutableSetOf<Int>() override fun onDisplayConnected(id: Int) { if (DEBUG) { Log.d(TAG, "display with id=$id connected.") @@ -170,6 +192,7 @@ constructor( trySend(connectedIds.toSet()) } } + trySend(connectedIds.toSet()) displayManager.registerDisplayListener( callback, backgroundHandler, @@ -183,6 +206,10 @@ constructor( .stateIn( applicationScope, started = SharingStarted.WhileSubscribed(), + // The initial value is set to empty, but connected displays are gathered as soon as + // the flow starts being collected. This is to ensure the call to get displays (an + // IPC) happens in the background instead of when this object + // is instantiated. initialValue = emptySet() ) diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 7e8f68225070..56c88fff9197 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -107,7 +107,7 @@ object Flags { // TODO(b/268005230): Tracking Bug @JvmField - val SENSITIVE_REVEAL_ANIM = unreleasedFlag("sensitive_reveal_anim", teamfood = true) + val SENSITIVE_REVEAL_ANIM = releasedFlag("sensitive_reveal_anim") // TODO(b/280783617): Tracking Bug @Keep @@ -115,7 +115,7 @@ object Flags { val BUILDER_EXTRAS_OVERRIDE = sysPropBooleanFlag( "persist.sysui.notification.builder_extras_override", - default = false + default = true ) /** Only notify group expansion listeners when a change happens. */ @@ -188,9 +188,6 @@ object Flags { // TODO(b/244313043): Tracking bug @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag("biometrics_animation_revamp") - // TODO(b/262780002): Tracking Bug - @JvmField val REVAMPED_WALLPAPER_UI = releasedFlag("revamped_wallpaper_ui") - // flag for controlling auto pin confirmation and material u shapes in bouncer @JvmField val AUTO_PIN_CONFIRMATION = releasedFlag("auto_pin_confirmation", "auto_pin_confirmation") @@ -294,6 +291,11 @@ object Flags { // TODO(b/291767565): Tracking bug. @JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag("migrate_keyguard_status_view") + /** Migrate the status bar view on keyguard from notification panel to keyguard root view. */ + // TODO(b/299115332): Tracking Bug. + @JvmField val MIGRATE_KEYGUARD_STATUS_BAR_VIEW = + unreleasedFlag("migrate_keyguard_status_bar_view") + /** Enables preview loading animation in the wallpaper picker. */ // TODO(b/274443705): Tracking Bug @JvmField @@ -403,7 +405,7 @@ object Flags { // TODO(b/290676905): Tracking Bug val NEW_SHADE_CARRIER_GROUP_MOBILE_ICONS = - unreleasedFlag("new_shade_carrier_group_mobile_icons", teamfood = true) + releasedFlag("new_shade_carrier_group_mobile_icons") // 700 - dialer/calls // TODO(b/254512734): Tracking Bug @@ -662,7 +664,6 @@ object Flags { // 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.) // TODO(b/259264861): Tracking Bug @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag("udfps_new_touch_detection") - @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag("udfps_ellipse_detection") // 2300 - stylus @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag("track_stylus_ever_used") @@ -774,7 +775,8 @@ object Flags { // TODO(b/290213663): Tracking Bug @JvmField - val ONE_WAY_HAPTICS_API_MIGRATION = unreleasedFlag("oneway_haptics_api_migration") + val ONE_WAY_HAPTICS_API_MIGRATION = + unreleasedFlag("oneway_haptics_api_migration", teamfood = true) /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */ @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 2b4dc8108418..9241776191d4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -764,13 +764,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } } } - - @Override - public void onStrongAuthStateChanged(int userId) { - if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { - doKeyguardLocked(null); - } - } }; ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() { @@ -2193,9 +2186,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, * Enable the keyguard if the settings are appropriate. */ private void doKeyguardLocked(Bundle options) { - // if another app is disabling us, don't show unless we're in lockdown mode - if (!mExternallyEnabled - && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { + // if another app is disabling us, don't show + if (!mExternallyEnabled) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); mNeedToReshowWhenReenabled = true; @@ -2208,14 +2200,21 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // we explicitly re-set state. if (mShowing && mKeyguardStateController.isShowing()) { if (mPM.isInteractive() && !mHiding) { - // It's already showing, and we're not trying to show it while the screen is off. - // We can simply reset all of the views, but don't hide the bouncer in case the user - // is currently interacting with it. - if (DEBUG) Log.d(TAG, "doKeyguard: not showing (instead, resetting) because it is " - + "already showing, we're interactive, and we were not previously hiding. " - + "It should be safe to short-circuit here."); - resetStateLocked(/* hideBouncer= */ false); - return; + if (mKeyguardStateController.isKeyguardGoingAway()) { + Log.e(TAG, "doKeyguard: we're still showing, but going away. Re-show the " + + "keyguard rather than short-circuiting and resetting."); + } else { + // It's already showing, and we're not trying to show it while the screen is + // off. We can simply reset all of the views, but don't hide the bouncer in case + // the user is currently interacting with it. + if (DEBUG) Log.d(TAG, + "doKeyguard: not showing (instead, resetting) because it is " + + "already showing, we're interactive, we were not " + + "previously hiding. It should be safe to short-circuit " + + "here."); + resetStateLocked(/* hideBouncer= */ false); + return; + } } else { // We are trying to show the keyguard while the screen is off or while we were in // the middle of hiding - this results from race conditions involving locking while @@ -2740,13 +2739,18 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, setUnlockAndWakeFromDream(false, WakeAndUnlockUpdateReason.SHOW); setPendingLock(false); - // Force if we we're showing in the middle of hiding, to ensure we end up in the correct - // state. - setShowingLocked(true, mHiding /* force */); - if (mHiding) { - Log.d(TAG, "Forcing setShowingLocked because mHiding=true, which means we're " - + "showing in the middle of hiding."); + final boolean hidingOrGoingAway = + mHiding || mKeyguardStateController.isKeyguardGoingAway(); + if (hidingOrGoingAway) { + Log.d(TAG, "Forcing setShowingLocked because one of these is true:" + + "mHiding=" + mHiding + + ", keyguardGoingAway=" + mKeyguardStateController.isKeyguardGoingAway() + + ", which means we're showing in the middle of hiding."); } + + // Force if we we're showing in the middle of unlocking, to ensure we end up in the + // correct state. + setShowingLocked(true, hidingOrGoingAway /* force */); mHiding = false; if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { @@ -2954,7 +2958,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime + " fadeoutDuration=" + fadeoutDuration); synchronized (KeyguardViewMediator.this) { - // Tell ActivityManager that we canceled the keyguard animation if // handleStartKeyguardExitAnimation was called, but we're not hiding the keyguard, // unless we're animating the surface behind the keyguard and will be hiding the @@ -3222,10 +3225,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // Post layout changes to the next frame, so we don't hang at the end of the animation. DejankUtils.postAfterTraversal(() -> { - if (!mPM.isInteractive()) { - Log.e(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation#postAfterTraversal" + - "Not interactive after traversal. Don't hide the keyguard. This means we " + - "re-locked the device during unlock."); + if (!mPM.isInteractive() && !mPendingLock) { + Log.e(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation#postAfterTraversal:" + + "mPM.isInteractive()=" + mPM.isInteractive() + + "mPendingLock=" + mPendingLock + "." + + "One of these being false means we re-locked the device during unlock. " + + "Do not proceed to finish keyguard exit and unlock."); + finishSurfaceBehindRemoteAnimation(true /* showKeyguard */); return; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index 6db21b2c1e9e..233acd90aee3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -54,13 +54,13 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.UserRepository +import com.google.errorprone.annotations.CompileTimeConstant import java.io.PrintWriter import java.util.Arrays import java.util.stream.Collectors import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.delay @@ -112,33 +112,27 @@ interface DeviceEntryFaceAuthRepository { fun setLockedOut(isLockedOut: Boolean) /** - * Cancel current face authentication and prevent it from running until [resumeFaceAuth] is - * invoked. - */ - fun pauseFaceAuth() - - /** - * Allow face auth paused using [pauseFaceAuth] to run again. The next invocation to - * [authenticate] will run as long as other gating conditions don't stop it from running. - */ - fun resumeFaceAuth() - - /** - * Trigger face authentication. + * Request face authentication or detection to be run. * * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be * ignored if face authentication is already running. Results should be propagated through * [authenticationStatus] * * Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false. + * + * Method returns immediately and the face auth request is processed as soon as possible. */ - suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean = false) + fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean = false) /** Stop currently running face authentication or detection. */ fun cancel() } -@OptIn(ExperimentalCoroutinesApi::class) +private data class AuthenticationRequest( + val uiEvent: FaceAuthUiEvent, + val fallbackToDetection: Boolean +) + @SysUISingleton class DeviceEntryFaceAuthRepositoryImpl @Inject @@ -171,6 +165,8 @@ constructor( private var faceAcquiredInfoIgnoreList: Set<Int> private var retryCount = 0 + private var pendingAuthenticateRequest = MutableStateFlow<AuthenticationRequest?>(null) + private var cancelNotReceivedHandlerJob: Job? = null private var halErrorRetryJob: Job? = null @@ -193,15 +189,6 @@ constructor( override val isAuthRunning: StateFlow<Boolean> get() = _isAuthRunning - private val faceAuthPaused = MutableStateFlow(false) - override fun pauseFaceAuth() { - faceAuthPaused.value = true - } - - override fun resumeFaceAuth() { - faceAuthPaused.value = false - } - private val keyguardSessionId: InstanceId? get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD) @@ -213,6 +200,8 @@ constructor( override val isAuthenticated: Flow<Boolean> get() = _isAuthenticated + private var cancellationInProgress = MutableStateFlow(false) + override val isBypassEnabled: Flow<Boolean> = keyguardBypassController?.let { conflatedCallbackFlow { @@ -302,6 +291,7 @@ constructor( observeFaceDetectGatingChecks() observeFaceAuthResettingConditions() listenForSchedulingWatchdog() + processPendingAuthRequests() } else { canRunFaceAuth = MutableStateFlow(false).asStateFlow() canRunDetection = MutableStateFlow(false).asStateFlow() @@ -338,6 +328,7 @@ constructor( ) .onEach { anyOfThemIsTrue -> if (anyOfThemIsTrue) { + clearPendingAuthRequest("Resetting auth status") _isAuthenticated.value = false retryCount = 0 halErrorRetryJob?.cancel() @@ -346,6 +337,15 @@ constructor( .launchIn(applicationScope) } + private fun clearPendingAuthRequest(@CompileTimeConstant loggingContext: String) { + faceAuthLogger.clearingPendingAuthRequest( + loggingContext, + pendingAuthenticateRequest.value?.uiEvent, + pendingAuthenticateRequest.value?.fallbackToDetection + ) + pendingAuthenticateRequest.value = null + } + private fun observeFaceDetectGatingChecks() { canRunDetection .onEach { @@ -378,7 +378,6 @@ constructor( biometricSettingsRepository.isFaceAuthEnrolledAndEnabled, "isFaceAuthEnrolledAndEnabled" ), - Pair(faceAuthPaused.isFalse(), "faceAuthIsNotPaused"), Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"), Pair( keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(), @@ -402,7 +401,13 @@ constructor( biometricSettingsRepository.isCurrentUserInLockdown.isFalse(), "userHasNotLockedDownDevice" ), - Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing") + Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing"), + Pair( + userRepository.selectedUser + .map { it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS } + .isFalse(), + "userSwitchingInProgress" + ) ) } @@ -440,9 +445,6 @@ constructor( } _authenticationStatus.value = errorStatus _isAuthenticated.value = false - if (errorStatus.isCancellationError()) { - handleFaceCancellationError() - } if (errorStatus.isHardwareError()) { faceAuthLogger.hardwareError(errorStatus) handleFaceHardwareError() @@ -471,16 +473,6 @@ constructor( } } - private fun handleFaceCancellationError() { - applicationScope.launch { - faceAuthRequestedWhileCancellation?.let { - faceAuthLogger.launchingQueuedFaceAuthRequest(it) - authenticate(it) - } - faceAuthRequestedWhileCancellation = null - } - } - private fun handleFaceHardwareError() { if (retryCount < HAL_ERROR_RETRY_MAX) { retryCount++ @@ -490,7 +482,7 @@ constructor( delay(HAL_ERROR_RETRY_TIMEOUT) if (retryCount < HAL_ERROR_RETRY_MAX) { faceAuthLogger.attemptingRetryAfterHardwareError(retryCount) - authenticate( + requestAuthenticate( FaceAuthUiEvent.FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE, fallbackToDetection = false ) @@ -501,7 +493,7 @@ constructor( private fun onFaceAuthRequestCompleted() { cancelNotReceivedHandlerJob?.cancel() - cancellationInProgress = false + cancellationInProgress.value = false _isAuthRunning.value = false authCancellationSignal = null } @@ -512,24 +504,60 @@ constructor( _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong) } - private var cancellationInProgress = false - private var faceAuthRequestedWhileCancellation: FaceAuthUiEvent? = null + override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { + if (pendingAuthenticateRequest.value != null) { + faceAuthLogger.ignoredFaceAuthTrigger( + pendingAuthenticateRequest.value?.uiEvent, + "Previously queued trigger skipped due to new request" + ) + } + faceAuthLogger.queueingRequest(uiEvent, fallbackToDetection) + pendingAuthenticateRequest.value = AuthenticationRequest(uiEvent, fallbackToDetection) + } - override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { + private fun processPendingAuthRequests() { + combine( + pendingAuthenticateRequest, + canRunFaceAuth, + canRunDetection, + cancellationInProgress, + ) { pending, canRunAuth, canRunDetect, cancelInProgress -> + if ( + pending != null && + !(canRunAuth || (canRunDetect && pending.fallbackToDetection)) || + cancelInProgress + ) { + faceAuthLogger.notProcessingRequestYet( + pending?.uiEvent, + canRunAuth, + canRunDetect, + cancelInProgress + ) + return@combine null + } else { + return@combine pending + } + } + .onEach { + it?.let { + faceAuthLogger.processingRequest(it.uiEvent, it.fallbackToDetection) + clearPendingAuthRequest("Authenticate was invoked") + authenticate(it.uiEvent, it.fallbackToDetection) + } + } + .flowOn(mainDispatcher) + .launchIn(applicationScope) + } + + private suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { if (_isAuthRunning.value) { faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "face auth is currently running") return } - if (cancellationInProgress) { - faceAuthLogger.queuingRequestWhileCancelling( - faceAuthRequestedWhileCancellation, - uiEvent - ) - faceAuthRequestedWhileCancellation = uiEvent + if (cancellationInProgress.value) { + faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "cancellation in progress") return - } else { - faceAuthRequestedWhileCancellation = null } if (canRunFaceAuth.value) { @@ -553,12 +581,19 @@ constructor( FaceAuthenticateOptions.Builder().setUserId(currentUserId).build() ) } - } else if (fallbackToDetection && canRunDetection.value) { - faceAuthLogger.ignoredFaceAuthTrigger( - uiEvent, - "face auth gating check is false, falling back to detection." - ) - detect() + } else if (canRunDetection.value) { + if (fallbackToDetection) { + faceAuthLogger.ignoredFaceAuthTrigger( + uiEvent, + "face auth gating check is false, falling back to detection." + ) + detect() + } else { + faceAuthLogger.ignoredFaceAuthTrigger( + uiEvent = uiEvent, + "face auth gating check is false and fallback to detection is not requested" + ) + } } else { faceAuthLogger.ignoredFaceAuthTrigger( uiEvent, @@ -608,13 +643,13 @@ constructor( faceAuthLogger.cancelSignalNotReceived( _isAuthRunning.value, _isLockedOut.value, - cancellationInProgress, - faceAuthRequestedWhileCancellation + cancellationInProgress.value, + pendingAuthenticateRequest.value?.uiEvent ) _authenticationStatus.value = ErrorFaceAuthenticationStatus.cancelNotReceivedError() onFaceAuthRequestCompleted() } - cancellationInProgress = true + cancellationInProgress.value = true _isAuthRunning.value = false } @@ -647,9 +682,7 @@ constructor( " supportsFaceDetection: " + "${faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection}" ) - pw.println( - " faceAuthRequestedWhileCancellation: ${faceAuthRequestedWhileCancellation?.reason}" - ) + pw.println(" _pendingAuthenticateRequest: ${pendingAuthenticateRequest.value}") pw.println(" authCancellationSignal: $authCancellationSignal") pw.println(" detectCancellationSignal: $detectCancellationSignal") pw.println(" faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt index 46135fa89451..c8cb9e6aa0dd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt @@ -56,9 +56,6 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA get() = emptyFlow() override fun setLockedOut(isLockedOut: Boolean) = Unit - override fun pauseFaceAuth() = Unit - - override fun resumeFaceAuth() = Unit /** * Trigger face authentication. @@ -69,7 +66,7 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA * * Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false. */ - override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {} + override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) = Unit /** Stop currently running face authentication or detection. */ override fun cancel() {} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt index ca430da0ffce..a257f529357f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt @@ -172,7 +172,6 @@ constructor( private fun isFeatureEnabled(): Boolean { return featureFlags.isEnabled(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED) && - featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI) && appContext.resources.getBoolean(R.bool.long_press_keyguard_customize_lockscreen_enabled) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 9c796f846994..7f43cbc69fa2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -380,10 +380,6 @@ constructor( suspend fun getPickerFlags(): List<KeyguardPickerFlag> { return listOf( KeyguardPickerFlag( - name = Contract.FlagsTable.FLAG_NAME_REVAMPED_WALLPAPER_UI, - value = featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI), - ), - KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED, value = !isFeatureDisabledByDevicePolicy() && diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index ccc2080d8fee..f0df3a2e6a6f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -52,8 +52,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import kotlinx.coroutines.yield /** @@ -144,14 +142,11 @@ constructor( .onEach { (previous, curr) -> val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS - if (!wasSwitching && isSwitching) { - repository.pauseFaceAuth() - } else if (wasSwitching && !isSwitching) { + if (wasSwitching && !isSwitching) { val lockoutMode = facePropertyRepository.getLockoutMode(curr.userInfo.id) repository.setLockedOut( lockoutMode == LockoutMode.PERMANENT || lockoutMode == LockoutMode.TIMED ) - repository.resumeFaceAuth() yield() runFaceAuth( FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING, @@ -232,12 +227,8 @@ constructor( ) } else { faceAuthenticationStatusOverride.value = null - applicationScope.launch { - withContext(mainDispatcher) { - faceAuthenticationLogger.authRequested(uiEvent) - repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect) - } - } + faceAuthenticationLogger.authRequested(uiEvent) + repository.requestAuthenticate(uiEvent, fallbackToDetection = fallbackToDetect) } } else { faceAuthenticationLogger.ignoredFaceAuthTrigger( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index 44acf4f0fd2d..a9c71adc095d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -79,12 +79,6 @@ object KeyguardBottomAreaViewBinder { //If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt] @Deprecated("Deprecated as part of b/278057014") interface Binding { - /** - * Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the - * indication areas. - */ - fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> - /** Notifies that device configuration has changed. */ fun onConfigurationChanged() @@ -281,10 +275,6 @@ object KeyguardBottomAreaViewBinder { } return object : Binding { - override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> { - return listOf(ambientIndicationArea).mapNotNull { it?.animate() } - } - override fun onConfigurationChanged() { configurationBasedDimensions.value = loadFromResources(view) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index 15bb90915d94..43bbf69db883 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSect import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import javax.inject.Inject @@ -49,6 +50,7 @@ constructor( defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, defaultStatusViewSection: DefaultStatusViewSection, + defaultStatusBarSection: DefaultStatusBarSection, defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection, splitShadeGuidelines: SplitShadeGuidelines, aodNotificationIconsSection: AodNotificationIconsSection, @@ -64,6 +66,7 @@ constructor( defaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection, defaultStatusViewSection, + defaultStatusBarSection, defaultNotificationStackScrollLayoutSection, splitShadeGuidelines, aodNotificationIconsSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt index 6534dcf5c86b..a9885fc8defe 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt @@ -26,6 +26,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAr import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import javax.inject.Inject @@ -41,6 +42,7 @@ constructor( defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection, defaultStatusViewSection: DefaultStatusViewSection, + defaultStatusBarSection: DefaultStatusBarSection, splitShadeGuidelines: SplitShadeGuidelines, defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection, aodNotificationIconsSection: AodNotificationIconsSection, @@ -55,6 +57,7 @@ constructor( defaultSettingsPopupMenuSection, alignShortcutsToUdfpsSection, defaultStatusViewSection, + defaultStatusBarSection, defaultNotificationStackScrollLayoutSection, splitShadeGuidelines, aodNotificationIconsSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt index 9c6e953ad2d5..100099d3988e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt @@ -73,7 +73,13 @@ constructor( val mBottomPaddingPx = context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom) val bounds = windowManager.currentWindowMetrics.bounds - val widthPixels = bounds.right.toFloat() + val insets = windowManager.currentWindowMetrics.windowInsets + var widthPixels = bounds.right.toFloat() + if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) { + // Assumed to be initially neglected as there are no left or right insets in portrait. + // However, on landscape, these insets need to included when calculating the midpoint. + widthPixels -= (insets.systemWindowInsetLeft + insets.systemWindowInsetRight).toFloat() + } val heightPixels = bounds.bottom.toFloat() val defaultDensity = DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusBarSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusBarSection.kt new file mode 100644 index 000000000000..d6c69b3c442f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusBarSection.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.view.layout.sections + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.END +import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID +import androidx.constraintlayout.widget.ConstraintSet.START +import androidx.constraintlayout.widget.ConstraintSet.TOP +import com.android.keyguard.dagger.KeyguardStatusBarViewComponent +import com.android.systemui.R +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.shade.NotificationPanelView +import com.android.systemui.shade.ShadeViewStateProvider +import com.android.systemui.statusbar.phone.KeyguardStatusBarView +import com.android.systemui.util.Utils +import javax.inject.Inject + +/** A section for the status bar displayed at the top of the lockscreen. */ +class DefaultStatusBarSection +@Inject +constructor( + private val context: Context, + private val featureFlags: FeatureFlags, + private val notificationPanelView: NotificationPanelView, + private val keyguardStatusBarViewComponentFactory: KeyguardStatusBarViewComponent.Factory, +) : KeyguardSection() { + + private val statusBarViewId = R.id.keyguard_header + + override fun addViews(constraintLayout: ConstraintLayout) { + if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW)) { + return + } + + notificationPanelView.findViewById<View>(statusBarViewId)?.let { + (it.parent as ViewGroup).removeView(it) + } + + val view = + LayoutInflater.from(constraintLayout.context) + .inflate(R.layout.keyguard_status_bar, constraintLayout, false) + as KeyguardStatusBarView + + constraintLayout.addView(view) + } + + override fun bindData(constraintLayout: ConstraintLayout) { + if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW)) { + return + } + + val statusBarView = + constraintLayout.findViewById<KeyguardStatusBarView>(statusBarViewId) ?: return + + val provider = + object : ShadeViewStateProvider { + override val lockscreenShadeDragProgress: Float = 0f + override val panelViewExpandedHeight: Float = 0f + override fun shouldHeadsUpBeVisible(): Boolean { + return false + } + } + val statusBarViewComponent = + keyguardStatusBarViewComponentFactory.build(statusBarView, provider) + val controller = statusBarViewComponent.keyguardStatusBarViewController + controller.init() + } + + override fun applyConstraints(constraintSet: ConstraintSet) { + constraintSet.apply { + constrainHeight(statusBarViewId, Utils.getStatusBarHeaderHeightKeyguard(context)) + connect(statusBarViewId, TOP, PARENT_ID, TOP) + connect(statusBarViewId, START, PARENT_ID, START) + connect(statusBarViewId, END, PARENT_ID, END) + } + } + + override fun removeViews(constraintLayout: ConstraintLayout) { + constraintLayout.removeView(statusBarViewId) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt index b1dd373a7657..1cb10bd762a5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt @@ -39,7 +39,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.media.controls.ui.KeyguardMediaController import com.android.systemui.shade.NotificationPanelView import com.android.systemui.shade.NotificationPanelViewController -import com.android.systemui.util.LargeScreenUtils +import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.Utils import dagger.Lazy import javax.inject.Inject @@ -55,6 +55,7 @@ constructor( private val keyguardViewConfigurator: Lazy<KeyguardViewConfigurator>, private val notificationPanelViewController: Lazy<NotificationPanelViewController>, private val keyguardMediaController: KeyguardMediaController, + private val splitShadeStateController: SplitShadeStateController ) : KeyguardSection() { private val statusViewId = R.id.keyguard_status_view @@ -90,7 +91,7 @@ constructor( it.requireViewById<ViewGroup>(R.id.status_view_media_container) ) keyguardViewConfigurator.get().keyguardStatusViewController = controller - notificationPanelViewController.get().updateStatusBarViewController() + notificationPanelViewController.get().updateStatusViewController() } } } @@ -99,12 +100,14 @@ constructor( constraintSet.apply { constrainWidth(statusViewId, MATCH_CONSTRAINT) constrainHeight(statusViewId, WRAP_CONTENT) + // TODO(b/296122465): Constrain to the top of [DefaultStatusBarSection] and remove the + // extra margin below. connect(statusViewId, TOP, PARENT_ID, TOP) connect(statusViewId, START, PARENT_ID, START) connect(statusViewId, END, PARENT_ID, END) val margin = - if (LargeScreenUtils.shouldUseSplitNotificationShade(context.resources)) { + if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) { context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin) } else { context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) + diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt index 66067b11a18c..8143f99c4d0a 100644 --- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt @@ -29,36 +29,18 @@ class FaceAuthenticationLogger constructor( @FaceAuthLog private val logBuffer: LogBuffer, ) { - fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent, ignoredReason: String) { + fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent?, ignoredReason: String) { logBuffer.log( TAG, DEBUG, { - str1 = uiEvent.reason + str1 = "${uiEvent?.reason}" str2 = ignoredReason }, { "Ignoring trigger because $str2, Trigger reason: $str1" } ) } - fun queuingRequestWhileCancelling( - alreadyQueuedRequest: FaceAuthUiEvent?, - newRequest: FaceAuthUiEvent - ) { - logBuffer.log( - TAG, - DEBUG, - { - str1 = alreadyQueuedRequest?.reason - str2 = newRequest.reason - }, - { - "Face auth requested while previous request is being cancelled, " + - "already queued request: $str1 queueing the new request: $str2" - } - ) - } - fun authenticating(uiEvent: FaceAuthUiEvent) { logBuffer.log(TAG, DEBUG, { str1 = uiEvent.reason }, { "Running authenticate for $str1" }) } @@ -161,15 +143,6 @@ constructor( ) } - fun launchingQueuedFaceAuthRequest(faceAuthRequestedWhileCancellation: FaceAuthUiEvent?) { - logBuffer.log( - TAG, - DEBUG, - { str1 = "${faceAuthRequestedWhileCancellation?.reason}" }, - { "Received cancellation error and starting queued face auth request: $str1" } - ) - } - fun faceAuthSuccess(result: FaceManager.AuthenticationResult) { logBuffer.log( TAG, @@ -182,31 +155,10 @@ constructor( ) } - fun observedConditionChanged(newValue: Boolean, context: String) { - logBuffer.log( - TAG, - DEBUG, - { - bool1 = newValue - str1 = context - }, - { "Observed condition changed: $str1, new value: $bool1" } - ) - } - fun canFaceAuthRunChanged(canRun: Boolean) { logBuffer.log(TAG, DEBUG, { bool1 = canRun }, { "canFaceAuthRun value changed to $bool1" }) } - fun canRunDetectionChanged(canRunDetection: Boolean) { - logBuffer.log( - TAG, - DEBUG, - { bool1 = canRunDetection }, - { "canRunDetection value changed to $bool1" } - ) - } - fun cancellingFaceAuth() { logBuffer.log(TAG, DEBUG, "cancelling face auth because a gating condition became false") } @@ -236,7 +188,7 @@ constructor( logBuffer.log( TAG, DEBUG, - { str1 = "$uiEvent" }, + { str1 = uiEvent.reason }, { "Requesting face auth for trigger: $str1" } ) } @@ -269,4 +221,77 @@ constructor( fun faceLockedOut(@CompileTimeConstant reason: String) { logBuffer.log(TAG, DEBUG, "Face auth has been locked out: $reason") } + + fun queueingRequest(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { + logBuffer.log( + TAG, + DEBUG, + { + str1 = "$uiEvent" + bool1 = fallbackToDetection + }, + { "Queueing $str1 request for face auth, fallbackToDetection: $bool1" } + ) + } + + fun notProcessingRequestYet( + uiEvent: FaceAuthUiEvent?, + canRunAuth: Boolean, + canRunDetect: Boolean, + cancelInProgress: Boolean + ) { + uiEvent?.let { + logBuffer.log( + TAG, + DEBUG, + { + str1 = uiEvent.reason + bool1 = canRunAuth + bool2 = canRunDetect + bool3 = cancelInProgress + }, + { + "Waiting to process request: reason: $str1, " + + "canRunAuth: $bool1, " + + "canRunDetect: $bool2, " + + "cancelInProgress: $bool3" + } + ) + } + } + + fun processingRequest(uiEvent: FaceAuthUiEvent?, fallbackToDetection: Boolean) { + logBuffer.log( + TAG, + DEBUG, + { + str1 = "${uiEvent?.reason}" + bool1 = fallbackToDetection + }, + { "Processing face auth request: $str1, fallbackToDetect: $bool1" } + ) + } + + fun clearingPendingAuthRequest( + @CompileTimeConstant loggingContext: String, + uiEvent: FaceAuthUiEvent?, + fallbackToDetection: Boolean? + ) { + uiEvent?.let { + logBuffer.log( + TAG, + DEBUG, + { + str1 = uiEvent.reason + str2 = "$fallbackToDetection" + str3 = loggingContext + }, + { + "Clearing pending auth: $str1, " + + "fallbackToDetection: $str2, " + + "reason: $str3" + } + ) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt index 60fd10492628..cf64a838c2bf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt @@ -274,7 +274,9 @@ class MediaProjectionAppSelectorActivity( recentsViewController.hasRecentTasks } - override fun shouldShowContentPreviewWhenEmpty() = shouldShowContentPreview() + override fun shouldShowStickyContentPreviewWhenEmpty() = shouldShowContentPreview() + + override fun shouldShowServiceTargets() = false private fun hasWorkProfile() = mMultiProfilePagerAdapter.count > 1 diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index dddbedac3dcc..e79fc74ffc86 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -22,12 +22,14 @@ import android.app.Notification import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME import android.app.PendingIntent import android.app.StatusBarManager +import android.app.UriGrantsManager import android.app.smartspace.SmartspaceAction import android.app.smartspace.SmartspaceConfig import android.app.smartspace.SmartspaceManager import android.app.smartspace.SmartspaceSession import android.app.smartspace.SmartspaceTarget import android.content.BroadcastReceiver +import android.content.ContentProvider import android.content.ContentResolver import android.content.Context import android.content.Intent @@ -700,10 +702,13 @@ class MediaDataManager( Log.d(TAG, "adding track for $userId from browser: $desc") } + val currentEntry = mediaEntries.get(packageName) + val appUid = currentEntry?.appUid ?: Process.INVALID_UID + // Album art var artworkBitmap = desc.iconBitmap if (artworkBitmap == null && desc.iconUri != null) { - artworkBitmap = loadBitmapFromUri(desc.iconUri!!) + artworkBitmap = loadBitmapFromUriForUser(desc.iconUri!!, userId, appUid, packageName) } val artworkIcon = if (artworkBitmap != null) { @@ -712,9 +717,7 @@ class MediaDataManager( null } - val currentEntry = mediaEntries.get(packageName) val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId() - val appUid = currentEntry?.appUid ?: Process.INVALID_UID val isExplicit = desc.extras?.getLong(MediaConstants.METADATA_KEY_IS_EXPLICIT) == MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT @@ -1261,6 +1264,30 @@ class MediaDataManager( false } } + + /** Returns a bitmap if the user can access the given URI, else null */ + private fun loadBitmapFromUriForUser( + uri: Uri, + userId: Int, + appUid: Int, + packageName: String, + ): Bitmap? { + try { + val ugm = UriGrantsManager.getService() + ugm.checkGrantUriPermission_ignoreNonSystem( + appUid, + packageName, + ContentProvider.getUriWithoutUserId(uri), + Intent.FLAG_GRANT_READ_URI_PERMISSION, + ContentProvider.getUserIdFromUri(uri, userId) + ) + return loadBitmapFromUri(uri) + } catch (e: SecurityException) { + Log.e(TAG, "Failed to get URI permission: $e") + } + return null + } + /** * Load a bitmap from a URI * diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt index 2883210805d3..83a6e58cc444 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt @@ -35,7 +35,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.MediaContainerView import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.util.LargeScreenUtils +import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.settings.SecureSettings import javax.inject.Inject import javax.inject.Named @@ -55,6 +55,7 @@ constructor( private val secureSettings: SecureSettings, @Main private val handler: Handler, configurationController: ConfigurationController, + private val splitShadeStateController: SplitShadeStateController ) { init { @@ -108,7 +109,7 @@ constructor( } private fun updateResources() { - useSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources) + useSplitShade = splitShadeStateController.shouldUseSplitNotificationShade(context.resources) } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt index c1c757e424dc..0b30e597ab1c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt @@ -52,7 +52,7 @@ import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.LargeScreenUtils +import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.traceSection @@ -101,6 +101,7 @@ constructor( panelEventsEvents: ShadeStateEvents, private val secureSettings: SecureSettings, @Main private val handler: Handler, + private val splitShadeStateController: SplitShadeStateController ) { /** Track the media player setting status on lock screen. */ @@ -568,7 +569,7 @@ constructor( context.resources.getDimensionPixelSize( R.dimen.lockscreen_shade_media_transition_distance ) - inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources) + inSplitShade = splitShadeStateController.shouldUseSplitNotificationShade(context.resources) } /** diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt index 5d732fb8ace9..cbb7e1d0ef83 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt @@ -17,6 +17,7 @@ package com.android.systemui.mediaprojection.appselector.view import android.app.ActivityOptions +import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED import android.app.IActivityTaskManager import android.graphics.Rect import android.os.Binder @@ -129,6 +130,9 @@ constructor( view.width, view.height ) + activityOptions.setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_ALLOWED + ) activityOptions.launchCookie = launchCookie activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle()) diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt index 9b9d561b5180..0c7705467e10 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt @@ -28,7 +28,6 @@ import android.view.View import android.view.WindowManager import androidx.core.content.getSystemService import androidx.core.content.res.use -import com.android.internal.R as AndroidR import com.android.systemui.R import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.shared.recents.model.ThumbnailData @@ -147,25 +146,14 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 previewRect.set(0, 0, thumbnailData.thumbnail.width, thumbnailData.thumbnail.height) val currentRotation: Int = display.rotation - val displayWidthPx = windowMetrics.bounds.width() - val displayHeightPx = windowMetrics.bounds.height() val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL val isLargeScreen = isLargeScreen(context) - val taskbarSize = - if (isLargeScreen) { - resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height) - } else { - 0 - } previewPositionHelper.updateThumbnailMatrix( previewRect, thumbnailData, measuredWidth, measuredHeight, - displayWidthPx, - displayHeightPx, - taskbarSize, isLargeScreen, currentRotation, isRtl diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 146b5f57630e..79e7b710c56e 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -359,6 +359,7 @@ public class NavigationBarView extends FrameLayout { public void setBackgroundExecutor(Executor bgExecutor) { mBgExecutor = bgExecutor; + mRotationButtonController.setBgExecutor(bgExecutor); } public void setDisplayTracker(DisplayTracker displayTracker) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 856a92e85ad7..9359958397d1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -39,6 +39,7 @@ import com.android.systemui.settings.brightness.BrightnessMirrorHandler; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.BrightnessMirrorController; +import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.tuner.TunerService; import javax.inject.Inject; @@ -80,9 +81,10 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory, BrightnessSliderController.Factory brightnessSliderFactory, FalsingManager falsingManager, - StatusBarKeyguardViewManager statusBarKeyguardViewManager) { + StatusBarKeyguardViewManager statusBarKeyguardViewManager, + SplitShadeStateController splitShadeStateController) { super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, - metricsLogger, uiEventLogger, qsLogger, dumpManager); + metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController); 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 20f035260ba4..81e3a2f68bfd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -40,7 +40,7 @@ import com.android.systemui.qs.customize.QSCustomizerController; 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.util.LargeScreenUtils; +import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.ViewController; import com.android.systemui.util.animation.DisappearParameters; @@ -86,6 +86,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr private final QSHost.Callback mQSHostCallback = this::setTiles; + private SplitShadeStateController mSplitShadeStateController; + @VisibleForTesting protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = new QSPanel.OnConfigurationChangedListener() { @@ -93,8 +95,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr public void onConfigurationChange(Configuration newConfig) { final boolean previousSplitShadeState = mShouldUseSplitNotificationShade; final int previousOrientation = mLastOrientation; - mShouldUseSplitNotificationShade = - LargeScreenUtils.shouldUseSplitNotificationShade(getResources()); + mShouldUseSplitNotificationShade = mSplitShadeStateController + .shouldUseSplitNotificationShade(getResources()); mLastOrientation = newConfig.orientation; mQSLogger.logOnConfigurationChanged( @@ -138,7 +140,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, - DumpManager dumpManager + DumpManager dumpManager, + SplitShadeStateController splitShadeStateController ) { super(view); mHost = host; @@ -149,8 +152,9 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr mUiEventLogger = uiEventLogger; mQSLogger = qsLogger; mDumpManager = dumpManager; + mSplitShadeStateController = splitShadeStateController; mShouldUseSplitNotificationShade = - LargeScreenUtils.shouldUseSplitNotificationShade(getResources()); + mSplitShadeStateController.shouldUseSplitNotificationShade(getResources()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index 2d543139a1b4..585136ad0789 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.plugins.qs.QSTile; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.leak.RotationUtils; import java.util.ArrayList; @@ -55,10 +56,10 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA) Provider<Boolean> usingCollapsedLandscapeMediaProvider, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, - DumpManager dumpManager + DumpManager dumpManager, SplitShadeStateController splitShadeStateController ) { super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, - uiEventLogger, qsLogger, dumpManager); + uiEventLogger, qsLogger, dumpManager, splitShadeStateController); mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt new file mode 100644 index 000000000000..1a03481b9fca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt @@ -0,0 +1,26 @@ +package com.android.systemui.qs.tiles.base.interactor + +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.Flow + +/** + * Provides data and availability for the tile. In most cases it would delegate data retrieval to + * repository, manager, controller or a combination of those. Avoid doing long running operations in + * these methods because there is no background thread guarantee. Use [Flow.flowOn] (typically + * with @Background [CoroutineDispatcher]) instead to move the calculations to another thread. + */ +interface QSTileDataInteractor<DATA_TYPE> { + + /** + * Returns the data to be mapped to [QSTileState]. Make sure to start the flow [Flow.onStart] + * with the current state to update the tile as soon as possible. + */ + fun tileData(qsTileDataRequest: QSTileDataRequest): Flow<DATA_TYPE> + + /** + * Returns tile availability - whether this device currently supports this tile. Make sure to + * start the flow [Flow.onStart] with the current state to update the tile as soon as possible. + */ + fun availability(): Flow<Boolean> +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt new file mode 100644 index 000000000000..82897044f06c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt @@ -0,0 +1,6 @@ +package com.android.systemui.qs.tiles.base.interactor + +data class QSTileDataRequest( + val userId: Int, + val trigger: StateUpdateTrigger, +) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt new file mode 100644 index 000000000000..8569fc73adb4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt @@ -0,0 +1,13 @@ +package com.android.systemui.qs.tiles.base.interactor + +import android.annotation.WorkerThread +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction + +interface QSTileUserActionInteractor<DATA_TYPE> { + + /** + * Processes user input based on [userAction] and [currentData]. It's safe to run long running + * computations inside this function in this. + */ + @WorkerThread suspend fun handleInput(userAction: QSTileUserAction, currentData: DATA_TYPE) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt new file mode 100644 index 000000000000..ed7ec8e32de4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt @@ -0,0 +1,11 @@ +package com.android.systemui.qs.tiles.base.interactor + +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction + +sealed interface StateUpdateTrigger { + class UserAction<T>(val action: QSTileUserAction, val tileState: QSTileState, val tileData: T) : + StateUpdateTrigger + data object ForceUpdate : StateUpdateTrigger + data object InitialRequest : StateUpdateTrigger +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt new file mode 100644 index 000000000000..a5eaac154230 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt @@ -0,0 +1,14 @@ +package com.android.systemui.qs.tiles.viewmodel + +import android.graphics.drawable.Icon +import com.android.systemui.qs.pipeline.shared.TileSpec + +data class QSTileConfig( + val tileSpec: TileSpec, + val tileIcon: Icon, + val tileLabel: CharSequence, +// TODO(b/299908705): Fill necessary params +/* +val instanceId: InstanceId, + */ +) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt new file mode 100644 index 000000000000..39db7038bfa2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt @@ -0,0 +1,6 @@ +package com.android.systemui.qs.tiles.viewmodel + +enum class QSTileLifecycle { + ON_CREATE, + ON_DESTROY, +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt new file mode 100644 index 000000000000..53f9edfb954c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt @@ -0,0 +1,18 @@ +package com.android.systemui.qs.tiles.viewmodel + +import android.graphics.drawable.Icon + +data class QSTileState( + val icon: Icon, + val label: CharSequence, +// TODO(b/299908705): Fill necessary params +/* + val subtitle: CharSequence = "", + val activeState: ActivationState = Active, + val enabledState: Enabled = Enabled, + val loopIconAnimation: Boolean = false, + val secondaryIcon: Icon? = null, + val slashState: SlashState? = null, + val supportedActions: Collection<UserAction> = listOf(Click), clicks should be a default action +*/ +) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt new file mode 100644 index 000000000000..f1f8f0152c67 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt @@ -0,0 +1,13 @@ +package com.android.systemui.qs.tiles.viewmodel + +import android.content.Context +import android.view.View + +sealed interface QSTileUserAction { + + val context: Context + val view: View? + + class Click(override val context: Context, override val view: View?) : QSTileUserAction + class LongClick(override val context: Context, override val view: View?) : QSTileUserAction +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt new file mode 100644 index 000000000000..49077f3f310d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt @@ -0,0 +1,41 @@ +package com.android.systemui.qs.tiles.viewmodel + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +/** + * Represents tiles behaviour logic. This ViewModel is a connection between tile view and data + * layers. + */ +interface QSTileViewModel { + + /** + * State of the tile to be shown by the view. Favor reactive consumption over the + * [StateFlow.value], because there is no guarantee that current value would be available at any + * time. + */ + val state: StateFlow<QSTileState> + + val config: QSTileConfig + + val isAvailable: Flow<Boolean> + + /** + * Handles ViewModel lifecycle. Implementations should be inactive outside of + * [QSTileLifecycle.ON_CREATE] and [QSTileLifecycle.ON_DESTROY] bounds. + */ + fun onLifecycle(lifecycle: QSTileLifecycle) + + /** + * Notifies about the user change. Implementations should avoid using 3rd party userId sources + * and use this value instead. This is to maintain consistent and concurrency-free behaviour + * across different parts of QS. + */ + fun onUserIdChanged(userId: Int) + + /** Triggers emit of the new [QSTileState] in [state]. */ + fun forceUpdate() + + /** Notifies underlying logic about user input. */ + fun onActionPerformed(userAction: QSTileUserAction) +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index d23bedace06f..cef52e73d711 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -151,6 +151,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private SysUiState mSysUiState; private final Handler mHandler; private final Lazy<NavigationBarController> mNavBarControllerLazy; + private final ScreenPinningRequest mScreenPinningRequest; private final NotificationShadeWindowController mStatusBarWinController; private final Provider<SceneInteractor> mSceneInteractor; @@ -185,9 +186,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @Override public void startScreenPinning(int taskId) { verifyCallerAndClearCallingIdentityPostMain("startScreenPinning", () -> - mCentralSurfacesOptionalLazy.get().ifPresent( - statusBar -> statusBar.showScreenPinningRequest(taskId, - false /* allowCancel */))); + mScreenPinningRequest.showPrompt(taskId, false /* allowCancel */)); } @Override @@ -572,6 +571,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis Lazy<NavigationBarController> navBarControllerLazy, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, Lazy<ShadeViewController> shadeViewControllerLazy, + ScreenPinningRequest screenPinningRequest, NavigationModeController navModeController, NotificationShadeWindowController statusBarWinController, SysUiState sysUiState, @@ -601,6 +601,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mShadeViewControllerLazy = shadeViewControllerLazy; mHandler = new Handler(); mNavBarControllerLazy = navBarControllerLazy; + mScreenPinningRequest = screenPinningRequest; mStatusBarWinController = statusBarWinController; mSceneInteractor = sceneInteractor; mUserTracker = userTracker; diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 346acf958e51..77fcd6813583 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -53,8 +53,10 @@ import android.widget.TextView; import androidx.annotation.NonNull; +import com.android.systemui.CoreStartable; import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.settings.UserTracker; @@ -69,8 +71,9 @@ import javax.inject.Inject; import dagger.Lazy; +@SysUISingleton public class ScreenPinningRequest implements View.OnClickListener, - NavigationModeController.ModeChangedListener { + NavigationModeController.ModeChangedListener, CoreStartable { private static final String TAG = "ScreenPinningRequest"; private final Context mContext; @@ -113,6 +116,9 @@ public class ScreenPinningRequest implements View.OnClickListener, mUserTracker = userTracker; } + @Override + public void start() {} + public void clearPrompt() { if (mRequestWindow != null) { mWindowManager.removeView(mRequestWindow); @@ -144,7 +150,8 @@ public class ScreenPinningRequest implements View.OnClickListener, mNavBarMode = mode; } - public void onConfigurationChanged() { + @Override + public void onConfigurationChanged(Configuration newConfig) { if (mRequestWindow != null) { mRequestWindow.onConfigurationChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index c5bc2fb8ea90..03c3f7a8ddf3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -150,16 +150,18 @@ constructor( } /** - * Returns a [RequestCallback] that calls [RequestCallback.onFinish] only when all callbacks for - * id created have finished. + * Returns a [RequestCallback] that wraps [originalCallback]. * - * If any callback created calls [reportError], then following [onFinish] are not considered. + * Each [RequestCallback] created with [createCallbackForId] is expected to be used with either + * [reportError] or [onFinish]. Once they are both called: + * - If any finished with an error, [reportError] of [originalCallback] is called + * - Otherwise, [onFinish] is called. */ private class MultiResultCallbackWrapper( private val originalCallback: RequestCallback, ) { private val idsPending = mutableSetOf<Int>() - private var errorReported = false + private val idsWithErrors = mutableSetOf<Int>() /** * Creates a callback for [id]. @@ -172,25 +174,34 @@ constructor( idsPending += id return object : RequestCallback { override fun reportError() { - Log.d(TAG, "ReportError id=$id") - Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TAG, id) - Trace.instantForTrack(Trace.TRACE_TAG_APP, TAG, "reportError id=$id") - originalCallback.reportError() - errorReported = true + endTrace("reportError id=$id") + idsWithErrors += id + idsPending -= id + reportToOriginalIfNeeded() } override fun onFinish() { - Log.d(TAG, "onFinish id=$id") - if (errorReported) return + endTrace("onFinish id=$id") idsPending -= id - Trace.instantForTrack(Trace.TRACE_TAG_APP, TAG, "onFinish id=$id") - if (idsPending.isEmpty()) { - Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TAG, id) - originalCallback.onFinish() - } + reportToOriginalIfNeeded() + } + + private fun endTrace(reason: String) { + Log.d(TAG, "Finished waiting for id=$id. $reason") + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TAG, id) + Trace.instantForTrack(Trace.TRACE_TAG_APP, TAG, reason) } } } + + private fun reportToOriginalIfNeeded() { + if (idsPending.isNotEmpty()) return + if (idsWithErrors.isEmpty()) { + originalCallback.onFinish() + } else { + originalCallback.reportError() + } + } } private companion object { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 1e8542fe8f0c..32df5121b0b8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -99,7 +99,11 @@ public class TakeScreenshotService extends Service { /** Informs about coarse grained state of the Controller. */ public interface RequestCallback { - /** Respond to the current request indicating the screenshot request failed. */ + /** + * Respond to the current request indicating the screenshot request failed. + * <p> + * After this, the service will be disconnected and all visible UI is removed. + */ void reportError(); /** The controller has completed handling this request UI has been removed */ diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 49ce832c1e8c..d2e80fc7cf65 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -141,7 +141,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.shared.model.WakefulnessModel; import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder; -import com.android.systemui.keyguard.ui.view.KeyguardRootView; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; @@ -206,7 +205,6 @@ import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm; -import com.android.systemui.statusbar.phone.KeyguardStatusBarView; import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; @@ -223,10 +221,10 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; +import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.util.Compile; -import com.android.systemui.util.LargeScreenUtils; import com.android.systemui.util.Utils; import com.android.systemui.util.time.SystemClock; import com.android.wm.shell.animation.FlingAnimationUtils; @@ -236,7 +234,6 @@ import kotlin.Unit; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; -import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -320,7 +317,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; private final LayoutInflater mLayoutInflater; private final FeatureFlags mFeatureFlags; - private final PowerManager mPowerManager; private final AccessibilityManager mAccessibilityManager; private final NotificationWakeUpCoordinator mWakeUpCoordinator; private final PulseExpansionHandler mPulseExpansionHandler; @@ -357,7 +353,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate(); private final NotificationGutsManager mGutsManager; private final AlternateBouncerInteractor mAlternateBouncerInteractor; - private final KeyguardRootView mKeyguardRootView; private final QuickSettingsController mQsController; private final TouchHandler mTouchHandler = new TouchHandler(); @@ -370,8 +365,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private float mExpandedHeight = 0; /** The current squish amount for the predictive back animation */ private float mCurrentBackProgress = 0.0f; - private boolean mTracking; - private boolean mHintAnimationRunning; @Deprecated private KeyguardBottomAreaView mKeyguardBottomArea; private boolean mExpanding; @@ -387,7 +380,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private int mMaxAllowedKeyguardNotifications; private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController; private KeyguardUserSwitcherController mKeyguardUserSwitcherController; - private KeyguardStatusBarView mKeyguardStatusBar; private KeyguardStatusBarViewController mKeyguardStatusBarViewController; private KeyguardStatusViewController mKeyguardStatusViewController; private final LockIconViewController mLockIconViewController; @@ -622,7 +614,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private int mLockscreenToDreamingTransitionTranslationY; private int mGoneToDreamingTransitionTranslationY; private int mLockscreenToOccludedTransitionTranslationY; - + private SplitShadeStateController mSplitShadeStateController; private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */, mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */); private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = @@ -781,7 +773,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump SharedNotificationContainerInteractor sharedNotificationContainerInteractor, KeyguardViewConfigurator keyguardViewConfigurator, KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, - KeyguardRootView keyguardRootView) { + SplitShadeStateController splitShadeStateController) { keyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override public void onKeyguardFadingAwayChanged() { @@ -877,8 +869,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mFragmentService = fragmentService; mStatusBarService = statusBarService; mSettingsChangeObserver = new SettingsChangeObserver(handler); + mSplitShadeStateController = splitShadeStateController; mSplitShadeEnabled = - LargeScreenUtils.shouldUseSplitNotificationShade(mResources); + mSplitShadeStateController.shouldUseSplitNotificationShade(mResources); mView.setWillNotDraw(!DEBUG_DRAWABLE); mShadeHeaderController = shadeHeaderController; mLayoutInflater = layoutInflater; @@ -886,7 +879,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE); mTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES); mFalsingCollector = falsingCollector; - mPowerManager = powerManager; mWakeUpCoordinator = coordinator; mMainDispatcher = mainDispatcher; mAccessibilityManager = accessibilityManager; @@ -987,7 +979,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } }); mAlternateBouncerInteractor = alternateBouncerInteractor; - mKeyguardRootView = keyguardRootView; dumpManager.registerDumpable(this); } @@ -1006,7 +997,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // cause blurring. This will eventually be re-enabled by the panel view on // ACTION_UP, since the user's finger might still be down after a swipe to // unlock gesture, and we don't want that to cause blurring either. - mDepthController.setBlursDisabledForUnlock(mTracking); + mDepthController.setBlursDisabledForUnlock(isTracking()); if (playingCannedAnimation && !isWakeAndUnlockNotFromDream) { // Hide the panel so it's not in the way or the surface behind the @@ -1042,7 +1033,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @VisibleForTesting void onFinishInflate() { loadDimens(); - mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header); FrameLayout userAvatarContainer = null; KeyguardUserSwitcherView keyguardUserSwitcherView = null; @@ -1060,7 +1050,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusBarViewController = mKeyguardStatusBarViewComponentFactory.build( - mKeyguardStatusBar, + mView.findViewById(R.id.keyguard_header), mShadeViewStateProvider) .getKeyguardStatusBarViewController(); mKeyguardStatusBarViewController.init(); @@ -1233,7 +1223,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void updateViewControllers( FrameLayout userAvatarView, KeyguardUserSwitcherView keyguardUserSwitcherView) { - updateStatusBarViewController(); + updateStatusViewController(); if (mKeyguardUserSwitcherController != null) { // Try to close the switcher so that callbacks are triggered if necessary. // Otherwise, NPV can get into a state where some of the views are still hidden @@ -1264,7 +1254,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } /** Updates the StatusBarViewController and updates any that depend on it. */ - public void updateStatusBarViewController() { + public void updateStatusViewController() { // Re-associate the KeyguardStatusViewController if (mKeyguardStatusViewController != null) { mKeyguardStatusViewController.onDestroy(); @@ -1300,7 +1290,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void updateResources() { final boolean newSplitShadeEnabled = - LargeScreenUtils.shouldUseSplitNotificationShade(mResources); + mSplitShadeStateController.shouldUseSplitNotificationShade(mResources); final boolean splitShadeChanged = mSplitShadeEnabled != newSplitShadeEnabled; mSplitShadeEnabled = newSplitShadeEnabled; mQsController.updateResources(); @@ -1519,7 +1509,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private boolean shouldAvoidChangingNotificationsCount() { - return mHintAnimationRunning || mUnlockedScreenOffAnimationController.isAnimationPlaying(); + return mUnlockedScreenOffAnimationController.isAnimationPlaying(); } @Deprecated @@ -1849,6 +1839,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** Returns extra space available to show the shelf on lockscreen */ @VisibleForTesting float getVerticalSpaceForLockscreenShelf() { + if (mSplitShadeEnabled) { + return 0f; + } final float lockIconPadding = getLockIconPadding(); final float noShelfOverlapBottomPadding = @@ -2549,10 +2542,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void onHeightUpdated(float expandedHeight) { if (expandedHeight <= 0) { mShadeLog.logExpansionChanged("onHeightUpdated: fully collapsed.", - mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx); + mExpandedFraction, isExpanded(), isTracking(), mExpansionDragDownAmountPx); } else if (isFullyExpanded()) { mShadeLog.logExpansionChanged("onHeightUpdated: fully expanded.", - mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx); + mExpandedFraction, isExpanded(), isTracking(), mExpansionDragDownAmountPx); } if (!mQsController.getExpanded() || mQsController.isExpandImmediate() || mIsExpandingOrCollapsing && mQsController.getExpandedWhenExpandingStarted()) { @@ -2645,7 +2638,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump && !mHeadsUpManager.hasPinnedHeadsUp()) { alpha = getFadeoutAlpha(); } - if (mBarState == KEYGUARD && !mHintAnimationRunning + if (mBarState == KEYGUARD && !mKeyguardBypassController.getBypassEnabled() && !mQsController.getFullyExpanded()) { alpha *= mClockPositionResult.clockAlpha; @@ -2683,7 +2676,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // change due to "unlock hint animation." In this case, fading out the bottom area // would also hide the message that says "swipe to unlock," we don't want to do that. float expansionAlpha = MathUtils.constrainedMap(0f, 1f, - isUnlockHintRunning() ? 0f : KeyguardBouncerConstants.ALPHA_EXPANSION_THRESHOLD, 1f, + KeyguardBouncerConstants.ALPHA_EXPANSION_THRESHOLD, 1f, getExpandedFraction()); float alpha = Math.min(expansionAlpha, 1 - mQsController.computeExpansionFraction()); @@ -2745,7 +2738,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mAnimateAfterExpanding = animate; mUpdateFlingOnLayout = false; abortAnimations(); - if (mTracking) { + if (isTracking()) { // The panel is expanded after this call. onTrackingStopped(true /* expands */); } @@ -2832,7 +2825,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void onTrackingStarted() { endClosing(); - mTracking = true; + mShadeRepository.setLegacyShadeTracking(true); mTrackingStartedListener.onTrackingStarted(); notifyExpandingStarted(); updateExpansionAndVisibility(); @@ -2846,7 +2839,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void onTrackingStopped(boolean expand) { - mTracking = false; + mShadeRepository.setLegacyShadeTracking(false); updateExpansionAndVisibility(); if (expand) { @@ -2865,44 +2858,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mView.getHeight(), mNavigationBarBottomHeight); } - @VisibleForTesting - void startUnlockHintAnimation() { - if (mPowerManager.isPowerSaveMode() || mAmbientState.getDozeAmount() > 0f) { - onUnlockHintStarted(); - onUnlockHintFinished(); - return; - } - - // We don't need to hint the user if an animation is already running or the user is changing - // the expansion. - if (mHeightAnimator != null || mTracking) { - return; - } - notifyExpandingStarted(); - startUnlockHintAnimationPhase1(() -> { - notifyExpandingFinished(); - onUnlockHintFinished(); - mHintAnimationRunning = false; - }); - onUnlockHintStarted(); - mHintAnimationRunning = true; - } - - @VisibleForTesting - void onUnlockHintFinished() { - // Delay the reset a bit so the user can read the text. - mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS); - mScrimController.setExpansionAffectsAlpha(true); - mNotificationStackScrollLayoutController.setUnlockHintRunning(false); - } - - @VisibleForTesting - void onUnlockHintStarted() { - mKeyguardIndicationController.showActionToUnlock(); - mScrimController.setExpansionAffectsAlpha(false); - mNotificationStackScrollLayoutController.setUnlockHintRunning(true); - } - private boolean shouldUseDismissingAnimation() { return mBarState != StatusBarState.SHADE && (mKeyguardStateController.canDismissLockScreen() || !isTracking()); @@ -2989,7 +2944,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); mLockscreenGestureLogger .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT); - startUnlockHintAnimation(); + mKeyguardIndicationController.showActionToUnlock(); } } break; @@ -3056,7 +3011,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void updateExpandedHeight(float expandedHeight) { - if (mTracking) { + if (isTracking()) { mNotificationStackScrollLayoutController .setExpandingVelocity(getCurrentExpandVelocity()); } @@ -3145,7 +3100,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mTouchDisabled = disabled; if (mTouchDisabled) { cancelHeightAnimator(); - if (mTracking) { + if (isTracking()) { onTrackingStopped(true /* expanded */); } notifyExpandingFinished(); @@ -3384,7 +3339,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void blockExpansionForCurrentTouch() { - mBlockingExpansionForCurrentTouch = mTracking; + mBlockingExpansionForCurrentTouch = isTracking(); } @Override @@ -3398,8 +3353,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ipw.print("mIsLaunchAnimationRunning="); ipw.println(mIsLaunchAnimationRunning); ipw.print("mOverExpansion="); ipw.println(mOverExpansion); ipw.print("mExpandedHeight="); ipw.println(mExpandedHeight); - ipw.print("mTracking="); ipw.println(mTracking); - ipw.print("mHintAnimationRunning="); ipw.println(mHintAnimationRunning); + ipw.print("isTracking()="); ipw.println(isTracking()); ipw.print("mExpanding="); ipw.println(mExpanding); ipw.print("mSplitShadeEnabled="); ipw.println(mSplitShadeEnabled); ipw.print("mKeyguardNotificationBottomPadding="); @@ -3737,7 +3691,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mQsController.beginJankMonitoring(isFullyCollapsed()); } mInitialOffsetOnTouch = expandedHeight; - if (!mTracking || isFullyCollapsed()) { + if (!isTracking() || isFullyCollapsed()) { mInitialExpandY = newY; mInitialExpandX = newX; } else { @@ -3755,7 +3709,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mShadeLog.logEndMotionEvent("endMotionEvent called", forceCancel, false); mTrackingPointer = -1; mAmbientState.setSwipingUp(false); - if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop + if ((isTracking() && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop || Math.abs(y - mInitialExpandY) > mTouchSlop || (!isFullyExpanded() && !isFullyCollapsed()) || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { @@ -3776,8 +3730,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump expand = true; mShadeLog.logEndMotionEvent("endMotionEvent: cancel while on keyguard", forceCancel, expand); - } else if (mCentralSurfaces.isBouncerShowingOverDream()) { - expand = false; } else { // If we get a cancel, put the shade back to the state it was in when the // gesture started @@ -3919,7 +3871,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return; } - if (mTracking && !(mBlockingExpansionForCurrentTouch + if (isTracking() && !(mBlockingExpansionForCurrentTouch || mQsController.isTrackingBlocked())) { return; } @@ -3945,7 +3897,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump float maxPanelHeight = getMaxPanelTransitionDistance(); if (mHeightAnimator == null) { // Split shade has its own overscroll logic - if (mTracking) { + if (isTracking()) { float overExpansionPixels = Math.max(0, h - maxPanelHeight); setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */); } @@ -4032,12 +3984,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } public boolean isTracking() { - return mTracking; + return mShadeRepository.getLegacyShadeTracking().getValue(); } @Override public boolean canBeCollapsed() { - return !isFullyCollapsed() && !mTracking && !mClosing; + return !isFullyCollapsed() && !isTracking() && !mClosing; } @Override @@ -4058,73 +4010,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mView.removeCallbacks(mFlingCollapseRunnable); } - @Override - public boolean isUnlockHintRunning() { - return mHintAnimationRunning; - } - - /** - * Phase 1: Move everything upwards. - */ - private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) { - float target = Math.max(0, getMaxPanelHeight() - mHintDistance); - ValueAnimator animator = createHeightAnimator(target); - animator.setDuration(250); - animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - animator.addListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - - @Override - public void onAnimationCancel(Animator animation) { - mCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - if (mCancelled) { - setAnimator(null); - onAnimationFinished.run(); - } else { - startUnlockHintAnimationPhase2(onAnimationFinished); - } - } - }); - animator.start(); - setAnimator(animator); - - - if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { - final ViewPropertyAnimator mKeyguardRootViewAnimator = mKeyguardRootView.animate(); - mKeyguardRootViewAnimator - .translationY(-mHintDistance) - .setDuration(250) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .withEndAction(() -> mKeyguardRootViewAnimator - .translationY(0) - .setDuration(450) - .setInterpolator(mBounceInterpolator) - .start()) - .start(); - } else { - final List<ViewPropertyAnimator> indicationAnimators = - mKeyguardBottomArea.getIndicationAreaAnimators(); - - for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) { - indicationAreaAnimator - .translationY(-mHintDistance) - .setDuration(250) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .withEndAction(() -> indicationAreaAnimator - .translationY(0) - .setDuration(450) - .setInterpolator(mBounceInterpolator) - .start()) - .start(); - } - } - - } - private void setAnimator(ValueAnimator animator) { mHeightAnimator = animator; if (animator == null && mPanelUpdateWhenAnimatorEnds) { @@ -4135,7 +4020,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** Returns whether a shade or QS expansion animation is running */ private boolean isShadeOrQsHeightAnimationRunning() { - return mHeightAnimator != null && !mHintAnimationRunning && !mIsSpringBackAnimation; + return mHeightAnimator != null && !mIsSpringBackAnimation; } /** @@ -4196,7 +4081,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void updateExpansionAndVisibility() { mShadeExpansionStateManager.onPanelExpansionChanged( - mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx); + mExpandedFraction, isExpanded(), isTracking(), mExpansionDragDownAmountPx); updateVisibility(); } @@ -4206,7 +4091,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return mExpandedFraction > 0f || mInstantExpanding || isPanelVisibleBecauseOfHeadsUp() - || mTracking + || isTracking() || mHeightAnimator != null || isPanelVisibleBecauseScrimIsAnimatingOff() && !mIsSpringBackAnimation; @@ -4214,9 +4099,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** Called when the user performs a click anywhere in the empty area of the panel. */ private void onEmptySpaceClick() { - if (!mHintAnimationRunning) { - onMiddleClicked(); - } + onMiddleClicked(); } @VisibleForTesting @@ -4816,11 +4699,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return mStatusBarStateListener; } - @VisibleForTesting - boolean isHintAnimationRunning() { - return mHintAnimationRunning; - } - private void onStatusBarWindowStateChanged(@StatusBarManager.WindowVisibleState int state) { if (state != WINDOW_STATE_SHOWING && mStatusBarStateController.getState() == StatusBarState.SHADE) { @@ -4909,15 +4787,14 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation; mMinExpandHeight = 0.0f; mDownTime = mSystemClock.uptimeMillis(); - if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) { + if (mAnimatingOnDown && mClosing) { cancelHeightAnimator(); mTouchSlopExceeded = true; mShadeLog.v("NotificationPanelViewController MotionEvent intercepted:" - + " mAnimatingOnDown: true, mClosing: true, mHintAnimationRunning:" - + " false"); + + " mAnimatingOnDown: true, mClosing: true"); return true; } - if (!mTracking || isFullyCollapsed()) { + if (!isTracking() || isFullyCollapsed()) { mInitialExpandY = y; mInitialExpandX = x; } else { @@ -5012,10 +4889,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return false; } - // Do not allow panel expansion if bouncer is scrimmed or showing over a dream, + // Do not allow panel expansion if bouncer is scrimmed, // otherwise user would be able to pull down QS or expand the shade. - if (mCentralSurfaces.isBouncerShowingScrimmed() - || mCentralSurfaces.isBouncerShowingOverDream()) { + if (mCentralSurfaces.isBouncerShowingScrimmed()) { mShadeLog.logMotionEvent(event, "onTouch: ignore touch, bouncer scrimmed or showing over dream"); return false; @@ -5094,7 +4970,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // If dragging should not expand the notifications shade, then return false. if (!mNotificationsDragEnabled) { - if (mTracking) { + if (isTracking()) { // Turn off tracking if it's on or the shade can get stuck in the down position. onTrackingStopped(true /* expand */); } @@ -5220,7 +5096,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump && (Math.abs(h) > Math.abs(x - mInitialExpandX) || mIgnoreXTouchSlop)) { mTouchSlopExceeded = true; - if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) { + if (mGestureWaitForTouchSlop + && !isTracking() + && !mCollapsedAndHeadsUpOnDown) { if (mInitialOffsetOnTouch != 0f) { startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); h = 0; @@ -5235,7 +5113,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mTouchAboveFalsingThreshold = true; mUpwardsWhenThresholdReached = isDirectionUpwards(x, y); } - if ((!mGestureWaitForTouchSlop || mTracking) + if ((!mGestureWaitForTouchSlop || isTracking()) && !(mBlockingExpansionForCurrentTouch || mQsController.isTrackingBlocked())) { // Count h==0 as part of swipe-up, @@ -5261,7 +5139,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } break; } - return !mGestureWaitForTouchSlop || mTracking; + return !mGestureWaitForTouchSlop || isTracking(); } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index 941254223965..bdf114efb2b4 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -39,6 +39,7 @@ import com.android.systemui.recents.OverviewProxyService import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener import com.android.systemui.shared.system.QuickStepContract import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.LargeScreenUtils import com.android.systemui.util.ViewController import com.android.systemui.util.concurrency.DelayableExecutor @@ -61,6 +62,7 @@ class NotificationsQSContainerController @Inject constructor( private val featureFlags: FeatureFlags, private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController, + private val splitShadeStateController: SplitShadeStateController ) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController { private var qsExpanded = false @@ -149,7 +151,8 @@ class NotificationsQSContainerController @Inject constructor( } fun updateResources() { - val newSplitShadeEnabled = LargeScreenUtils.shouldUseSplitNotificationShade(resources) + val newSplitShadeEnabled = + splitShadeStateController.shouldUseSplitNotificationShade(resources) val splitShadeEnabledChanged = newSplitShadeEnabled != splitShadeEnabled splitShadeEnabled = newSplitShadeEnabled largeScreenShadeHeaderActive = LargeScreenUtils.shouldUseLargeScreenShadeHeader(resources) diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index b2bbffdb5453..ff0d78f89e68 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -100,6 +100,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.LargeScreenUtils; import com.android.systemui.util.kotlin.JavaAdapter; @@ -150,6 +151,7 @@ public class QuickSettingsController implements Dumpable { private final ShadeLogger mShadeLog; private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; private final CastController mCastController; + private final SplitShadeStateController mSplitShadeStateController; private final FeatureFlags mFeatureFlags; private final InteractionJankMonitor mInteractionJankMonitor; private final ShadeRepository mShadeRepository; @@ -199,8 +201,6 @@ public class QuickSettingsController implements Dumpable { private float mInitialTouchY; /** whether current touch Y delta is above falsing threshold */ private boolean mTouchAboveFalsingThreshold; - /** whether we are tracking a touch on QS container */ - private boolean mTracking; /** pointerId of the pointer we're currently tracking */ private int mTrackingPointer; @@ -344,14 +344,16 @@ public class QuickSettingsController implements Dumpable { ShadeRepository shadeRepository, ShadeInteractor shadeInteractor, JavaAdapter javaAdapter, - CastController castController + CastController castController, + SplitShadeStateController splitShadeStateController ) { mPanelViewControllerLazy = panelViewControllerLazy; mPanelView = panelView; mQsFrame = mPanelView.findViewById(R.id.qs_frame); mKeyguardStatusBar = mPanelView.findViewById(R.id.keyguard_header); mResources = mPanelView.getResources(); - mSplitShadeEnabled = LargeScreenUtils.shouldUseSplitNotificationShade(mResources); + mSplitShadeStateController = splitShadeStateController; + mSplitShadeEnabled = mSplitShadeStateController.shouldUseSplitNotificationShade(mResources); mQsFrameTranslateController = qsFrameTranslateController; mShadeTransitionController = shadeTransitionController; mPulseExpansionHandler = pulseExpansionHandler; @@ -437,7 +439,7 @@ public class QuickSettingsController implements Dumpable { } void updateResources() { - mSplitShadeEnabled = LargeScreenUtils.shouldUseSplitNotificationShade(mResources); + mSplitShadeEnabled = mSplitShadeStateController.shouldUseSplitNotificationShade(mResources); if (mQs != null) { mQs.setInSplitShade(mSplitShadeEnabled); } @@ -596,7 +598,7 @@ public class QuickSettingsController implements Dumpable { @VisibleForTesting boolean isTracking() { - return mTracking; + return mShadeRepository.getLegacyQsTracking().getValue(); } public boolean getFullyExpanded() { @@ -609,10 +611,14 @@ public class QuickSettingsController implements Dumpable { // split shade as there QS are always expanded so every collapsing motion is motion from // expanded QS to closed panel return mExpandImmediate || (mExpanded - && !mTracking && !isExpansionAnimating() + && !isTracking() && !isExpansionAnimating() && !mExpansionFromOverscroll); } + private void setTracking(boolean tracking) { + mShadeRepository.setLegacyQsTracking(tracking); + } + private boolean isQsFragmentCreated() { return mQs != null; } @@ -1597,7 +1603,7 @@ public class QuickSettingsController implements Dumpable { if (action == MotionEvent.ACTION_DOWN && expandedShadeCollapsedQs) { // Down in the empty area while fully expanded - go to QS. mShadeLog.logMotionEvent(event, "handleQsTouch: down action, QS tracking enabled"); - mTracking = true; + setTracking(true); traceQsJank(true, false); mConflictingExpansionGesture = true; onExpansionStarted(); @@ -1612,9 +1618,9 @@ public class QuickSettingsController implements Dumpable { // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS. if (!mSplitShadeEnabled && !mLastShadeFlingWasExpanding && computeExpansionFraction() <= 0.01 && mShadeExpandedFraction < 1.0) { - mTracking = false; + setTracking(false); } - if (!isExpandImmediate() && mTracking) { + if (!isExpandImmediate() && isTracking()) { onTouch(event); if (!mConflictingExpansionGesture && !mSplitShadeEnabled) { return true; @@ -1658,7 +1664,7 @@ public class QuickSettingsController implements Dumpable { if (shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) { mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled"); - mTracking = true; + setTracking(true); onExpansionStarted(); mInitialHeightOnTouch = mExpansionHeight; mInitialTouchY = event.getY(); @@ -1684,7 +1690,7 @@ public class QuickSettingsController implements Dumpable { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mShadeLog.logMotionEvent(event, "onQsTouch: down action, QS tracking enabled"); - mTracking = true; + setTracking(true); traceQsJank(true, false); mInitialTouchY = y; mInitialTouchX = x; @@ -1721,7 +1727,7 @@ public class QuickSettingsController implements Dumpable { case MotionEvent.ACTION_CANCEL: mShadeLog.logMotionEvent(event, "onQsTouch: up/cancel action, QS tracking disabled"); - mTracking = false; + setTracking(false); mTrackingPointer = -1; trackMovement(event); float fraction = computeExpansionFraction(); @@ -1776,7 +1782,7 @@ public class QuickSettingsController implements Dumpable { mInitialHeightOnTouch = mExpansionHeight; mShadeLog.logMotionEvent(event, "onQsIntercept: down action, QS tracking enabled"); - mTracking = true; + setTracking(true); traceQsJank(true, false); mNotificationStackScrollLayoutController.cancelLongPress(); } @@ -1795,7 +1801,7 @@ public class QuickSettingsController implements Dumpable { case MotionEvent.ACTION_MOVE: final float h = y - mInitialTouchY; trackMovement(event); - if (mTracking) { + if (isTracking()) { // Already tracking because onOverscrolled was called. We need to update here // so we don't stop for a frame until the next touch event gets handled in // onTouchEvent. @@ -1815,7 +1821,7 @@ public class QuickSettingsController implements Dumpable { mInitialTouchX, mInitialTouchY, h)) { mPanelView.getParent().requestDisallowInterceptTouchEvent(true); mShadeLog.onQsInterceptMoveQsTrackingEnabled(h); - mTracking = true; + setTracking(true); traceQsJank(true, false); onExpansionStarted(); mPanelViewControllerLazy.get().notifyExpandingFinished(); @@ -1835,7 +1841,7 @@ public class QuickSettingsController implements Dumpable { case MotionEvent.ACTION_UP: trackMovement(event); mShadeLog.logMotionEvent(event, "onQsIntercept: up action, QS tracking disabled"); - mTracking = false; + setTracking(false); break; } return false; @@ -2061,7 +2067,7 @@ public class QuickSettingsController implements Dumpable { ipw.print("mTouchAboveFalsingThreshold="); ipw.println(mTouchAboveFalsingThreshold); ipw.print("mTracking="); - ipw.println(mTracking); + ipw.println(isTracking()); ipw.print("mTrackingPointer="); ipw.println(mTrackingPointer); ipw.print("mExpanded="); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index 656411874de5..9a356ad1fd0b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -135,7 +135,8 @@ constructor( private val date: TextView = header.requireViewById(R.id.date) private val iconContainer: StatusIconContainer = header.requireViewById(R.id.statusIcons) private val mShadeCarrierGroup: ShadeCarrierGroup = header.requireViewById(R.id.carrier_group) - private val systemIcons: View = header.requireViewById(R.id.shade_header_system_icons) + private val systemIconsHoverContainer: View = + header.requireViewById(R.id.hover_system_icons_container) private var roundedCorners = 0 private var cutout: DisplayCutout? = null @@ -259,14 +260,18 @@ constructor( header.paddingRight, header.paddingBottom ) - systemIcons.setPaddingRelative( + systemIconsHoverContainer.setPaddingRelative( resources.getDimensionPixelSize( - R.dimen.shade_header_system_icons_padding_start + R.dimen.hover_system_icons_container_padding_start ), - resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_top), - resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_end), resources.getDimensionPixelSize( - R.dimen.shade_header_system_icons_padding_bottom + R.dimen.hover_system_icons_container_padding_top + ), + resources.getDimensionPixelSize( + R.dimen.hover_system_icons_container_padding_end + ), + resources.getDimensionPixelSize( + R.dimen.hover_system_icons_container_padding_bottom ) ) } @@ -330,8 +335,8 @@ constructor( demoModeController.addCallback(demoModeReceiver) statusBarIconController.addIconGroup(iconManager) nextAlarmController.addCallback(nextAlarmCallback) - systemIcons.setOnHoverListener( - statusOverlayHoverListenerFactory.createListener(systemIcons) + systemIconsHoverContainer.setOnHoverListener( + statusOverlayHoverListenerFactory.createListener(systemIconsHoverContainer) ) } @@ -343,7 +348,7 @@ constructor( demoModeController.removeCallback(demoModeReceiver) statusBarIconController.removeIconGroup(iconManager) nextAlarmController.removeCallback(nextAlarmCallback) - systemIcons.setOnHoverListener(null) + systemIconsHoverContainer.setOnHoverListener(null) } fun disable(state1: Int, state2: Int, animate: Boolean) { @@ -479,11 +484,11 @@ constructor( if (largeScreenActive) { logInstantEvent("Large screen constraints set") header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID) - systemIcons.setOnClickListener { shadeCollapseAction?.run() } + systemIconsHoverContainer.setOnClickListener { shadeCollapseAction?.run() } } else { logInstantEvent("Small screen constraints set") header.setTransition(HEADER_TRANSITION_ID) - systemIcons.setOnClickListener(null) + systemIconsHoverContainer.setOnClickListener(null) } header.jumpToState(header.startState) updatePosition() diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index cdbea8185edd..b3f6e1681ad6 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -199,12 +199,6 @@ interface ShadeViewController { /** Animate to expanded shade after a delay in ms. Used for lockscreen to shade transition. */ fun transitionToExpandedShade(delay: Long) - /** - * Returns whether the unlock hint animation is running. The unlock hint animation is when the - * user taps the lock screen, causing the contents of the lock screen visually bounce. - */ - val isUnlockHintRunning: Boolean - /** @see ViewGroupFadeHelper.reset */ fun resetViewGroupFade() diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt index 189375620285..b8a41019419d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt @@ -73,7 +73,6 @@ class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeViewController { return false } override fun transitionToExpandedShade(delay: Long) {} - override val isUnlockHintRunning: Boolean = false override fun resetViewGroupFade() {} override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {} diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt index 947259a79345..52a99af3eee4 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt @@ -56,6 +56,30 @@ interface ShadeRepository { @Deprecated("Use ShadeInteractor.shadeExpansion instead") val legacyShadeExpansion: StateFlow<Float> + /** + * NotificationPanelViewController.mTracking as a flow. "Tracking" means that the user is moving + * the shade up or down with a pointer. Going forward, this concept will be replaced by checks + * for whether a transition was driven by user input instead of whether a pointer is currently + * touching the screen, i.e. after the user has lifted their finger to fling the shade, these + * values would be different. + */ + @Deprecated("Use ShadeInteractor instead") val legacyShadeTracking: StateFlow<Boolean> + + /** + * QuickSettingsController.mTracking as a flow. "Tracking" means that the user is moving quick + * settings up or down with a pointer. Going forward, this concept will be replaced by checks + * for whether a transition was driven by user input instead of whether a pointer is currently + * touching the screen, i.e. after the user has lifted their finger to fling the QS, these + * values would be different. + */ + @Deprecated("Use ShadeInteractor instead") val legacyQsTracking: StateFlow<Boolean> + + /** Sets whether the user is moving Quick Settings with a pointer */ + fun setLegacyQsTracking(legacyQsTracking: Boolean) + + /** Sets whether the user is moving the shade with a pointer */ + fun setLegacyShadeTracking(tracking: Boolean) + /** Amount shade has expanded with regard to the UDFPS location */ val udfpsTransitionToFullShadeProgress: StateFlow<Float> @@ -123,6 +147,24 @@ constructor(shadeExpansionStateManager: ShadeExpansionStateManager) : ShadeRepos @Deprecated("Use ShadeInteractor.shadeExpansion instead") override val legacyShadeExpansion: StateFlow<Float> = _legacyShadeExpansion.asStateFlow() + private val _legacyShadeTracking = MutableStateFlow(false) + @Deprecated("Use ShadeInteractor instead") + override val legacyShadeTracking: StateFlow<Boolean> = _legacyShadeTracking.asStateFlow() + + private val _legacyQsTracking = MutableStateFlow(false) + @Deprecated("Use ShadeInteractor instead") + override val legacyQsTracking: StateFlow<Boolean> = _legacyQsTracking.asStateFlow() + + @Deprecated("Should only be called by NPVC and tests") + override fun setLegacyQsTracking(legacyQsTracking: Boolean) { + _legacyQsTracking.value = legacyQsTracking + } + + @Deprecated("Should only be called by NPVC and tests") + override fun setLegacyShadeTracking(tracking: Boolean) { + _legacyShadeTracking.value = tracking + } + override fun setQsExpansion(qsExpansion: Float) { _qsExpansion.value = qsExpansion } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index 95a072c0d4c9..b77b9e488d7e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -35,15 +35,20 @@ import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.isActive import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.isActive /** Business logic for shade interactions. */ @OptIn(ExperimentalCoroutinesApi::class) @@ -119,18 +124,41 @@ constructor( repository.qsExpansion } - /** The amount [0-1] either QS or the shade has been opened */ + /** The amount [0-1] either QS or the shade has been opened. */ val anyExpansion: StateFlow<Float> = combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) } .stateIn(scope, SharingStarted.Eagerly, 0f) /** Whether either the shade or QS is expanding from a fully collapsed state. */ - val anyExpanding = + val isAnyExpanding = anyExpansion .pairwise(1f) .map { (prev, curr) -> curr > 0f && curr < 1f && prev < 1f } .distinctUntilChanged() + /** + * Whether the user is expanding or collapsing the shade with user input. This will be true even + * if the user's input gesture has ended but a transition they initiated is animating. + */ + val isUserInteractingWithShade: Flow<Boolean> = + userInteractingFlow(repository.legacyShadeTracking, repository.legacyShadeExpansion) + + /** + * Whether the user is expanding or collapsing quick settings with user input. This will be true + * even if the user's input gesture has ended but a transition they initiated is still + * animating. + */ + val isUserInteractingWithQs: Flow<Boolean> = + userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion) + + /** + * Whether the user is expanding or collapsing either the shade or quick settings with user + * input (i.e. dragging a pointer). This will be true even if the user's input gesture had ended + * but a transition they initiated is still animating. + */ + val isUserInteracting: Flow<Boolean> = + combine(isUserInteractingWithShade, isUserInteractingWithShade) { shade, qs -> shade || qs } + /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */ val isExpandToQsEnabled: Flow<Boolean> = combine( @@ -169,4 +197,30 @@ constructor( } } .distinctUntilChanged() + + /** + * Return a flow for whether a user is interacting with an expandable shade component using + * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that + * [expansion.first] checks the current value of the flow. + */ + private fun userInteractingFlow( + tracking: Flow<Boolean>, + expansion: StateFlow<Float> + ): Flow<Boolean> { + return flow { + // initial value is false + emit(false) + while (currentCoroutineContext().isActive) { + // wait for tracking to become true + tracking.first { it } + emit(true) + // wait for tracking to become false + tracking.first { !it } + // wait for expansion to complete in either direction + expansion.first { it <= 0f || it >= 1f } + // interaction complete + emit(false) + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt index fd57f21b2e1e..4ba56749de91 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt @@ -20,7 +20,7 @@ import android.content.Context import android.content.res.Configuration import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.util.LargeScreenUtils +import com.android.systemui.statusbar.policy.SplitShadeStateController import javax.inject.Inject /** Interpolator responsible for the shade when on large screens. */ @@ -32,6 +32,7 @@ internal constructor( private val context: Context, private val splitShadeInterpolator: SplitShadeInterpolator, private val portraitShadeInterpolator: LargeScreenPortraitShadeInterpolator, + private val splitShadeStateController: SplitShadeStateController ) : LargeScreenShadeInterpolator { private var inSplitShade = false @@ -48,7 +49,7 @@ internal constructor( } private fun updateResources() { - inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources) + inSplitShade = splitShadeStateController.shouldUseSplitNotificationShade(context.resources) } private val impl: LargeScreenShadeInterpolator diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt index ec16109fe1c7..971507055873 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt @@ -18,7 +18,6 @@ package com.android.systemui.shade.transition import android.content.Context import android.content.res.Configuration -import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.qs.QS @@ -31,6 +30,7 @@ import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.SplitShadeStateController import java.io.PrintWriter import javax.inject.Inject @@ -45,6 +45,7 @@ constructor( private val context: Context, private val scrimShadeTransitionController: ScrimShadeTransitionController, private val statusBarStateController: SysuiStatusBarStateController, + private val splitShadeStateController: SplitShadeStateController ) { lateinit var shadeViewController: ShadeViewController @@ -73,7 +74,7 @@ constructor( } private fun updateResources() { - inSplitShade = context.resources.getBoolean(R.bool.config_use_split_notification_shade) + inSplitShade = splitShadeStateController.shouldUseSplitNotificationShade(context.resources) } private fun onPanelStateChanged(@PanelState state: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AbstractLockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/AbstractLockscreenShadeTransitionController.kt index 5b24af038b6b..b6a633fdd1a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AbstractLockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AbstractLockscreenShadeTransitionController.kt @@ -6,14 +6,15 @@ import android.util.IndentingPrintWriter import com.android.systemui.Dumpable import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.util.LargeScreenUtils +import com.android.systemui.statusbar.policy.SplitShadeStateController import java.io.PrintWriter /** An abstract implementation of a class that controls the lockscreen to shade transition. */ abstract class AbstractLockscreenShadeTransitionController( protected val context: Context, configurationController: ConfigurationController, - dumpManager: DumpManager + dumpManager: DumpManager, + private val splitShadeStateController: SplitShadeStateController ) : Dumpable { protected var useSplitShade = false @@ -44,7 +45,8 @@ abstract class AbstractLockscreenShadeTransitionController( } private fun updateResourcesInternal() { - useSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources) + useSplitShade = splitShadeStateController + .shouldUseSplitNotificationShade(context.resources) updateResources() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt index fec61124bc86..238317cfc99e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt @@ -8,6 +8,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.media.controls.ui.MediaHierarchyManager import com.android.systemui.shade.ShadeViewController import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.SplitShadeStateController import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -16,12 +17,14 @@ import dagger.assisted.AssistedInject class LockscreenShadeKeyguardTransitionController @AssistedInject constructor( - private val mediaHierarchyManager: MediaHierarchyManager, - @Assisted private val notificationPanelController: ShadeViewController, - context: Context, - configurationController: ConfigurationController, - dumpManager: DumpManager -) : AbstractLockscreenShadeTransitionController(context, configurationController, dumpManager) { + private val mediaHierarchyManager: MediaHierarchyManager, + @Assisted private val notificationPanelController: ShadeViewController, + context: Context, + configurationController: ConfigurationController, + dumpManager: DumpManager, + splitShadeStateController: SplitShadeStateController +) : AbstractLockscreenShadeTransitionController(context, configurationController, dumpManager, + splitShadeStateController) { /** * Distance that the full shade transition takes in order for the keyguard content on diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionController.kt index df8c6abfff97..5f3d75786973 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionController.kt @@ -25,6 +25,7 @@ import com.android.systemui.R import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.qs.QS import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.SplitShadeStateController import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -38,7 +39,14 @@ constructor( configurationController: ConfigurationController, dumpManager: DumpManager, @Assisted private val qsProvider: () -> QS, -) : AbstractLockscreenShadeTransitionController(context, configurationController, dumpManager) { + splitShadeStateController: SplitShadeStateController +) : + AbstractLockscreenShadeTransitionController( + context, + configurationController, + dumpManager, + splitShadeStateController + ) { private val qs: QS get() = qsProvider() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionController.kt index 00d3701a0cc7..af4a1aa98d9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionController.kt @@ -7,6 +7,7 @@ import com.android.systemui.R import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.SplitShadeStateController import javax.inject.Inject /** Controls the lockscreen to shade transition for scrims. */ @@ -16,8 +17,10 @@ constructor( private val scrimController: ScrimController, context: Context, configurationController: ConfigurationController, - dumpManager: DumpManager -) : AbstractLockscreenShadeTransitionController(context, configurationController, dumpManager) { + dumpManager: DumpManager, + splitShadeStateController: SplitShadeStateController +) : AbstractLockscreenShadeTransitionController(context, configurationController, dumpManager, + splitShadeStateController) { /** * Distance that the full shade transition takes in order for scrim to fully transition to the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 73bbbca09fe8..29ca0f438aac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -42,7 +42,7 @@ import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.LSShadeTransitionLogger import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.util.LargeScreenUtils +import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.wm.shell.animation.Interpolators import java.io.PrintWriter import javax.inject.Inject @@ -79,6 +79,7 @@ class LockscreenShadeTransitionController @Inject constructor( private val shadeRepository: ShadeRepository, private val shadeInteractor: ShadeInteractor, private val powerInteractor: PowerInteractor, + private val splitShadeStateController: SplitShadeStateController ) : Dumpable { private var pulseHeight: Float = 0f @@ -267,7 +268,9 @@ class LockscreenShadeTransitionController @Inject constructor( R.dimen.lockscreen_shade_udfps_keyguard_transition_distance) statusBarTransitionDistance = context.resources.getDimensionPixelSize( R.dimen.lockscreen_shade_status_bar_transition_distance) - useSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources) + + useSplitShade = splitShadeStateController + .shouldUseSplitNotificationShade(context.resources) } fun setStackScroller(nsslController: NotificationStackScrollLayoutController) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 59c63aa28d0f..5c45f3d1bbc8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -46,7 +46,7 @@ import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.LargeScreenUtils +import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.WallpaperController import java.io.PrintWriter import javax.inject.Inject @@ -67,6 +67,7 @@ class NotificationShadeDepthController @Inject constructor( private val notificationShadeWindowController: NotificationShadeWindowController, private val dozeParameters: DozeParameters, private val context: Context, + private val splitShadeStateController: SplitShadeStateController, dumpManager: DumpManager, configurationController: ConfigurationController ) : ShadeExpansionListener, Dumpable { @@ -329,7 +330,7 @@ class NotificationShadeDepthController @Inject constructor( } private fun updateResources() { - inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources) + inSplitShade = splitShadeStateController.shouldUseSplitNotificationShade(context.resources) } fun addListener(listener: DepthListener) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 3f37c60bee8d..c760227aa124 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -776,7 +776,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } } else if (viewEnd >= shelfClipStart - && (!mAmbientState.isUnlockHintRunning() || view.isInShelf()) + && view.isInShelf() && (mAmbientState.isShadeExpanded() || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index e20614178885..70ccc4f3ae43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -31,6 +31,7 @@ import android.view.ViewGroup; import com.android.internal.util.NotificationMessagingUtil; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -38,6 +39,7 @@ import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationClicker; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.icon.IconManager; +import com.android.systemui.statusbar.notification.row.BigPictureIconManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; import com.android.systemui.statusbar.notification.row.NotifBindPipeline; @@ -151,6 +153,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { component.getExpandableNotificationRowController(); rowController.init(entry); entry.setRowController(rowController); + maybeSetBigPictureIconManager(row, component); bindRow(entry, row); updateRow(entry, row); inflateContentViews(entry, params, row, callback); @@ -165,6 +168,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { return; } mLogger.logReleasingViews(entry); + cancelRunningJobs(entry.getRow()); final RowContentBindParams params = mRowContentBindStage.getStageParams(entry); params.markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED); params.markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED); @@ -172,6 +176,23 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { mRowContentBindStage.requestRebind(entry, null); } + private void maybeSetBigPictureIconManager(ExpandableNotificationRow row, + ExpandableNotificationRowComponent component) { + if (mFeatureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) { + row.setBigPictureIconManager(component.getBigPictureIconManager()); + } + } + + private void cancelRunningJobs(ExpandableNotificationRow row) { + if (row == null) { + return; + } + BigPictureIconManager iconManager = row.getBigPictureIconManager(); + if (iconManager != null) { + iconManager.cancelJobs(); + } + } + /** * Bind row to various controllers and managers. This is only called when the row is first * created. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt new file mode 100644 index 000000000000..88dbb4cf1342 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import android.annotation.WorkerThread +import android.app.ActivityManager +import android.content.Context +import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon +import android.util.Dumpable +import android.util.Log +import android.util.Size +import com.android.internal.R +import com.android.internal.widget.NotificationDrawableConsumer +import com.android.internal.widget.NotificationIconManager +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.graphics.ImageLoader +import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.Empty +import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.FullImage +import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.PlaceHolder +import java.io.PrintWriter +import javax.inject.Inject +import kotlin.math.min +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +private const val TAG = "BigPicImageLoader" +private const val DEBUG = false +private const val FREE_IMAGE_DELAY_MS = 3000L + +/** + * A helper class for [com.android.internal.widget.BigPictureNotificationImageView] to lazy-load + * images from SysUI. It replaces the placeholder image with the fully loaded one, and vica versa. + * + * TODO(b/283082473) move the logs to a [com.android.systemui.log.LogBuffer] + */ +@SuppressWarnings("DumpableNotRegistered") +class BigPictureIconManager +@Inject +constructor( + private val context: Context, + private val imageLoader: ImageLoader, + @Application private val scope: CoroutineScope, + @Main private val mainDispatcher: CoroutineDispatcher, + @Background private val bgDispatcher: CoroutineDispatcher +) : NotificationIconManager, Dumpable { + + private var lastLoadingJob: Job? = null + private var drawableConsumer: NotificationDrawableConsumer? = null + private var displayedState: DrawableState = Empty(null) + private var viewShown = false + + private var maxWidth = getMaxWidth() + private var maxHeight = getMaxHeight() + + /** + * Called when the displayed state changes of the view. + * + * @param shown true if the view is shown, and the image needs to be displayed. + */ + fun onViewShown(shown: Boolean) { + log("onViewShown:$shown") + + if (this.viewShown != shown) { + this.viewShown = shown + + val state = displayedState + + this.lastLoadingJob?.cancel() + this.lastLoadingJob = + when { + state is Empty && shown -> state.icon?.let(::startLoadingJob) + state is PlaceHolder && shown -> startLoadingJob(state.icon) + state is FullImage && !shown -> + startFreeImageJob(state.icon, state.drawableSize) + else -> null + } + } + } + + /** + * Update the maximum width and height allowed for bitmaps, ex. after a configuration change. + */ + fun updateMaxImageSizes() { + log("updateMaxImageSizes") + maxWidth = getMaxWidth() + maxHeight = getMaxHeight() + } + + /** Cancels all currently running jobs. */ + fun cancelJobs() { + lastLoadingJob?.cancel() + } + + @WorkerThread + override fun updateIcon(drawableConsumer: NotificationDrawableConsumer, icon: Icon?): Runnable { + if (this.drawableConsumer != null && this.drawableConsumer != drawableConsumer) { + Log.wtf(TAG, "A consumer is already set for this iconManager.") + return Runnable {} + } + + if (displayedState.iconSameAs(icon)) { + // We're already handling this icon, nothing to do here. + log("skipping updateIcon for consumer:$drawableConsumer with icon:$icon") + return Runnable {} + } + + this.drawableConsumer = drawableConsumer + this.displayedState = Empty(icon) + this.lastLoadingJob?.cancel() + + val drawable = loadImageOrPlaceHolderSync(icon) + + log("icon updated") + + return Runnable { drawableConsumer.setImageDrawable(drawable) } + } + + override fun dump(pw: PrintWriter, args: Array<out String>?) { + pw.println("BigPictureIconManager ${getDebugString()}") + } + + @WorkerThread + private fun loadImageOrPlaceHolderSync(icon: Icon?): Drawable? { + icon ?: return null + + if (viewShown) { + return loadImageSync(icon) + } + + return loadPlaceHolderSync(icon) ?: loadImageSync(icon) + } + + @WorkerThread + private fun loadImageSync(icon: Icon): Drawable? { + return imageLoader.loadDrawableSync(icon, context, maxWidth, maxHeight)?.also { drawable -> + checkPlaceHolderSizeForDrawable(this.displayedState, drawable) + this.displayedState = FullImage(icon, drawable.intrinsicSize) + } + } + + private fun checkPlaceHolderSizeForDrawable( + displayedState: DrawableState, + newDrawable: Drawable + ) { + if (displayedState is PlaceHolder) { + val (oldWidth, oldHeight) = displayedState.drawableSize + val (newWidth, newHeight) = newDrawable.intrinsicSize + + if (oldWidth != newWidth || oldHeight != newHeight) { + Log.e( + TAG, + "Mismatch in dimensions, when replacing PlaceHolder " + + "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight." + ) + } + } + } + + @WorkerThread + private fun loadPlaceHolderSync(icon: Icon): Drawable? { + return imageLoader + .loadSizeSync(icon, context) + ?.resizeToMax(maxWidth, maxHeight) // match the dimensions of the fully loaded drawable + ?.let { size -> createPlaceHolder(size) } + ?.also { drawable -> this.displayedState = PlaceHolder(icon, drawable.intrinsicSize) } + } + + private fun startLoadingJob(icon: Icon): Job = + scope.launch { + val drawable = withContext(bgDispatcher) { loadImageSync(icon) } + withContext(mainDispatcher) { drawableConsumer?.setImageDrawable(drawable) } + log("image loaded") + } + + private fun startFreeImageJob(icon: Icon, drawableSize: Size): Job = + scope.launch { + delay(FREE_IMAGE_DELAY_MS) + val drawable = createPlaceHolder(drawableSize) + displayedState = PlaceHolder(icon, drawable.intrinsicSize) + withContext(mainDispatcher) { drawableConsumer?.setImageDrawable(drawable) } + log("placeholder loaded") + } + + private fun createPlaceHolder(size: Size): Drawable { + return PlaceHolderDrawable(width = size.width, height = size.height) + } + + private fun isLowRam(): Boolean { + return ActivityManager.isLowRamDeviceStatic() + } + + private fun getMaxWidth() = + context.resources.getDimensionPixelSize( + if (isLowRam()) { + R.dimen.notification_big_picture_max_width_low_ram + } else { + R.dimen.notification_big_picture_max_width + } + ) + + private fun getMaxHeight() = + context.resources.getDimensionPixelSize( + if (isLowRam()) { + R.dimen.notification_big_picture_max_height_low_ram + } else { + R.dimen.notification_big_picture_max_height + } + ) + + private fun log(msg: String) { + if (DEBUG) { + Log.d(TAG, "$msg state=${getDebugString()}") + } + } + + private fun getDebugString() = + "{ state:$displayedState, hasConsumer:${drawableConsumer != null}, viewShown:$viewShown}" + + private sealed class DrawableState(open val icon: Icon?) { + data class Empty(override val icon: Icon?) : DrawableState(icon) + data class PlaceHolder(override val icon: Icon, val drawableSize: Size) : + DrawableState(icon) + data class FullImage(override val icon: Icon, val drawableSize: Size) : DrawableState(icon) + + fun iconSameAs(other: Icon?): Boolean { + val displayedIcon = icon + return when { + displayedIcon == null && other == null -> true + displayedIcon != null && other != null -> displayedIcon.sameAs(other) + else -> false + } + } + } +} + +/** + * @return an image size that conforms to the maxWidth / maxHeight parameters. It can be the same + * instance, if the provided size was already small enough. + */ +private fun Size.resizeToMax(maxWidth: Int, maxHeight: Int): Size { + if (width <= maxWidth && height <= maxHeight) { + return this + } + + // Calculate the scale factor for both dimensions + val wScale = + if (maxWidth <= 0) { + 1.0f + } else { + maxWidth.toFloat() / width.toFloat() + } + + val hScale = + if (maxHeight <= 0) { + 1.0f + } else { + maxHeight.toFloat() / height.toFloat() + } + + // Scale down to the smaller scale factor + val scale = min(wScale, hScale) + if (scale < 1.0f) { + val targetWidth = (width * scale).toInt() + val targetHeight = (height * scale).toInt() + + return Size(targetWidth, targetHeight) + } + + return this +} + +private val Drawable.intrinsicSize + get() = Size(/*width=*/ intrinsicWidth, /*height=*/ intrinsicHeight) + +private operator fun Size.component1() = width + +private operator fun Size.component2() = height diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureLayoutInflaterFactory.kt new file mode 100644 index 000000000000..e22666574e0d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureLayoutInflaterFactory.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.notification.row + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import com.android.internal.widget.BigPictureNotificationImageView +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag +import javax.inject.Inject + +class BigPictureLayoutInflaterFactory @Inject constructor() : NotifRemoteViewsFactory { + + override fun instantiate( + row: ExpandableNotificationRow, + @InflationFlag layoutType: Int, + parent: View?, + name: String, + context: Context, + attrs: AttributeSet + ): View? { + // Currently the [BigPictureIconManager] only handles one view per notification. + // Exclude other layout types for now, to make sure that we set the same iconManager + // on only one [BigPictureNotificationImageView]. + if (layoutType != FLAG_CONTENT_VIEW_EXPANDED) { + return null + } + + return when (name) { + BigPictureNotificationImageView::class.java.name -> + BigPictureNotificationImageView(context, attrs).also { view -> + view.setIconManager(row.bigPictureIconManager) + } + else -> null + } + } +} 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 c02382dcde94..7fa955bc75eb 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 @@ -376,6 +376,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private float mTranslationWhenRemoved; private boolean mWasChildInGroupWhenRemoved; private NotificationInlineImageResolver mImageResolver; + private BigPictureIconManager mBigPictureIconManager; @Nullable private OnExpansionChangedListener mExpansionChangedListener; @Nullable @@ -1355,6 +1356,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mImageResolver != null) { mImageResolver.updateMaxImageSizes(); } + if (mBigPictureIconManager != null) { + mBigPictureIconManager.updateMaxImageSizes(); + } } public void onUiModeChanged() { @@ -1794,6 +1798,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mImageResolver; } + public BigPictureIconManager getBigPictureIconManager() { + return mBigPictureIconManager; + } + + public void setBigPictureIconManager( + BigPictureIconManager bigPictureIconManager) { + mBigPictureIconManager = bigPictureIconManager; + } + + /** * Resets this view so it can be re-used for an updated notification. */ @@ -3687,6 +3701,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView pw.println("no viewState!!!"); } pw.println(getRoundableState().debugString()); + if (mBigPictureIconManager != null) { + mBigPictureIconManager.dump(pw, args); + } dumpBackgroundView(pw, args); int transientViewCount = mChildrenContainer == null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java index 867e08b2e743..0239afc08ec5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java @@ -60,12 +60,16 @@ public abstract class NotificationRowModule { @Named(NOTIF_REMOTEVIEWS_FACTORIES) static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories( FeatureFlags featureFlags, - PrecomputedTextViewFactory precomputedTextViewFactory + PrecomputedTextViewFactory precomputedTextViewFactory, + BigPictureLayoutInflaterFactory bigPictureLayoutInflaterFactory ) { final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>(); if (featureFlags.isEnabled(Flags.PRECOMPUTED_TEXT)) { replacementFactories.add(precomputedTextViewFactory); } + if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) { + replacementFactories.add(bigPictureLayoutInflaterFactory); + } return replacementFactories; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PlaceHolderDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PlaceHolderDrawable.kt new file mode 100644 index 000000000000..40aa27fc4592 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PlaceHolderDrawable.kt @@ -0,0 +1,33 @@ +package com.android.systemui.statusbar.notification.row + +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.PixelFormat +import android.graphics.drawable.Drawable + +class PlaceHolderDrawable(private val width: Int, private val height: Int) : Drawable() { + + companion object { + fun createFrom(other: Drawable): PlaceHolderDrawable { + return PlaceHolderDrawable(other.intrinsicWidth, other.intrinsicHeight) + } + } + + override fun getIntrinsicWidth(): Int { + return width + } + + override fun getIntrinsicHeight(): Int { + return height + } + + override fun draw(canvas: Canvas) {} + override fun setAlpha(alpha: Int) {} + override fun setColorFilter(colorFilter: ColorFilter?) {} + + @Suppress("DeprecatedCallableAddReplaceWith") + @Deprecated("Deprecated in android.graphics.drawable.Drawable") + override fun getOpacity(): Int { + return PixelFormat.TRANSPARENT + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java index 3588621b12ad..0a6a2c84b0c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java @@ -23,6 +23,7 @@ import android.service.notification.StatusBarNotification; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; +import com.android.systemui.statusbar.notification.row.BigPictureIconManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -68,6 +69,12 @@ public interface ExpandableNotificationRowComponent { ExpandableNotificationRowController getExpandableNotificationRowController(); /** + * Creates a BigPictureIconManager. + */ + @NotificationRowScope + BigPictureIconManager getBigPictureIconManager(); + + /** * Dagger Module that extracts interesting properties from an ExpandableNotificationRow. */ @Module diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java index 175ba15eebae..acd6cc69b553 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java @@ -28,6 +28,7 @@ import android.view.View; import com.android.internal.R; import com.android.internal.widget.BigPictureNotificationImageView; import com.android.systemui.statusbar.notification.ImageTransformState; +import com.android.systemui.statusbar.notification.row.BigPictureIconManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; /** @@ -66,6 +67,17 @@ public class NotificationBigPictureTemplateViewWrapper extends NotificationTempl } } + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + + BigPictureIconManager imageManager = mRow.getBigPictureIconManager(); + if (imageManager != null) { + // TODO(b/283082473) call it a bit earlier for true, as soon as the row starts to expand + imageManager.onViewShown(visible); + } + } + /** * Starts or stops the animations in any drawables contained in this BigPicture Notification. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 95e74f210c5d..38a368e1fdc8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -86,7 +86,6 @@ public class AmbientState implements Dumpable { private boolean mExpansionChanging; private boolean mIsSmallScreen; private boolean mPulsing; - private boolean mUnlockHintRunning; private float mHideAmount; private boolean mAppearing; private float mPulseHeight = MAX_PULSE_HEIGHT; @@ -592,14 +591,6 @@ public class AmbientState implements Dumpable { mIsSmallScreen = smallScreen; } - public void setUnlockHintRunning(boolean unlockHintRunning) { - mUnlockHintRunning = unlockHintRunning; - } - - public boolean isUnlockHintRunning() { - return mUnlockHintRunning; - } - /** * @return Whether we need to do a fling down after swiping up on lockscreen. */ @@ -770,7 +761,6 @@ public class AmbientState implements Dumpable { pw.println("mPulseHeight=" + mPulseHeight); pw.println("mTrackedHeadsUpRow.key=" + logKey(mTrackedHeadsUpRow)); pw.println("mMaxHeadsUpTranslation=" + mMaxHeadsUpTranslation); - pw.println("mUnlockHintRunning=" + mUnlockHintRunning); pw.println("mDozeAmount=" + mDozeAmount); pw.println("mDozing=" + mDozing); pw.println("mFractionToShade=" + mFractionToShade); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 5e3a67ece4a5..e8521d1673cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -118,9 +118,9 @@ import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; +import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.Assert; import com.android.systemui.util.DumpUtilsKt; -import com.android.systemui.util.LargeScreenUtils; import com.google.errorprone.annotations.CompileTimeConstant; @@ -568,6 +568,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private final ScreenOffAnimationController mScreenOffAnimationController; private boolean mShouldUseSplitNotificationShade; private boolean mHasFilteredOutSeenNotifications; + @Nullable private SplitShadeStateController mSplitShadeStateController = null; + + /** Pass splitShadeStateController to view and update split shade */ + public void passSplitShadeStateController(SplitShadeStateController splitShadeStateController) { + mSplitShadeStateController = splitShadeStateController; + updateSplitNotificationShade(); + } private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener = new ExpandableView.OnHeightChangedListener() { @@ -630,7 +637,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mSectionsManager = Dependency.get(NotificationSectionsManager.class); mScreenOffAnimationController = Dependency.get(ScreenOffAnimationController.class); - updateSplitNotificationShade(); mSectionsManager.initialize(this); mSections = mSectionsManager.createSectionsForBuckets(); @@ -1350,8 +1356,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ private boolean shouldSkipHeightUpdate() { return mAmbientState.isOnKeyguard() - && (mAmbientState.isUnlockHintRunning() - || mAmbientState.isSwipingUp() + && (mAmbientState.isSwipingUp() || mAmbientState.isFlingingAfterSwipeUpOnLockscreen()); } @@ -5071,14 +5076,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAmbientState.setSmallScreen(isFullWidth); } - public void setUnlockHintRunning(boolean running) { - mAmbientState.setUnlockHintRunning(running); - if (!running) { - // re-calculate the stack height which was frozen while running this animation - updateStackPosition(); - } - } - public void setPanelFlinging(boolean flinging) { mAmbientState.setFlinging(flinging); if (!flinging) { @@ -5675,7 +5672,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @VisibleForTesting void updateSplitNotificationShade() { - boolean split = LargeScreenUtils.shouldUseSplitNotificationShade(getResources()); + boolean split = mSplitShadeStateController.shouldUseSplitNotificationShade(getResources()); if (split != mShouldUseSplitNotificationShade) { mShouldUseSplitNotificationShade = split; updateDismissBehavior(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 93b5ff7268e3..b051809f2c5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -128,6 +128,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController.Configurati import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; +import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.Compile; @@ -672,7 +673,8 @@ public class NotificationStackScrollLayoutController { NotificationTargetsHelper notificationTargetsHelper, SecureSettings secureSettings, NotificationDismissibilityProvider dismissibilityProvider, - ActivityStarter activityStarter) { + ActivityStarter activityStarter, + SplitShadeStateController splitShadeStateController) { mView = view; mKeyguardTransitionRepo = keyguardTransitionRepo; mStackStateLogger = stackLogger; @@ -722,6 +724,7 @@ public class NotificationStackScrollLayoutController { mSecureSettings = secureSettings; mDismissibilityProvider = dismissibilityProvider; mActivityStarter = activityStarter; + mView.passSplitShadeStateController(splitShadeStateController); updateResources(); setUpView(); } @@ -1200,10 +1203,6 @@ public class NotificationStackScrollLayoutController { mView.setHeadsUpBoundaries(height, bottomBarHeight); } - public void setUnlockHintRunning(boolean running) { - mView.setUnlockHintRunning(running); - } - public void setPanelFlinging(boolean flinging) { mView.setPanelFlinging(flinging); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index c7cb70c1da05..24104d2f37e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -29,8 +29,8 @@ import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView +import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.Compile -import com.android.systemui.util.LargeScreenUtils.shouldUseSplitNotificationShade import com.android.systemui.util.children import java.io.PrintWriter import javax.inject.Inject @@ -54,7 +54,8 @@ constructor( private val statusBarStateController: SysuiStatusBarStateController, private val lockscreenShadeTransitionController: LockscreenShadeTransitionController, private val mediaDataManager: MediaDataManager, - @Main private val resources: Resources + @Main private val resources: Resources, + private val splitShadeStateController: SplitShadeStateController ) { /** @@ -181,7 +182,8 @@ constructor( // How many notifications we can show at heightWithoutLockscreenConstraints var minCountAtHeightWithoutConstraints = - if (isMediaShowing && !shouldUseSplitNotificationShade(resources)) 2 else 1 + if (isMediaShowing && !splitShadeStateController + .shouldUseSplitNotificationShade(resources)) 2 else 1 log { "\t---maxNotifWithoutSavingSpace=$maxNotifWithoutSavingSpace " + "isMediaShowing=$isMediaShowing" + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt index 4ed31c2cec89..51b6c75f44b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt @@ -21,6 +21,7 @@ import android.content.Context import com.android.systemui.R import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.policy.SplitShadeStateController import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -36,6 +37,7 @@ class SharedNotificationContainerInteractor constructor( configurationRepository: ConfigurationRepository, private val context: Context, + private val splitShadeStateController: SplitShadeStateController ) { private val _topPosition = MutableStateFlow(0f) @@ -47,7 +49,10 @@ constructor( .map { _ -> with(context.resources) { ConfigurationBasedDimensions( - useSplitShade = getBoolean(R.bool.config_use_split_notification_shade), + useSplitShade = + splitShadeStateController.shouldUseSplitNotificationShade( + context.resources + ), useLargeScreenHeader = getBoolean(R.bool.config_use_large_screen_shade_header), marginHorizontal = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index cfa481eaf7ef..7d575685dcae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -269,8 +269,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { boolean isScreenFullyOff(); - void showScreenPinningRequest(int taskId, boolean allowCancel); - @Nullable Intent getEmergencyActionIntent(); @@ -301,8 +299,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { boolean isBouncerShowingScrimmed(); - boolean isBouncerShowingOverDream(); - void updateNotificationPanelTouchState(); int getRotation(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index 28bb58108916..ebcfb8adb08d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -59,6 +59,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QSPanelController; +import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.QuickSettingsController; @@ -84,6 +85,7 @@ import javax.inject.Inject; public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callbacks { private final CentralSurfaces mCentralSurfaces; private final Context mContext; + private final ScreenPinningRequest mScreenPinningRequest; private final com.android.systemui.shade.ShadeController mShadeController; private final CommandQueue mCommandQueue; private final ShadeViewController mShadeViewController; @@ -126,6 +128,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba QuickSettingsController quickSettingsController, Context context, @Main Resources resources, + ScreenPinningRequest screenPinningRequest, ShadeController shadeController, CommandQueue commandQueue, ShadeViewController shadeViewController, @@ -155,6 +158,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mCentralSurfaces = centralSurfaces; mQsController = quickSettingsController; mContext = context; + mScreenPinningRequest = screenPinningRequest; mShadeController = shadeController; mCommandQueue = commandQueue; mShadeViewController = shadeViewController; @@ -516,7 +520,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba return; } // Show screen pinning request, since this comes from an app, show 'no thanks', button. - mCentralSurfaces.showScreenPinningRequest(taskId, true); + mScreenPinningRequest.showPrompt(taskId, true); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt index ff380db99c16..5e505f733c93 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt @@ -71,7 +71,6 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces { override fun isOverviewEnabled() = false override fun setBouncerShowing(bouncerShowing: Boolean) {} override fun isScreenFullyOff() = false - override fun showScreenPinningRequest(taskId: Int, allowCancel: Boolean) {} override fun getEmergencyActionIntent(): Intent? = null override fun isCameraAllowedByAdmin() = false override fun isGoingToSleep() = false @@ -84,7 +83,6 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces { override fun awakenDreams() {} override fun isBouncerShowing() = false override fun isBouncerShowingScrimmed() = false - override fun isBouncerShowingOverDream() = false override fun updateNotificationPanelTouchState() {} override fun getRotation() = 0 override fun setBarStateForTest(state: Int) {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 490c469261d9..8d35d39bceea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -168,7 +168,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragment; import com.android.systemui.qs.QSPanelController; -import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.UserTracker; @@ -388,7 +387,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { */ protected int mState; // TODO: remove this. Just use StatusBarStateController protected boolean mBouncerShowing; - private boolean mBouncerShowingOverDream; private final PhoneStatusBarPolicy mIconPolicy; @@ -507,8 +505,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { ? new GestureRecorder("/sdcard/statusbar_gestures.dat") : null; - private final ScreenPinningRequest mScreenPinningRequest; - private final MetricsLogger mMetricsLogger; // ensure quick settings is disabled until the current user makes it through the setup wizard @@ -692,7 +688,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { DozeServiceHost dozeServiceHost, BackActionInteractor backActionInteractor, PowerManager powerManager, - ScreenPinningRequest screenPinningRequest, DozeScrimController dozeScrimController, VolumeComponent volumeComponent, CommandQueue commandQueue, @@ -799,7 +794,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mDozeParameters = dozeParameters; mScrimController = scrimController; mLockscreenWallpaperLazy = lockscreenWallpaperLazy; - mScreenPinningRequest = screenPinningRequest; mDozeScrimController = dozeScrimController; mBiometricUnlockControllerLazy = biometricUnlockControllerLazy; mAuthRippleController = authRippleController; @@ -2460,10 +2454,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { */ @Override public boolean shouldKeyguardHideImmediately() { - final boolean isScrimmedBouncer = - mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED; - final boolean isBouncerOverDream = isBouncerShowingOverDream(); - return (isScrimmedBouncer || isBouncerOverDream); + return mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED; } private void showBouncerOrLockScreenIfKeyguard() { @@ -2815,11 +2806,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_OFF; } - @Override - public void showScreenPinningRequest(int taskId, boolean allowCancel) { - mScreenPinningRequest.showPrompt(taskId, allowCancel); - } - @Nullable @Override public Intent getEmergencyActionIntent() { @@ -3160,11 +3146,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return isBouncerShowing() && mStatusBarKeyguardViewManager.primaryBouncerNeedsScrimming(); } - @Override - public boolean isBouncerShowingOverDream() { - return mBouncerShowingOverDream; - } - // End Extra BaseStatusBarMethods. boolean isTransientShown() { @@ -3251,8 +3232,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (DEBUG) { Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration()); } - - mScreenPinningRequest.onConfigurationChanged(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt index cdd410e766a2..47ab316bb239 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt @@ -20,7 +20,6 @@ import android.content.res.Configuration import android.util.AttributeSet import android.view.View import android.view.ViewGroup -import android.view.ViewPropertyAnimator import android.view.WindowInsets import android.widget.FrameLayout import androidx.annotation.StringRes @@ -32,7 +31,6 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.VibratorHelper -import com.android.systemui.util.animation.requiresRemeasuring /** * Renders the bottom area of the lock-screen. Concerned primarily with the quick affordance UI @@ -61,6 +59,7 @@ constructor( } private var ambientIndicationArea: View? = null + private var keyguardIndicationArea: View? = null private var binding: KeyguardBottomAreaViewBinder.Binding? = null private var lockIconViewController: LockIconViewController? = null @@ -124,13 +123,14 @@ constructor( override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) binding?.onConfigurationChanged() - } - - /** Returns a list of animators to use to animate the indication areas. */ - @Deprecated("Deprecated as part of b/278057014") - val indicationAreaAnimators: List<ViewPropertyAnimator> - get() = checkNotNull(binding).getIndicationAreaAnimators() + keyguardIndicationArea?.let { + val params = it.layoutParams as FrameLayout.LayoutParams + params.bottomMargin = + resources.getDimensionPixelSize(R.dimen.keyguard_indication_margin_bottom) + it.layoutParams = params + } + } override fun hasOverlappingRendering(): Boolean { return false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index be336e59f534..16413d2ef33a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -45,6 +45,8 @@ import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.log.core.LogLevel; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeViewStateProvider; @@ -69,6 +71,8 @@ import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel; import com.android.systemui.util.ViewController; import com.android.systemui.util.settings.SecureSettings; +import kotlin.Unit; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -76,8 +80,6 @@ import java.util.concurrent.Executor; import javax.inject.Inject; -import kotlin.Unit; - /** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */ public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> { private static final String TAG = "KeyguardStatusBarViewController"; @@ -111,6 +113,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private final BiometricUnlockController mBiometricUnlockController; private final SysuiStatusBarStateController mStatusBarStateController; private final StatusBarContentInsetsProvider mInsetsProvider; + private final FeatureFlags mFeatureFlags; private final UserManager mUserManager; private final StatusBarUserChipViewModel mStatusBarUserChipViewModel; private final SecureSettings mSecureSettings; @@ -283,6 +286,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat BiometricUnlockController biometricUnlockController, SysuiStatusBarStateController statusBarStateController, StatusBarContentInsetsProvider statusBarContentInsetsProvider, + FeatureFlags featureFlags, UserManager userManager, StatusBarUserChipViewModel userChipViewModel, SecureSettings secureSettings, @@ -308,6 +312,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mBiometricUnlockController = biometricUnlockController; mStatusBarStateController = statusBarStateController; mInsetsProvider = statusBarContentInsetsProvider; + mFeatureFlags = featureFlags; mUserManager = userManager; mStatusBarUserChipViewModel = userChipViewModel; mSecureSettings = secureSettings; @@ -367,7 +372,19 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mView.findViewById(R.id.statusIcons), StatusBarLocation.KEYGUARD); mTintedIconManager.setBlockList(getBlockedIcons()); mStatusBarIconController.addIconGroup(mTintedIconManager); + } else { + // In the old implementation, the keyguard status bar view is never detached and + // re-attached, so only calling #addIconGroup when the IconManager is first created was + // safe and correct. + // In the new scene framework implementation, the keyguard status bar view *is* detached + // whenever the shade is opened on top of lockscreen, and then re-attached when the + // shade is closed. So, we need to re-add the IconManager each time we're re-attached to + // get icon updates. + if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW)) { + mStatusBarIconController.addIconGroup(mTintedIconManager); + } } + mSystemIconsContainer = mView.findViewById(R.id.system_icons); StatusOverlayHoverListener hoverListener = mStatusOverlayHoverListenerFactory .createDarkAwareListener(mSystemIconsContainer, mView.darkChangeFlow()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 3afbbfddeb8a..8d86d729d958 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -22,7 +22,6 @@ import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; -import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows; import android.content.Context; import android.content.res.ColorStateList; @@ -472,17 +471,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) { + // Show the keyguard views whenever we've told WM that the lockscreen is visible. mShadeViewController.postToView(() -> collectFlow( getViewRootImpl().getView(), - combineFlows( - mKeyguardTransitionInteractor.getFinishedKeyguardState(), - mWmLockscreenVisibilityInteractor.get() - .getUsingKeyguardGoingAwayAnimation(), - (finishedState, animating) -> - KeyguardInteractor.Companion.isKeyguardVisibleInState( - finishedState) - || animating), + mWmLockscreenVisibilityInteractor.get().getLockscreenVisibility(), this::consumeShowStatusBarKeyguardView)); } } @@ -565,7 +558,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb && !mKeyguardStateController.isOccluded() && !mKeyguardStateController.canDismissLockScreen() && !bouncerIsAnimatingAway() - && !mShadeViewController.isUnlockHintRunning() && !(mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED); } @@ -636,8 +628,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) { if (needsFullscreenBouncer() && !mDozing) { // The keyguard might be showing (already). So we need to hide it. - mCentralSurfaces.hideKeyguard(); - mPrimaryBouncerInteractor.show(true); + if (!primaryBouncerIsShowing()) { + mCentralSurfaces.hideKeyguard(); + mPrimaryBouncerInteractor.show(true); + } else { + Log.e(TAG, "Attempted to show the sim bouncer when it is already showing."); + } } else { mCentralSurfaces.showKeyguard(); if (hideBouncerWhenShowing) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt index 249ca35b610a..7ec8e12e557e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.binder import android.content.res.ColorStateList import android.view.View import android.view.View.GONE -import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.ImageView @@ -34,12 +33,11 @@ import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.StatusBarIconView -import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN -import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding +import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged @@ -106,20 +104,11 @@ object MobileIconBinder { launch { visibilityState.collect { state -> - when (state) { - STATE_ICON -> { - mobileGroupView.visibility = VISIBLE - dotView.visibility = GONE - } - STATE_DOT -> { - mobileGroupView.visibility = INVISIBLE - dotView.visibility = VISIBLE - } - STATE_HIDDEN -> { - mobileGroupView.visibility = INVISIBLE - dotView.visibility = INVISIBLE - } - } + ModernStatusBarViewVisibilityHelper.setVisibilityState( + state, + mobileGroupView, + dotView, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewVisibilityHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewVisibilityHelper.kt new file mode 100644 index 000000000000..6668cbc290fc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewVisibilityHelper.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.shared.ui.binder + +import android.view.View +import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder +import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder + +/** + * The helper to update the groupView and dotView visibility based on given visibility state, only + * used for [MobileIconBinder] and [WifiViewBinder] now. + */ +class ModernStatusBarViewVisibilityHelper { + companion object { + + fun setVisibilityState( + @StatusBarIconView.VisibleState state: Int, + groupView: View, + dotView: View, + ) { + when (state) { + StatusBarIconView.STATE_ICON -> { + groupView.visibility = View.VISIBLE + dotView.visibility = View.GONE + } + StatusBarIconView.STATE_DOT -> { + groupView.visibility = View.INVISIBLE + dotView.visibility = View.VISIBLE + } + StatusBarIconView.STATE_HIDDEN -> { + groupView.visibility = View.INVISIBLE + dotView.visibility = View.INVISIBLE + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt index 3082a6629dc2..e593575bd791 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt @@ -27,10 +27,9 @@ import com.android.systemui.R import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.StatusBarIconView -import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN -import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding +import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel import kotlinx.coroutines.InternalCoroutinesApi @@ -83,8 +82,18 @@ object WifiViewBinder { launch { visibilityState.collect { visibilityState -> - groupView.isVisible = visibilityState == STATE_ICON - dotView.isVisible = visibilityState == STATE_DOT + // for b/296864006, we can not hide all the child views if visibilityState + // is STATE_HIDDEN. Because hiding all child views would cause the + // getWidth() of this view return 0, and that would cause the translation + // calculation fails in StatusIconContainer. Therefore, like class + // MobileIconBinder, instead of set the child views visibility to View.GONE, + // we set their visibility to View.INVISIBLE to make them invisible but + // keep the width. + ModernStatusBarViewVisibilityHelper.setVisibilityState( + visibilityState, + groupView, + dotView, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AospPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AospPolicyModule.java index ba5fa1df4b15..3d51ab0a2b27 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AospPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AospPolicyModule.java @@ -45,6 +45,7 @@ public class AospPolicyModule { BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController, DumpManager dumpManager, + BatteryControllerLogger logger, @Main Handler mainHandler, @Background Handler bgHandler) { BatteryController bC = new BatteryControllerImpl( @@ -54,6 +55,7 @@ public class AospPolicyModule { broadcastDispatcher, demoModeController, dumpManager, + logger, mainHandler, bgHandler); bC.init(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index 4b515115dd77..41ed76d7edb1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -20,7 +20,6 @@ import static android.os.BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE; import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT; import static android.os.BatteryManager.EXTRA_CHARGING_STATUS; import static android.os.BatteryManager.EXTRA_PRESENT; - import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS; import static com.android.systemui.util.DumpUtilsKt.asIndenting; @@ -85,6 +84,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC private final PowerManager mPowerManager; private final DemoModeController mDemoModeController; private final DumpManager mDumpManager; + private final BatteryControllerLogger mLogger; private final Handler mMainHandler; private final Handler mBgHandler; protected final Context mContext; @@ -122,6 +122,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController, DumpManager dumpManager, + BatteryControllerLogger logger, @Main Handler mainHandler, @Background Handler bgHandler) { mContext = context; @@ -132,6 +133,8 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC mBroadcastDispatcher = broadcastDispatcher; mDemoModeController = demoModeController; mDumpManager = dumpManager; + mLogger = logger; + mLogger.logBatteryControllerInstance(this); } private void registerReceiver() { @@ -145,6 +148,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC @Override public void init() { + mLogger.logBatteryControllerInit(this, mHasReceivedBattery); registerReceiver(); if (!mHasReceivedBattery) { // Get initial state. Relying on Sticky behavior until API for getting info. @@ -232,8 +236,13 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC @Override public void onReceive(final Context context, Intent intent) { final String action = intent.getAction(); + mLogger.logIntentReceived(action); if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { - if (mTestMode && !intent.getBooleanExtra("testmode", false)) return; + mLogger.logBatteryChangedIntent(intent); + if (mTestMode && !intent.getBooleanExtra("testmode", false)) { + mLogger.logBatteryChangedSkipBecauseTest(); + return; + } mHasReceivedBattery = true; mLevel = (int) (100f * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) @@ -275,6 +284,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC fireIsIncompatibleChargingChanged(); } } else if (action.equals(ACTION_LEVEL_TEST)) { + mLogger.logEnterTestMode(); mTestMode = true; mMainHandler.post(new Runnable() { int mCurrentLevel = 0; @@ -286,6 +296,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC @Override public void run() { if (mCurrentLevel < 0) { + mLogger.logExitTestMode(); mTestMode = false; mTestIntent.putExtra("level", mSavedLevel); mTestIntent.putExtra("plugged", mSavedPluggedIn); @@ -438,6 +449,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC } protected void fireBatteryLevelChanged() { + mLogger.logBatteryLevelChangedCallback(mLevel, mPluggedIn, mCharging); synchronized (mChangeCallbacks) { final int N = mChangeCallbacks.size(); for (int i = 0; i < N; i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerLogger.kt new file mode 100644 index 000000000000..4a2a2dbad638 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerLogger.kt @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy + +import android.content.Intent +import android.os.BatteryManager.EXTRA_LEVEL +import android.os.BatteryManager.EXTRA_SCALE +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.statusbar.policy.dagger.BatteryControllerLog +import javax.inject.Inject + +/** Detailed, [LogBuffer]-backed logs for [BatteryControllerImpl] */ +@SysUISingleton +class BatteryControllerLogger +@Inject +constructor(@BatteryControllerLog private val logBuffer: LogBuffer) { + fun logBatteryControllerInstance(controller: BatteryController) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { int1 = System.identityHashCode(controller) }, + { "BatteryController CREATE (${Integer.toHexString(int1)})" } + ) + } + + fun logBatteryControllerInit(controller: BatteryController, hasReceivedBattery: Boolean) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + int1 = System.identityHashCode(controller) + bool1 = hasReceivedBattery + }, + { "BatteryController INIT (${Integer.toHexString(int1)}) hasReceivedBattery=$bool1" } + ) + } + + fun logIntentReceived(action: String) { + logBuffer.log(TAG, LogLevel.DEBUG, { str1 = action }, { "Received intent $str1" }) + } + + fun logBatteryChangedIntent(intent: Intent) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + int1 = intent.getIntExtra(EXTRA_LEVEL, DEFAULT) + int2 = intent.getIntExtra(EXTRA_SCALE, DEFAULT) + }, + { "Processing BATTERY_CHANGED intent. level=${int1.report()} scale=${int2.report()}" } + ) + } + + fun logBatteryChangedSkipBecauseTest() { + logBuffer.log( + TAG, + LogLevel.DEBUG, + {}, + { "Detected test intent. Will not execute battery level callbacks." } + ) + } + + fun logEnterTestMode() { + logBuffer.log( + TAG, + LogLevel.DEBUG, + {}, + { "Entering test mode for BATTERY_LEVEL_TEST intent" } + ) + } + + fun logExitTestMode() { + logBuffer.log(TAG, LogLevel.DEBUG, {}, { "Exiting test mode" }) + } + + fun logBatteryLevelChangedCallback(level: Int, plugged: Boolean, charging: Boolean) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + int1 = level + bool1 = plugged + bool2 = charging + }, + { + "Sending onBatteryLevelChanged callbacks " + + "with level=$int1, plugged=$bool1, charging=$bool2" + } + ) + } + + private fun Int.report(): String = + if (this == DEFAULT) { + "(missing)" + } else { + toString() + } + + companion object { + const val TAG: String = "BatteryControllerLog" + } +} + +// Use a token value so we can determine if we got the default +private const val DEFAULT = -11 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt index d5f2d210b9b0..67a8e3d0e345 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt @@ -20,7 +20,6 @@ import android.content.Context import android.content.res.Configuration import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.util.LargeScreenUtils import javax.inject.Inject /** @@ -30,9 +29,10 @@ import javax.inject.Inject */ @SysUISingleton class RemoteInputQuickSettingsDisabler @Inject constructor( - private val context: Context, - private val commandQueue: CommandQueue, - configController: ConfigurationController + private val context: Context, + private val commandQueue: CommandQueue, + private val splitShadeStateController: SplitShadeStateController, + configController: ConfigurationController ) : ConfigurationController.ConfigurationListener { private var remoteInputActive = false @@ -43,7 +43,7 @@ class RemoteInputQuickSettingsDisabler @Inject constructor( isLandscape = context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE shouldUseSplitNotificationShade = - LargeScreenUtils.shouldUseSplitNotificationShade(context.resources) + splitShadeStateController.shouldUseSplitNotificationShade(context.resources) configController.addCallback(this) } @@ -74,7 +74,8 @@ class RemoteInputQuickSettingsDisabler @Inject constructor( needToRecompute = true } - val newSplitShadeFlag = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources) + val newSplitShadeFlag = splitShadeStateController + .shouldUseSplitNotificationShade(context.resources) if (newSplitShadeFlag != shouldUseSplitNotificationShade) { shouldUseSplitNotificationShade = newSplitShadeFlag needToRecompute = true diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ResourcesSplitShadeStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ResourcesSplitShadeStateController.kt new file mode 100644 index 000000000000..e71c972266f2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ResourcesSplitShadeStateController.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.policy + +import android.content.res.Resources +import com.android.systemui.R + +/** + * Fake SplitShadeStateController + * + * Identical behaviour to legacy implementation (that used LargeScreenUtils.kt) I.E., behaviour + * based solely on resources, no extra flag logic. + */ +class ResourcesSplitShadeStateController : SplitShadeStateController { + override fun shouldUseSplitNotificationShade(resources: Resources): Boolean { + return resources.getBoolean(R.bool.config_use_split_notification_shade) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt new file mode 100644 index 000000000000..f64d4c67ea23 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateController.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.policy + +import android.content.res.Resources + +/** Source of truth for split shade state: should or should not use split shade. */ +interface SplitShadeStateController { + /** Returns true if the device should use the split notification shade. */ + fun shouldUseSplitNotificationShade(resources: Resources): Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerImpl.kt new file mode 100644 index 000000000000..ab4a8afcc3c9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitShadeStateControllerImpl.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.policy + +import android.content.res.Resources +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import javax.inject.Inject + +/** + * Source of truth for split shade state: should or should not use split shade based on orientation, + * screen width, and flags. + */ +@SysUISingleton +class SplitShadeStateControllerImpl @Inject constructor(private val featureFlags: FeatureFlags) : + SplitShadeStateController { + /** + * Returns true if the device should use the split notification shade. Based on orientation, + * screen width, and flags. + */ + override fun shouldUseSplitNotificationShade(resources: Resources): Boolean { + return (resources.getBoolean(R.bool.config_use_split_notification_shade) || + (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE) && + resources.getBoolean(R.bool.force_config_use_split_notification_shade))) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index dabdcc5fc0f7..39cdfa382bff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -169,6 +169,7 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { @Override public void addCallback(@NonNull Callback callback) { synchronized (mCallbacksLock) { + Log.d(TAG, "Added callback " + callback.getClass()); mCallbacks.add(callback); } } @@ -176,6 +177,7 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { @Override public void removeCallback(@NonNull Callback callback) { synchronized (mCallbacksLock) { + Log.d(TAG, "Removed callback " + callback.getClass()); mCallbacks.remove(callback); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/BatteryControllerLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/BatteryControllerLog.kt new file mode 100644 index 000000000000..5322b3811f95 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/BatteryControllerLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.dagger + +import javax.inject.Qualifier + +/** Logs for Battery events. See [BatteryControllerImpl] */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class BatteryControllerLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index c2a8e701653a..927024fbdd34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -24,6 +24,8 @@ import com.android.internal.R; import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.LogBufferFactory; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.connectivity.AccessPointControllerImpl; @@ -31,6 +33,7 @@ import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.connectivity.NetworkControllerImpl; import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory; import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; +import com.android.systemui.statusbar.policy.BatteryControllerLogger; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.BluetoothControllerImpl; import com.android.systemui.statusbar.policy.CastController; @@ -57,6 +60,8 @@ import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.RotationLockControllerImpl; import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.statusbar.policy.SecurityControllerImpl; +import com.android.systemui.statusbar.policy.SplitShadeStateController; +import com.android.systemui.statusbar.policy.SplitShadeStateControllerImpl; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.WalletController; @@ -110,6 +115,11 @@ public interface StatusBarPolicyModule { /** */ @Binds + SplitShadeStateController provideSplitShadeStateController( + SplitShadeStateControllerImpl splitShadeStateControllerImpl); + + /** */ + @Binds HotspotController provideHotspotController(HotspotControllerImpl controllerImpl); /** */ @@ -202,4 +212,13 @@ public interface StatusBarPolicyModule { static DataSaverController provideDataSaverController(NetworkController networkController) { return networkController.getDataSaverController(); } + + /** Provides a log bufffer for BatteryControllerImpl */ + @Provides + @SysUISingleton + @BatteryControllerLog + //TODO(b/300147438): reduce the size of this log buffer + static LogBuffer provideBatteryControllerLog(LogBufferFactory factory) { + return factory.create(BatteryControllerLogger.TAG, 300); + } } diff --git a/packages/SystemUI/src/com/android/systemui/util/LargeScreenUtils.kt b/packages/SystemUI/src/com/android/systemui/util/LargeScreenUtils.kt index 8b29310cf1c5..9b241a72b708 100644 --- a/packages/SystemUI/src/com/android/systemui/util/LargeScreenUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/util/LargeScreenUtils.kt @@ -4,16 +4,6 @@ import android.content.res.Resources import com.android.systemui.R object LargeScreenUtils { - - /** - * Returns true if the device should use the split notification shade, based on orientation and - * screen width. - */ - @JvmStatic - fun shouldUseSplitNotificationShade(resources: Resources): Boolean { - return resources.getBoolean(R.bool.config_use_split_notification_shade) - } - /** * Returns true if we should use large screen shade header: * [com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController] diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index e1b608ffb1d6..ee67348c9b93 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.mockito.whenever import org.junit.Before @@ -67,6 +68,7 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { @Mock private lateinit var mKeyguardMessageAreaController: KeyguardMessageAreaController<BouncerKeyguardMessageArea> + @Mock private lateinit var postureController: DevicePostureController private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController @@ -89,6 +91,7 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { `when`(keyguardPasswordView.resources).thenReturn(context.resources) val fakeFeatureFlags = FakeFeatureFlags() fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) + fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) keyguardPasswordViewController = KeyguardPasswordViewController( keyguardPasswordView, @@ -104,6 +107,7 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { mContext.resources, falsingCollector, keyguardViewController, + postureController, fakeFeatureFlags ) } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index 93048a5787b2..0ef9f4533015 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -94,9 +94,10 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { .thenReturn(mKeyguardMessageAreaController) fakeFeatureFlags = FakeFeatureFlags() fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false) + fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) mKeyguardPatternView = View.inflate(mContext, R.layout.keyguard_pattern_view, null) as KeyguardPatternView - + mKeyguardPatternView.setIsLockScreenLandscapeEnabled(false) mKeyguardPatternViewController = KeyguardPatternViewController( mKeyguardPatternView, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 33d40976a2e4..a9f044ccd144 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -123,7 +123,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { private fun constructPinViewController( mKeyguardPinView: KeyguardPINView ): KeyguardPinViewController { - mKeyguardPinView.setIsLockScreenLandscapeEnabled(false) return KeyguardPinViewController( mKeyguardPinView, keyguardUpdateMonitor, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 7c1861e42d6d..decc457dc452 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -60,6 +60,7 @@ import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.UserSwitcherController @@ -140,6 +141,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { @Mock private lateinit var userInteractor: UserInteractor @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController + @Mock private lateinit var postureController: DevicePostureController @Captor private lateinit var swipeListenerArgumentCaptor: @@ -197,6 +199,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false) featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) + featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) keyguardPasswordViewController = KeyguardPasswordViewController( @@ -213,6 +216,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { mock(), null, keyguardViewController, + postureController, featureFlags ) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index 09ff546120c6..0e4b3c9b6a23 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -19,6 +19,7 @@ package com.android.keyguard; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1; import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR; +import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; import static com.android.systemui.flags.Flags.MIGRATE_LOCK_ICON; @@ -146,6 +147,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mFeatureFlags.set(FACE_AUTH_REFACTOR, false); mFeatureFlags.set(MIGRATE_LOCK_ICON, false); mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false); + mFeatureFlags.set(LOCKSCREEN_ENABLE_LANDSCAPE, false); mUnderTest = new LockIconViewController( mStatusBarStateController, mKeyguardUpdateMonitor, 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 7775a05568e8..969a01164734 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -41,7 +41,7 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.FakePromptRepository -import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository +import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor @@ -109,7 +109,7 @@ open class AuthContainerViewTest : SysuiTestCase() { private val fakeExecutor = FakeExecutor(FakeSystemClock()) private val biometricPromptRepository = FakePromptRepository() private val fingerprintRepository = FakeFingerprintPropertyRepository() - private val rearDisplayStateRepository = FakeRearDisplayStateRepository() + private val displayStateRepository = FakeDisplayStateRepository() private val credentialInteractor = FakeCredentialInteractor() private val bpCredentialInteractor = PromptCredentialInteractor( Dispatchers.Main.immediate, @@ -141,7 +141,7 @@ open class AuthContainerViewTest : SysuiTestCase() { testScope.backgroundScope, mContext, fakeExecutor, - rearDisplayStateRepository, + displayStateRepository, displayRepository, ) } @@ -520,6 +520,7 @@ open class AuthContainerViewTest : SysuiTestCase() { displayStateInteractor, promptSelectorInteractor, vibrator, + context, featureFlags ), { credentialViewModel }, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt index 2bb3785f0eef..8fc63b246c9d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFl import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor import com.android.systemui.user.data.repository.FakeUserRepository @@ -72,6 +73,7 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { SharedNotificationContainerInteractor( configurationRepository, mContext, + ResourcesSplitShadeStateController() ) private lateinit var detector: AuthDialogPanelInteractionDetector diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt index 3ebc2d6174ad..17928a3da140 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt @@ -56,7 +56,7 @@ import com.android.systemui.R import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestableContext -import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository +import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository @@ -117,7 +117,7 @@ class SideFpsControllerTest : SysuiTestCase() { @Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams> private lateinit var displayRepository: FakeDisplayRepository - private lateinit var rearDisplayStateRepository: FakeRearDisplayStateRepository + private lateinit var displayStateRepository: FakeDisplayStateRepository private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor private lateinit var displayStateInteractor: DisplayStateInteractor @@ -145,7 +145,7 @@ class SideFpsControllerTest : SysuiTestCase() { @Before fun setup() { displayRepository = FakeDisplayRepository() - rearDisplayStateRepository = FakeRearDisplayStateRepository() + displayStateRepository = FakeDisplayStateRepository() keyguardBouncerRepository = FakeKeyguardBouncerRepository() alternateBouncerInteractor = AlternateBouncerInteractor( @@ -161,7 +161,7 @@ class SideFpsControllerTest : SysuiTestCase() { testScope.backgroundScope, context, executor, - rearDisplayStateRepository, + displayStateRepository, displayRepository, ) @@ -273,7 +273,7 @@ class SideFpsControllerTest : SysuiTestCase() { TestCoroutineScope(), dumpManager ) - rearDisplayStateRepository.setIsInRearDisplayMode(inRearDisplayMode) + displayStateRepository.setIsInRearDisplayMode(inRearDisplayMode) overlayController = ArgumentCaptor.forClass(ISidefpsController::class.java) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt index dfe8d36504d0..c9c46cbe8420 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/RearDisplayStateRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt @@ -17,13 +17,21 @@ package com.android.systemui.keyguard.data.repository import android.hardware.devicestate.DeviceStateManager +import android.hardware.display.DisplayManager +import android.os.Handler +import android.view.Display +import android.view.DisplayInfo +import android.view.Surface import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository -import com.android.systemui.biometrics.data.repository.RearDisplayStateRepositoryImpl +import com.android.systemui.biometrics.data.repository.DisplayStateRepository +import com.android.systemui.biometrics.data.repository.DisplayStateRepositoryImpl +import com.android.systemui.biometrics.shared.model.DisplayRotation import com.android.systemui.coroutines.collectLastValue import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -38,8 +46,10 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.same import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule @@ -50,16 +60,19 @@ private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3 @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) -class RearDisplayStateRepositoryTest : SysuiTestCase() { +class DisplayStateRepositoryTest : SysuiTestCase() { @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule() @Mock private lateinit var deviceStateManager: DeviceStateManager - private lateinit var underTest: RearDisplayStateRepository + @Mock private lateinit var displayManager: DisplayManager + @Mock private lateinit var handler: Handler + @Mock private lateinit var display: Display + private lateinit var underTest: DisplayStateRepository private val testScope = TestScope(StandardTestDispatcher()) private val fakeExecutor = FakeExecutor(FakeSystemClock()) @Captor - private lateinit var callbackCaptor: ArgumentCaptor<DeviceStateManager.DeviceStateCallback> + private lateinit var displayListenerCaptor: ArgumentCaptor<DisplayManager.DisplayListener> @Before fun setUp() { @@ -69,11 +82,16 @@ class RearDisplayStateRepositoryTest : SysuiTestCase() { rearDisplayDeviceStates ) + mContext = spy(mContext) + whenever(mContext.display).thenReturn(display) + underTest = - RearDisplayStateRepositoryImpl( + DisplayStateRepositoryImpl( testScope.backgroundScope, mContext, deviceStateManager, + displayManager, + handler, fakeExecutor ) } @@ -81,16 +99,46 @@ class RearDisplayStateRepositoryTest : SysuiTestCase() { @Test fun updatesIsInRearDisplayMode_whenRearDisplayStateChanges() = testScope.runTest { - val isInRearDisplayMode = collectLastValue(underTest.isInRearDisplayMode) + val isInRearDisplayMode by collectLastValue(underTest.isInRearDisplayMode) runCurrent() val callback = deviceStateManager.captureCallback() callback.onStateChanged(NORMAL_DISPLAY_MODE_DEVICE_STATE) - assertThat(isInRearDisplayMode()).isFalse() + assertThat(isInRearDisplayMode).isFalse() callback.onStateChanged(REAR_DISPLAY_MODE_DEVICE_STATE) - assertThat(isInRearDisplayMode()).isTrue() + assertThat(isInRearDisplayMode).isTrue() + } + + @Test + fun updatesCurrentRotation_whenDisplayStateChanges() = + testScope.runTest { + val currentRotation by collectLastValue(underTest.currentRotation) + runCurrent() + + verify(displayManager) + .registerDisplayListener( + displayListenerCaptor.capture(), + same(handler), + eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) + ) + + whenever(display.getDisplayInfo(any())).then { + val info = it.getArgument<DisplayInfo>(0) + info.rotation = Surface.ROTATION_90 + return@then true + } + displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_90) + assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_90) + + whenever(display.getDisplayInfo(any())).then { + val info = it.getArgument<DisplayInfo>(0) + info.rotation = Surface.ROTATION_180 + return@then true + } + displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180) + assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_180) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt index 524f2547ea4c..bf6caad688e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt @@ -3,7 +3,8 @@ package com.android.systemui.biometrics.domain.interactor import android.view.Display import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository +import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository +import com.android.systemui.biometrics.shared.model.DisplayRotation import com.android.systemui.coroutines.collectLastValue import com.android.systemui.display.data.repository.FakeDisplayRepository import com.android.systemui.display.data.repository.display @@ -37,7 +38,7 @@ class DisplayStateInteractorImplTest : SysuiTestCase() { private val fakeExecutor = FakeExecutor(FakeSystemClock()) private val testScope = TestScope(StandardTestDispatcher()) - private lateinit var rearDisplayStateRepository: FakeRearDisplayStateRepository + private lateinit var displayStateRepository: FakeDisplayStateRepository private lateinit var displayRepository: FakeDisplayRepository @Mock private lateinit var screenSizeFoldProvider: ScreenSizeFoldProvider @@ -45,14 +46,14 @@ class DisplayStateInteractorImplTest : SysuiTestCase() { @Before fun setup() { - rearDisplayStateRepository = FakeRearDisplayStateRepository() + displayStateRepository = FakeDisplayStateRepository() displayRepository = FakeDisplayRepository() interactor = DisplayStateInteractorImpl( testScope.backgroundScope, mContext, fakeExecutor, - rearDisplayStateRepository, + displayStateRepository, displayRepository, ) interactor.setScreenSizeFoldProvider(screenSizeFoldProvider) @@ -61,27 +62,39 @@ class DisplayStateInteractorImplTest : SysuiTestCase() { @Test fun isInRearDisplayModeChanges() = testScope.runTest { - val isInRearDisplayMode = collectLastValue(interactor.isInRearDisplayMode) + val isInRearDisplayMode by collectLastValue(interactor.isInRearDisplayMode) - rearDisplayStateRepository.setIsInRearDisplayMode(false) - assertThat(isInRearDisplayMode()).isFalse() + displayStateRepository.setIsInRearDisplayMode(false) + assertThat(isInRearDisplayMode).isFalse() - rearDisplayStateRepository.setIsInRearDisplayMode(true) - assertThat(isInRearDisplayMode()).isTrue() + displayStateRepository.setIsInRearDisplayMode(true) + assertThat(isInRearDisplayMode).isTrue() + } + + @Test + fun currentRotationChanges() = + testScope.runTest { + val currentRotation by collectLastValue(interactor.currentRotation) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180) + assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_180) + + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_90) } @Test fun isFoldedChanges() = testScope.runTest { - val isFolded = collectLastValue(interactor.isFolded) + val isFolded by collectLastValue(interactor.isFolded) runCurrent() val callback = screenSizeFoldProvider.captureCallback() callback.onFoldUpdated(isFolded = true) - assertThat(isFolded()).isTrue() + assertThat(isFolded).isTrue() callback.onFoldUpdated(isFolded = false) - assertThat(isFolded()).isFalse() + assertThat(isFolded).isFalse() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt index b3964b619505..fd86486aeff8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt @@ -4,9 +4,9 @@ import android.content.res.Configuration import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.FakePromptRepository -import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor @@ -43,7 +43,7 @@ class PromptFingerprintIconViewModelTest : SysuiTestCase() { private lateinit var displayRepository: FakeDisplayRepository private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository private lateinit var promptRepository: FakePromptRepository - private lateinit var rearDisplayStateRepository: FakeRearDisplayStateRepository + private lateinit var displayStateRepository: FakeDisplayStateRepository private val testScope = TestScope(StandardTestDispatcher()) private val fakeExecutor = FakeExecutor(FakeSystemClock()) @@ -57,7 +57,7 @@ class PromptFingerprintIconViewModelTest : SysuiTestCase() { displayRepository = FakeDisplayRepository() fingerprintRepository = FakeFingerprintPropertyRepository() promptRepository = FakePromptRepository() - rearDisplayStateRepository = FakeRearDisplayStateRepository() + displayStateRepository = FakeDisplayStateRepository() promptSelectorInteractor = PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) @@ -66,7 +66,7 @@ class PromptFingerprintIconViewModelTest : SysuiTestCase() { testScope.backgroundScope, mContext, fakeExecutor, - rearDisplayStateRepository, + displayStateRepository, displayRepository, ) viewModel = PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor) 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 5834e31cb591..ca6df4027ea9 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 @@ -24,9 +24,9 @@ import android.view.MotionEvent import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.FakePromptRepository -import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor @@ -82,7 +82,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository private lateinit var promptRepository: FakePromptRepository - private lateinit var rearDisplayStateRepository: FakeRearDisplayStateRepository + private lateinit var displayStateRepository: FakeDisplayStateRepository private lateinit var displayRepository: FakeDisplayRepository private lateinit var displayStateInteractor: DisplayStateInteractor @@ -94,21 +94,22 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa fun setup() { fingerprintRepository = FakeFingerprintPropertyRepository() promptRepository = FakePromptRepository() - rearDisplayStateRepository = FakeRearDisplayStateRepository() + displayStateRepository = FakeDisplayStateRepository() displayRepository = FakeDisplayRepository() displayStateInteractor = DisplayStateInteractorImpl( testScope.backgroundScope, mContext, fakeExecutor, - rearDisplayStateRepository, + displayStateRepository, displayRepository, ) selector = PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) selector.resetPrompt() - viewModel = PromptViewModel(displayStateInteractor, selector, vibrator, featureFlags) + viewModel = + PromptViewModel(displayStateInteractor, selector, vibrator, mContext, featureFlags) featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 3c5212ae7e5c..8ce738ca89f6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -21,6 +21,7 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -30,6 +31,7 @@ import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -67,6 +69,9 @@ class PatternBouncerViewModelTest : SysuiTestCase() { isInputEnabled = MutableStateFlow(true).asStateFlow(), ) + private val containerSize = 90 // px + private val dotSize = 30 // px + @Before fun setUp() { overrideResource(R.string.keyguard_enter_your_pattern, ENTER_YOUR_PATTERN) @@ -80,13 +85,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern - ) - utils.authenticationRepository.setUnlocked(false) - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) + transitionToPatternBouncer() underTest.onShown() @@ -103,13 +102,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern - ) - utils.authenticationRepository.setUnlocked(false) - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) + transitionToPatternBouncer() underTest.onShown() runCurrent() @@ -127,23 +120,12 @@ class PatternBouncerViewModelTest : SysuiTestCase() { val currentScene by collectLastValue(sceneInteractor.desiredScene) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern - ) - utils.authenticationRepository.setUnlocked(false) - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) + transitionToPatternBouncer() underTest.onShown() underTest.onDragStart() assertThat(currentDot).isNull() CORRECT_PATTERN.forEachIndexed { index, coordinate -> - underTest.onDrag( - xPx = 30f * coordinate.x + 15, - yPx = 30f * coordinate.y + 15, - containerSizePx = 90, - verticalOffsetPx = 0f, - ) + dragToCoordinate(coordinate) assertWithMessage("Wrong selected dots for index $index") .that(selectedDots) .isEqualTo( @@ -176,23 +158,10 @@ class PatternBouncerViewModelTest : SysuiTestCase() { val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern - ) - utils.authenticationRepository.setUnlocked(false) - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) + transitionToPatternBouncer() underTest.onShown() underTest.onDragStart() - CORRECT_PATTERN.subList(0, 3).forEach { coordinate -> - underTest.onDrag( - xPx = 30f * coordinate.x + 15, - yPx = 30f * coordinate.y + 15, - containerSizePx = 90, - verticalOffsetPx = 0f, - ) - } + CORRECT_PATTERN.subList(0, 3).forEach { coordinate -> dragToCoordinate(coordinate) } underTest.onDragEnd() @@ -203,6 +172,147 @@ class PatternBouncerViewModelTest : SysuiTestCase() { } @Test + fun onDrag_shouldIncludeDotsThatWereSkippedOverAlongTheSameRow() = + testScope.runTest { + val selectedDots by collectLastValue(underTest.selectedDots) + transitionToPatternBouncer() + underTest.onShown() + + /* + * Pattern setup, coordinates are (column, row) + * 0 1 2 + * 0 x x x + * 1 x x x + * 2 x x x + */ + // Select (0,0), Skip over (1,0) and select (2,0) + dragOverCoordinates(Point(0, 0), Point(2, 0)) + + assertThat(selectedDots) + .isEqualTo( + listOf( + PatternDotViewModel(0, 0), + PatternDotViewModel(1, 0), + PatternDotViewModel(2, 0) + ) + ) + } + + @Test + fun onDrag_shouldIncludeDotsThatWereSkippedOverAlongTheSameColumn() = + testScope.runTest { + val selectedDots by collectLastValue(underTest.selectedDots) + transitionToPatternBouncer() + underTest.onShown() + + /* + * Pattern setup, coordinates are (column, row) + * 0 1 2 + * 0 x x x + * 1 x x x + * 2 x x x + */ + // Select (1,0), Skip over (1,1) and select (1, 2) + dragOverCoordinates(Point(1, 0), Point(1, 2)) + + assertThat(selectedDots) + .isEqualTo( + listOf( + PatternDotViewModel(1, 0), + PatternDotViewModel(1, 1), + PatternDotViewModel(1, 2) + ) + ) + } + + @Test + fun onDrag_shouldIncludeDotsThatWereSkippedOverAlongTheDiagonal() = + testScope.runTest { + val selectedDots by collectLastValue(underTest.selectedDots) + transitionToPatternBouncer() + underTest.onShown() + + /* + * Pattern setup + * 0 1 2 + * 0 x x x + * 1 x x x + * 2 x x x + * + * Coordinates are (column, row) + * Select (2,0), Skip over (1,1) and select (0, 2) + */ + dragOverCoordinates(Point(2, 0), Point(0, 2)) + + assertThat(selectedDots) + .isEqualTo( + listOf( + PatternDotViewModel(2, 0), + PatternDotViewModel(1, 1), + PatternDotViewModel(0, 2) + ) + ) + } + + @Test + fun onDrag_shouldNotIncludeDotIfItIsNotOnTheLine() = + testScope.runTest { + val selectedDots by collectLastValue(underTest.selectedDots) + transitionToPatternBouncer() + underTest.onShown() + + /* + * Pattern setup + * 0 1 2 + * 0 x x x + * 1 x x x + * 2 x x x + * + * Coordinates are (column, row) + */ + dragOverCoordinates(Point(0, 0), Point(1, 0), Point(2, 0), Point(0, 1)) + + assertThat(selectedDots) + .isEqualTo( + listOf( + PatternDotViewModel(0, 0), + PatternDotViewModel(1, 0), + PatternDotViewModel(2, 0), + PatternDotViewModel(0, 1), + ) + ) + } + + @Test + fun onDrag_shouldNotIncludeSkippedOverDotsIfTheyAreAlreadySelected() = + testScope.runTest { + val selectedDots by collectLastValue(underTest.selectedDots) + transitionToPatternBouncer() + underTest.onShown() + + /* + * Pattern setup + * 0 1 2 + * 0 x x x + * 1 x x x + * 2 x x x + * + * Coordinates are (column, row) + */ + dragOverCoordinates(Point(1, 0), Point(1, 1), Point(0, 0), Point(2, 0)) + + assertThat(selectedDots) + .isEqualTo( + listOf( + PatternDotViewModel(1, 0), + PatternDotViewModel(1, 1), + PatternDotViewModel(0, 0), + PatternDotViewModel(2, 0), + ) + ) + } + + @Test fun onDragEnd_whenPatternTooShort() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) @@ -252,23 +362,10 @@ class PatternBouncerViewModelTest : SysuiTestCase() { val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pattern - ) - utils.authenticationRepository.setUnlocked(false) - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) + transitionToPatternBouncer() underTest.onShown() underTest.onDragStart() - CORRECT_PATTERN.subList(2, 7).forEach { coordinate -> - underTest.onDrag( - xPx = 30f * coordinate.x + 15, - yPx = 30f * coordinate.y + 15, - containerSizePx = 90, - verticalOffsetPx = 0f, - ) - } + CORRECT_PATTERN.subList(2, 7).forEach { coordinate -> dragToCoordinate(coordinate) } underTest.onDragEnd() assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() @@ -276,20 +373,36 @@ class PatternBouncerViewModelTest : SysuiTestCase() { assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) // Enter the correct pattern: - CORRECT_PATTERN.forEach { coordinate -> - underTest.onDrag( - xPx = 30f * coordinate.x + 15, - yPx = 30f * coordinate.y + 15, - containerSizePx = 90, - verticalOffsetPx = 0f, - ) - } + CORRECT_PATTERN.forEach { coordinate -> dragToCoordinate(coordinate) } underTest.onDragEnd() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } + private fun dragOverCoordinates(vararg coordinatesDragged: Point) { + underTest.onDragStart() + coordinatesDragged.forEach { dragToCoordinate(it) } + } + + private fun dragToCoordinate(coordinate: Point) { + underTest.onDrag( + xPx = dotSize * coordinate.x + 15f, + yPx = dotSize * coordinate.y + 15f, + containerSizePx = containerSize, + verticalOffsetPx = 0f, + ) + } + + private fun TestScope.transitionToPatternBouncer() { + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern) + utils.authenticationRepository.setUnlocked(false) + sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") + sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") + assertThat(collectLastValue(sceneInteractor.desiredScene).invoke()) + .isEqualTo(SceneModel(SceneKey.Bouncer)) + } + companion object { private const val ENTER_YOUR_PATTERN = "Enter your pattern" private const val WRONG_PATTERN = "Wrong pattern" diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt index 3a0883b3a575..511562f2aec0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt @@ -21,6 +21,7 @@ import android.os.Looper import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.Display +import android.view.Display.TYPE_EXTERNAL import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.FlowValue @@ -62,6 +63,7 @@ class DisplayRepositoryTest : SysuiTestCase() { @Before fun setup() { setDisplays(emptyList()) + setAllDisplaysIncludingDisabled() displayRepository = DisplayRepositoryImpl( displayManager, @@ -70,6 +72,7 @@ class DisplayRepositoryTest : SysuiTestCase() { UnconfinedTestDispatcher() ) verify(displayManager, never()).registerDisplayListener(any(), any()) + verify(displayManager, never()).getDisplays(any()) } @Test @@ -351,6 +354,22 @@ class DisplayRepositoryTest : SysuiTestCase() { } @Test + fun initialState_onePendingDisplayOnBoot_notNull() = + testScope.runTest { + // 1 is not enabled, but just connected. It should be seen as pending + setAllDisplaysIncludingDisabled(0, 1) + setDisplays(0) // 0 is enabled. + verify(displayManager, never()).getDisplays(any()) + + val pendingDisplay by collectLastValue(displayRepository.pendingDisplay) + + verify(displayManager).getDisplays(any()) + + assertThat(pendingDisplay).isNotNull() + assertThat(pendingDisplay!!.id).isEqualTo(1) + } + + @Test fun onPendingDisplay_internalDisplay_ignored() = testScope.runTest { val pendingDisplay by lastPendingDisplay() @@ -365,7 +384,7 @@ class DisplayRepositoryTest : SysuiTestCase() { testScope.runTest { val pendingDisplay by lastPendingDisplay() - sendOnDisplayConnected(1, Display.TYPE_EXTERNAL) + sendOnDisplayConnected(1, TYPE_EXTERNAL) sendOnDisplayConnected(2, Display.TYPE_INTERNAL) assertThat(pendingDisplay!!.id).isEqualTo(1) @@ -416,7 +435,7 @@ class DisplayRepositoryTest : SysuiTestCase() { whenever(displayManager.getDisplay(eq(id))).thenReturn(null) } - private fun sendOnDisplayConnected(id: Int, displayType: Int = Display.TYPE_EXTERNAL) { + private fun sendOnDisplayConnected(id: Int, displayType: Int = TYPE_EXTERNAL) { val mockDisplay = display(id = id, type = displayType) whenever(displayManager.getDisplay(eq(id))).thenReturn(mockDisplay) connectedDisplayListener.value.onDisplayConnected(id) @@ -424,15 +443,25 @@ class DisplayRepositoryTest : SysuiTestCase() { private fun setDisplays(displays: List<Display>) { whenever(displayManager.displays).thenReturn(displays.toTypedArray()) + displays.forEach { display -> + whenever(displayManager.getDisplay(eq(display.displayId))).thenReturn(display) + } } - private fun setDisplays(vararg ids: Int) { - setDisplays(ids.map { display(it) }) + private fun setAllDisplaysIncludingDisabled(vararg ids: Int) { + val displays = ids.map { display(type = TYPE_EXTERNAL, id = it) }.toTypedArray() + whenever( + displayManager.getDisplays( + eq(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED) + ) + ) + .thenReturn(displays) + displays.forEach { display -> + whenever(displayManager.getDisplay(eq(display.displayId))).thenReturn(display) + } } - private fun display(id: Int): Display { - return mock<Display>().also { mockDisplay -> - whenever(mockDisplay.displayId).thenReturn(id) - } + private fun setDisplays(vararg ids: Int) { + setDisplays(ids.map { display(type = TYPE_EXTERNAL, id = it) }) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index f62137c62c65..f3c9432cb4bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -172,7 +172,6 @@ class CustomizationProviderTest : SysuiTestCase() { val featureFlags = FakeFeatureFlags().apply { set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true) - set(Flags.REVAMPED_WALLPAPER_UI, true) set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true) set(Flags.FACE_AUTH_REFACTOR, true) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index f78d051eca61..a45b0bd4cca8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -186,8 +186,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock AuthController mAuthController; private @Mock ShadeExpansionStateManager mShadeExpansionStateManager; private @Mock ShadeWindowLogger mShadeWindowLogger; - private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback> - mKeyguardUpdateMonitorCallbackCaptor; private @Captor ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); @@ -294,25 +292,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { } @Test - @TestableLooper.RunWithLooper(setAsMainLooper = true) - public void onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally() { - // GIVEN keyguard is not enabled and isn't showing - mViewMediator.onSystemReady(); - mViewMediator.setKeyguardEnabled(false); - TestableLooper.get(this).processAllMessages(); - captureKeyguardUpdateMonitorCallback(); - assertFalse(mViewMediator.isShowingAndNotOccluded()); - - // WHEN lockdown occurs - when(mLockPatternUtils.isUserInLockdown(anyInt())).thenReturn(true); - mKeyguardUpdateMonitorCallbackCaptor.getValue().onStrongAuthStateChanged(0); - - // THEN keyguard is shown - TestableLooper.get(this).processAllMessages(); - assertTrue(mViewMediator.isShowingAndNotOccluded()); - } - - @Test public void testOnGoingToSleep_UpdatesKeyguardGoingAway() { mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER); verify(mUpdateMonitor).dispatchKeyguardGoingAway(false); @@ -1120,10 +1099,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null); } - private void captureKeyguardUpdateMonitorCallback() { - verify(mUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallbackCaptor.capture()); - } - private void captureKeyguardStateControllerCallback() { verify(mKeyguardStateController).addCallback(mKeyguardStateControllerCallback.capture()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index f234582d792b..6b194f243b2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -36,13 +36,14 @@ import com.android.internal.logging.InstanceId.fakeInstanceId import com.android.internal.logging.UiEventLogger import com.android.keyguard.FaceAuthUiEvent import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN +import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.R import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository -import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository @@ -208,7 +209,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { applicationScope = testScope.backgroundScope, context = context, mainExecutor = FakeExecutor(FakeSystemClock()), - rearDisplayStateRepository = FakeRearDisplayStateRepository(), + displayStateRepository = FakeDisplayStateRepository(), displayRepository = displayRepository, ) @@ -285,7 +286,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { allPreconditionsToRunFaceAuthAreTrue() FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER.extraInfo = 10 - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() uiEventIsLogged(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) @@ -318,12 +319,12 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { initCollectors() allPreconditionsToRunFaceAuthAreTrue() - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() clearInvocations(faceManager) clearInvocations(uiEventLogger) - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) verifyNoMoreInteractions(faceManager) verifyNoMoreInteractions(uiEventLogger) } @@ -335,7 +336,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { verify(faceManager).addLockoutResetCallback(faceLockoutResetCallback.capture()) allPreconditionsToRunFaceAuthAreTrue() - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() authenticationCallback.value.onAuthenticationError( @@ -389,7 +390,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { initCollectors() allPreconditionsToRunFaceAuthAreTrue() - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() var wasAuthCancelled = false @@ -443,7 +444,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { initCollectors() allPreconditionsToRunFaceAuthAreTrue() - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() // Enter cancelling state @@ -451,7 +452,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { clearInvocations(faceManager) // Auth is while cancelling. - underTest.authenticate(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN) // Auth is not started verifyNoMoreInteractions(faceManager) @@ -474,14 +475,14 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { initCollectors() allPreconditionsToRunFaceAuthAreTrue() - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() clearInvocations(faceManager) underTest.cancel() advanceTimeBy(DeviceEntryFaceAuthRepositoryImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT + 1) - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() } @@ -492,7 +493,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { allPreconditionsToRunFaceAuthAreTrue() val emittedValues by collectValues(underTest.authenticationStatus) - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) underTest.cancel() advanceTimeBy(100) underTest.cancel() @@ -519,7 +520,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { initCollectors() allPreconditionsToRunFaceAuthAreTrue() - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) faceAuthenticateIsCalled() authenticationCallback.value.onAuthenticationHelp(9, "help msg") @@ -562,8 +563,26 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test - fun authenticateDoesNotRunIfFaceAuthIsCurrentlyPaused() = - testScope.runTest { testGatingCheckForFaceAuth { underTest.pauseFaceAuth() } } + fun authenticateDoesNotRunIfUserSwitchingIsCurrentlyInProgress() = + testScope.runTest { + testGatingCheckForFaceAuth { + fakeUserRepository.setSelectedUserInfo( + primaryUser, + SelectionStatus.SELECTION_IN_PROGRESS + ) + } + } + + @Test + fun detectDoesNotRunIfUserSwitchingIsCurrentlyInProgress() = + testScope.runTest { + testGatingCheckForDetect { + fakeUserRepository.setSelectedUserInfo( + userInfo = primaryUser, + selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS + ) + } + } @Test fun authenticateDoesNotRunIfKeyguardIsNotShowing() = @@ -582,12 +601,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { testScope.runTest { testGatingCheckForFaceAuth { underTest.setLockedOut(true) } } @Test - fun authenticateDoesNotRunWhenUserIsCurrentlyTrusted() = - testScope.runTest { - testGatingCheckForFaceAuth { trustRepository.setCurrentUserTrusted(true) } - } - - @Test fun authenticateDoesNotRunWhenKeyguardIsGoingAway() = testScope.runTest { testGatingCheckForFaceAuth { keyguardRepository.setKeyguardGoingAway(true) } @@ -608,14 +621,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test - fun authenticateDoesNotRunWhenFaceAuthIsNotCurrentlyAllowedToRun() = - testScope.runTest { - testGatingCheckForFaceAuth { - biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false) - } - } - - @Test fun authenticateDoesNotRunWhenSecureCameraIsActive() = testScope.runTest { testGatingCheckForFaceAuth { @@ -672,7 +677,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { // Flip one precondition to false. biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false) assertThat(canFaceAuthRun()).isFalse() - underTest.authenticate( + underTest.requestAuthenticate( FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true ) @@ -693,7 +698,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { trustRepository.setCurrentUserTrusted(true) assertThat(canFaceAuthRun()).isFalse() - underTest.authenticate( + underTest.requestAuthenticate( FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true ) @@ -884,10 +889,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test - fun detectDoesNotRunWhenUserSwitchingInProgress() = - testScope.runTest { testGatingCheckForDetect { underTest.pauseFaceAuth() } } - - @Test fun detectDoesNotRunWhenKeyguardGoingAway() = testScope.runTest { testGatingCheckForDetect { keyguardRepository.setKeyguardGoingAway(true) } @@ -1075,6 +1076,28 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { faceAuthenticateIsCalled() } + @Test + fun queuedAuthOnlyRequestShouldNotBeProcessedIfOnlyDetectionCanBeRun() = + testScope.runTest { + initCollectors() + allPreconditionsToRunFaceAuthAreTrue() + + // This will prevent auth from running but not detection + biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false) + + runCurrent() + assertThat(canFaceAuthRun()).isFalse() + + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, false) + runCurrent() + + faceDetectIsNotCalled() + faceAuthenticateIsNotCalled() + + biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true) + faceAuthenticateIsCalled() + } + private suspend fun TestScope.testGatingCheckForFaceAuth( gatingCheckModifier: suspend () -> Unit ) { @@ -1087,10 +1110,18 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { // gating check doesn't allow face auth to run. assertThat(underTest.canRunFaceAuth.value).isFalse() + // request face auth just before gating conditions become true, this ensures any race + // conditions won't prevent face auth from running + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false) + faceAuthenticateIsNotCalled() + // flip the gating check back on. allPreconditionsToRunFaceAuthAreTrue() + assertThat(underTest.canRunFaceAuth.value).isTrue() - triggerFaceAuth(false) + faceAuthenticateIsCalled() + assertThat(authRunning()).isTrue() + cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true } // Flip gating check off gatingCheckModifier() @@ -1101,13 +1132,17 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { clearInvocations(faceManager) // Try auth again - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER) + + runCurrent() // Auth can't run again faceAuthenticateIsNotCalled() } - private suspend fun TestScope.testGatingCheckForDetect(gatingCheckModifier: () -> Unit) { + private suspend fun TestScope.testGatingCheckForDetect( + gatingCheckModifier: suspend () -> Unit + ) { initCollectors() allPreconditionsToRunFaceAuthAreTrue() @@ -1118,7 +1153,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { assertThat(canFaceAuthRun()).isFalse() // Trigger authenticate with detection fallback - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true) + underTest.requestAuthenticate( + FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, + fallbackToDetection = true + ) + runCurrent() faceAuthenticateIsNotCalled() faceDetectIsCalled() @@ -1133,15 +1172,21 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { clearInvocations(faceManager) // Try to run detect again - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true) + underTest.requestAuthenticate( + FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, + fallbackToDetection = true + ) // Detect won't run because preconditions are not true anymore. faceDetectIsNotCalled() } - private suspend fun triggerFaceAuth(fallbackToDetect: Boolean) { + private fun TestScope.triggerFaceAuth(fallbackToDetect: Boolean) { assertThat(canFaceAuthRun()).isTrue() - underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetect) + underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetect) + + runCurrent() + faceAuthenticateIsCalled() assertThat(authRunning()).isTrue() cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true } @@ -1150,7 +1195,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() { verify(faceManager, atLeastOnce()) .addLockoutResetCallback(faceLockoutResetCallback.capture()) - underTest.resumeFaceAuth() trustRepository.setCurrentUserTrusted(false) keyguardRepository.setKeyguardGoingAway(false) keyguardRepository.setWakefulnessModel( @@ -1164,7 +1208,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true) biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true) biometricSettingsRepository.setIsUserInLockdown(false) - fakeUserRepository.setSelectedUserInfo(primaryUser) + fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE) faceLockoutResetCallback.value.onLockoutReset(0) bouncerRepository.setAlternateVisible(true) keyguardRepository.setKeyguardShowing(true) @@ -1187,7 +1231,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { private fun successResult() = FaceManager.AuthenticationResult(null, null, primaryUserId, false) - private fun faceDetectIsCalled() { + private fun TestScope.faceDetectIsCalled() { + runCurrent() + verify(faceManager) .detectFace( cancellationSignal.capture(), @@ -1196,7 +1242,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { ) } - private fun faceAuthenticateIsCalled() { + private fun TestScope.faceAuthenticateIsCalled() { + runCurrent() + verify(faceManager) .authenticate( isNull(), @@ -1207,7 +1255,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { ) } - private fun faceAuthenticateIsNotCalled() { + private fun TestScope.faceAuthenticateIsNotCalled() { + runCurrent() + verify(faceManager, never()) .authenticate( isNull(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index da70a9ff036f..2ed9de26dfa3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -224,23 +224,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { } @Test - fun faceAuthIsPausedWhenUserSwitchingIsInProgress() = - testScope.runTest { - underTest.start() - - fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE) - runCurrent() - fakeUserRepository.setSelectedUserInfo( - secondaryUser, - SelectionStatus.SELECTION_IN_PROGRESS - ) - runCurrent() - - assertThat(faceAuthRepository.isFaceAuthPaused()).isTrue() - } - - @Test - fun faceAuthIsUnpausedWhenUserSwitchingIsInComplete() = + fun faceAuthLockedOutStateIsUpdatedAfterUserSwitch() = testScope.runTest { underTest.start() @@ -251,7 +235,6 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { SelectionStatus.SELECTION_IN_PROGRESS ) runCurrent() - assertThat(faceAuthRepository.isFaceAuthPaused()).isTrue() bouncerRepository.setPrimaryShow(true) // New user is not locked out. @@ -262,7 +245,6 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { ) runCurrent() - assertThat(faceAuthRepository.isFaceAuthPaused()).isFalse() assertThat(faceAuthRepository.isLockedOut.value).isFalse() runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt index 0050d64d7505..0bbeeff8eee3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt @@ -302,7 +302,6 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { featureFlags = FakeFeatureFlags().apply { set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled) - set(Flags.REVAMPED_WALLPAPER_UI, isRevampedWppFeatureEnabled) set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled) }, broadcastDispatcher = fakeBroadcastDispatcher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 681fce822342..d6b621e55dd7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSect import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import com.android.systemui.util.mockito.whenever @@ -60,6 +61,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection @Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection @Mock private lateinit var defaultStatusViewSection: DefaultStatusViewSection + @Mock private lateinit var defaultStatusBarViewSection: DefaultStatusBarSection @Mock private lateinit var defaultNSSLSection: DefaultNotificationStackScrollLayoutSection @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines @Mock private lateinit var aodNotificationIconsSection: AodNotificationIconsSection @@ -78,6 +80,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { defaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection, defaultStatusViewSection, + defaultStatusBarViewSection, defaultNSSLSection, splitShadeGuidelines, aodNotificationIconsSection, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index d1299d40ea12..5939bb50bb25 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -16,10 +16,12 @@ package com.android.systemui.media.controls.pipeline +import android.app.IUriGrantsManager import android.app.Notification import android.app.Notification.FLAG_NO_CLEAR import android.app.Notification.MediaStyle import android.app.PendingIntent +import android.app.UriGrantsManager import android.app.smartspace.SmartspaceAction import android.app.smartspace.SmartspaceConfig import android.app.smartspace.SmartspaceManager @@ -27,12 +29,14 @@ import android.app.smartspace.SmartspaceTarget import android.content.Intent import android.content.pm.PackageManager import android.graphics.Bitmap +import android.graphics.ImageDecoder import android.graphics.drawable.Icon import android.media.MediaDescription import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.MediaSession import android.media.session.PlaybackState +import android.net.Uri import android.os.Bundle import android.provider.Settings import android.service.notification.StatusBarNotification @@ -40,6 +44,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.media.utils.MediaConstants import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.logging.InstanceId import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.InstanceIdSequenceFake @@ -83,7 +88,9 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoSession import org.mockito.junit.MockitoJUnit +import org.mockito.quality.Strictness private const val KEY = "KEY" private const val KEY_2 = "KEY_2" @@ -149,6 +156,8 @@ class MediaDataManagerTest : SysuiTestCase() { @Captor lateinit var stateCallbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit> @Captor lateinit var sessionCallbackCaptor: ArgumentCaptor<(String) -> Unit> @Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig> + @Mock private lateinit var ugm: IUriGrantsManager + @Mock private lateinit var imageSource: ImageDecoder.Source private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20) @@ -159,8 +168,17 @@ class MediaDataManagerTest : SysuiTestCase() { 1 ) + private lateinit var staticMockSession: MockitoSession + @Before fun setup() { + staticMockSession = + ExtendedMockito.mockitoSession() + .mockStatic<UriGrantsManager>(UriGrantsManager::class.java) + .mockStatic<ImageDecoder>(ImageDecoder::class.java) + .strictness(Strictness.LENIENT) + .startMocking() + whenever(UriGrantsManager.getService()).thenReturn(ugm) foregroundExecutor = FakeExecutor(clock) backgroundExecutor = FakeExecutor(clock) uiExecutor = FakeExecutor(clock) @@ -270,6 +288,7 @@ class MediaDataManagerTest : SysuiTestCase() { @After fun tearDown() { + staticMockSession.finishMocking() session.release() mediaDataManager.destroy() Settings.Secure.putInt( @@ -2198,6 +2217,66 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener).onMediaDataRemoved(eq(KEY)) } + @Test + fun testResumeMediaLoaded_hasArtPermission_artLoaded() { + // When resume media is loaded and user/app has permission to access the art URI, + whenever( + ugm.checkGrantUriPermission_ignoreNonSystem( + anyInt(), + any(), + any(), + anyInt(), + anyInt() + ) + ) + .thenReturn(1) + val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + val uri = Uri.parse("content://example") + whenever(ImageDecoder.createSource(any(), eq(uri))).thenReturn(imageSource) + whenever(ImageDecoder.decodeBitmap(any(), any())).thenReturn(artwork) + + val desc = + MediaDescription.Builder().run { + setTitle(SESSION_TITLE) + setIconUri(uri) + build() + } + addResumeControlAndLoad(desc) + + // Then the artwork is loaded + assertThat(mediaDataCaptor.value.artwork).isNotNull() + } + + @Test + fun testResumeMediaLoaded_noArtPermission_noArtLoaded() { + // When resume media is loaded and user/app does not have permission to access the art URI + whenever( + ugm.checkGrantUriPermission_ignoreNonSystem( + anyInt(), + any(), + any(), + anyInt(), + anyInt() + ) + ) + .thenThrow(SecurityException("Test no permission")) + val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + val uri = Uri.parse("content://example") + whenever(ImageDecoder.createSource(any(), eq(uri))).thenReturn(imageSource) + whenever(ImageDecoder.decodeBitmap(any(), any())).thenReturn(artwork) + + val desc = + MediaDescription.Builder().run { + setTitle(SESSION_TITLE) + setIconUri(uri) + build() + } + addResumeControlAndLoad(desc) + + // Then the artwork is not loaded + assertThat(mediaDataCaptor.value.artwork).isNull() + } + /** Helper function to add a basic media notification and capture the resulting MediaData */ private fun addNotificationAndLoad() { addNotificationAndLoad(mediaNotification) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt index 91b0245be8d3..7ad2ce8ae110 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.MediaContainerView import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings @@ -90,6 +91,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { settings, fakeHandler, configurationController, + ResourcesSplitShadeStateController() ) keyguardMediaController.attachSinglePaneContainer(mediaContainerView) keyguardMediaController.useSplitShade = false diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt index 2ce236d4ba89..33ed01f115b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -120,6 +121,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { notifPanelEvents, settings, fakeHandler, + ResourcesSplitShadeStateController() ) verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture()) verify(statusBarStateController).addCallback(statusBarCallback.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index fcda5f50cef2..8afe095cbaf0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -66,6 +66,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; import com.android.systemui.util.animation.UniqueObjectHostView; @@ -524,7 +525,10 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { return new QSFragment( new RemoteInputQuickSettingsDisabler( - context, commandQueue, mock(ConfigurationController.class)), + context, + commandQueue, + new ResourcesSplitShadeStateController(), + mock(ConfigurationController.class)), mStatusBarStateController, commandQueue, mQSMediaHost, 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 6720dae10230..2ac220c1975d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java @@ -50,6 +50,7 @@ import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.util.animation.DisappearParameters; import org.junit.Before; @@ -110,7 +111,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, DumpManager dumpManager) { super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger, - qsLogger, dumpManager); + qsLogger, dumpManager, new ResourcesSplitShadeStateController()); } @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 9d9d0c7de2ad..8a530dd7206c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt @@ -18,6 +18,7 @@ import com.android.systemui.qs.logging.QSLogger import com.android.systemui.settings.brightness.BrightnessController import com.android.systemui.settings.brightness.BrightnessSliderController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.tuner.TunerService import com.google.common.truth.Truth.assertThat import org.junit.After @@ -91,7 +92,8 @@ class QSPanelControllerTest : SysuiTestCase() { brightnessControllerFactory, brightnessSliderFactory, falsingManager, - statusBarKeyguardViewManager + statusBarKeyguardViewManager, + ResourcesSplitShadeStateController() ) } 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 71ea831e0f92..f188b4e19a2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.qs.QSTileView import com.android.systemui.qs.customize.QSCustomizerController import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.util.leak.RotationUtils import org.junit.After import org.junit.Before @@ -167,7 +168,8 @@ class QuickQSPanelControllerTest : SysuiTestCase() { metricsLogger, uiEventLogger, qsLogger, - dumpManager) { + dumpManager, + ResourcesSplitShadeStateController()) { private var rotation = RotationUtils.ROTATION_NONE diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt index e353a53af752..18fa0bec000c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt @@ -96,6 +96,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { @Mock private lateinit var navBarController: NavigationBarController @Mock private lateinit var centralSurfaces: CentralSurfaces @Mock private lateinit var shadeViewController: ShadeViewController + @Mock private lateinit var screenPinningRequest: ScreenPinningRequest @Mock private lateinit var navModeController: NavigationModeController @Mock private lateinit var statusBarWinController: NotificationShadeWindowController @Mock private lateinit var userTracker: UserTracker @@ -136,6 +137,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { { navBarController }, { Optional.of(centralSurfaces) }, { shadeViewController }, + screenPinningRequest, navModeController, statusBarWinController, sysUiState, diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt index cfdf66ef2488..a105c15b630a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -180,7 +180,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { } @Test - fun executeScreenshots_doesNotReportFinishedIfOneFinishesOtherFails() = + fun executeScreenshots_oneFinishesOtherFails_reportFailsOnlyAtTheEnd() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) val onSaved = { _: Uri -> } @@ -207,7 +207,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { } @Test - fun executeScreenshots_doesNotReportFinishedAfterOneFails() = + fun executeScreenshots_allDisplaysFail_reportsFail() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) val onSaved = { _: Uri -> } @@ -224,11 +224,12 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { capturer0.value.reportError() verify(callback, never()).onFinish() - verify(callback).reportError() + verify(callback, never()).reportError() - capturer1.value.onFinish() + capturer1.value.reportError() verify(callback, never()).onFinish() + verify(callback).reportError() screenshotExecutor.onDestroy() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 4f57fd0d0a92..9130bc266d11 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -120,6 +120,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.QSFragment; import com.android.systemui.screenrecord.RecordingController; +import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.data.repository.ShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.shade.transition.ShadeTransitionController; @@ -172,6 +173,7 @@ import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.util.kotlin.JavaAdapter; @@ -320,7 +322,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mEmptySpaceClickListenerCaptor; @Mock protected ActivityStarter mActivityStarter; @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; - @Mock protected ShadeRepository mShadeRepository; @Mock private ShadeInteractor mShadeInteractor; @Mock private JavaAdapter mJavaAdapter; @Mock private CastController mCastController; @@ -341,6 +342,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected Handler mMainHandler; protected View.OnLayoutChangeListener mLayoutChangeListener; protected KeyguardStatusViewController mKeyguardStatusViewController; + protected ShadeRepository mShadeRepository; protected final FalsingManagerFake mFalsingManager = new FalsingManagerFake(); protected final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty(); @@ -362,6 +364,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mFakeKeyguardRepository = keyguardInteractorDeps.getRepository(); mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository); mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor(); + mShadeRepository = new FakeShadeRepository(); SystemClock systemClock = new FakeSystemClock(); mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, @@ -659,7 +662,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mSharedNotificationContainerInteractor, mKeyguardViewConfigurator, mKeyguardFaceAuthInteractor, - mKeyguardRootView); + new ResourcesSplitShadeStateController()); mNotificationPanelViewController.initDependencies( mCentralSurfaces, null, @@ -731,7 +734,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mShadeRepository, mShadeInteractor, mJavaAdapter, - mCastController + mCastController, + new ResourcesSplitShadeStateController() ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 7aeafeb2a752..6dadd4c3dca9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -197,6 +197,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test public void getVerticalSpaceForLockscreenShelf_useLockIconBottomPadding_returnsShelfHeight() { + enableSplitShade(/* enabled= */ false); setBottomPadding(/* stackScrollLayoutBottom= */ 100, /* lockIconPadding= */ 20, /* indicationPadding= */ 0, @@ -209,6 +210,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test public void getVerticalSpaceForLockscreenShelf_useIndicationBottomPadding_returnsZero() { + enableSplitShade(/* enabled= */ false); setBottomPadding(/* stackScrollLayoutBottom= */ 100, /* lockIconPadding= */ 0, /* indicationPadding= */ 30, @@ -221,6 +223,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test public void getVerticalSpaceForLockscreenShelf_useAmbientBottomPadding_returnsZero() { + enableSplitShade(/* enabled= */ false); setBottomPadding(/* stackScrollLayoutBottom= */ 100, /* lockIconPadding= */ 0, /* indicationPadding= */ 0, @@ -233,6 +236,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test public void getVerticalSpaceForLockscreenShelf_useLockIconPadding_returnsLessThanShelfHeight() { + enableSplitShade(/* enabled= */ false); setBottomPadding(/* stackScrollLayoutBottom= */ 100, /* lockIconPadding= */ 10, /* indicationPadding= */ 8, @@ -244,6 +248,19 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + public void getVerticalSpaceForLockscreenShelf_splitShade() { + enableSplitShade(/* enabled= */ true); + setBottomPadding(/* stackScrollLayoutBottom= */ 100, + /* lockIconPadding= */ 10, + /* indicationPadding= */ 8, + /* ambientPadding= */ 0); + + when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5); + assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) + .isEqualTo(0); + } + + @Test public void testSetPanelScrimMinFractionWhenHeadsUpIsDragged() { mNotificationPanelViewController.setHeadsUpDraggingStartingHeight( mNotificationPanelViewController.getMaxPanelHeight() / 2); @@ -866,37 +883,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test - public void testUnlockAnimationDoesNotAffectScrim() { - mNotificationPanelViewController.onUnlockHintStarted(); - verify(mScrimController).setExpansionAffectsAlpha(false); - mNotificationPanelViewController.onUnlockHintFinished(); - verify(mScrimController).setExpansionAffectsAlpha(true); - } - - @Test - public void testUnlockHintAnimation_runs_whenNotInPowerSaveMode_andDozeAmountIsZero() { - when(mPowerManager.isPowerSaveMode()).thenReturn(false); - when(mAmbientState.getDozeAmount()).thenReturn(0f); - mNotificationPanelViewController.startUnlockHintAnimation(); - assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isTrue(); - } - - @Test - public void testUnlockHintAnimation_doesNotRun_inPowerSaveMode() { - when(mPowerManager.isPowerSaveMode()).thenReturn(true); - mNotificationPanelViewController.startUnlockHintAnimation(); - assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isFalse(); - } - - @Test - public void testUnlockHintAnimation_doesNotRun_whenDozeAmountNotZero() { - when(mPowerManager.isPowerSaveMode()).thenReturn(false); - when(mAmbientState.getDozeAmount()).thenReturn(0.5f); - mNotificationPanelViewController.startUnlockHintAnimation(); - assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isFalse(); - } - - @Test public void setKeyguardStatusBarAlpha_setsAlphaOnKeyguardStatusBarController() { float statusBarAlpha = 0.5f; @@ -1056,36 +1042,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test - public void onEmptySpaceClicked_notDozingAndFaceDetectionIsNotRunning_startsUnlockAnimation() { - StatusBarStateController.StateListener statusBarStateListener = - mNotificationPanelViewController.getStatusBarStateListener(); - statusBarStateListener.onStateChanged(KEYGUARD); - mNotificationPanelViewController.setDozing(false, false); - when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(false); - - // This sets the dozing state that is read when onMiddleClicked is eventually invoked. - mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); - mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); - - verify(mNotificationStackScrollLayoutController).setUnlockHintRunning(true); - } - - @Test - public void onEmptySpaceClicked_notDozingAndFaceDetectionIsRunning_doesNotStartUnlockHint() { - StatusBarStateController.StateListener statusBarStateListener = - mNotificationPanelViewController.getStatusBarStateListener(); - statusBarStateListener.onStateChanged(KEYGUARD); - mNotificationPanelViewController.setDozing(false, false); - when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(true); - - // This sets the dozing state that is read when onMiddleClicked is eventually invoked. - mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); - mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); - - verify(mNotificationStackScrollLayoutController, never()).setUnlockHintRunning(true); - } - - @Test public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() { StatusBarStateController.StateListener statusBarStateListener = mNotificationPanelViewController.getStatusBarStateListener(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt index 577b6e0bc58a..36cf1d96b891 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt @@ -39,6 +39,7 @@ import com.android.systemui.plugins.qs.QS import com.android.systemui.recents.OverviewProxyService import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.whenever @@ -118,6 +119,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { delayableExecutor, featureFlags, notificationStackScrollLayoutController, + ResourcesSplitShadeStateController() ) overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN) @@ -474,6 +476,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { delayableExecutor, featureFlags, notificationStackScrollLayoutController, + ResourcesSplitShadeStateController() ) controller.updateConstraints() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt index 405199ed4ecb..090bee2c772e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt @@ -39,6 +39,7 @@ import com.android.systemui.plugins.qs.QS import com.android.systemui.recents.OverviewProxyService import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.whenever @@ -117,6 +118,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { delayableExecutor, featureFlags, notificationStackScrollLayoutController, + ResourcesSplitShadeStateController() ) overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN) @@ -457,6 +459,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { delayableExecutor, featureFlags, notificationStackScrollLayoutController, + ResourcesSplitShadeStateController() ) controller.updateConstraints() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index 46b636b264be..849127ed10e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -75,6 +75,7 @@ import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.user.domain.interactor.UserInteractor; import com.android.systemui.util.kotlin.JavaAdapter; @@ -182,7 +183,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { mUserInteractor, new SharedNotificationContainerInteractor( new FakeConfigurationRepository(), - mContext), + mContext, + new ResourcesSplitShadeStateController()), mShadeRepository ); @@ -260,7 +262,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { mShadeRepository, mShadeInteractor, new JavaAdapter(mTestScope.getBackgroundScope()), - mCastController + mCastController, + new ResourcesSplitShadeStateController() ); mQsController.init(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt index 8f8b840b03c9..7d5f68e783d5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt @@ -130,7 +130,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { var viewVisibility = View.GONE var viewAlpha = 1f - private val systemIcons = LinearLayout(context) + private val systemIconsHoverContainer = LinearLayout(context) private lateinit var shadeHeaderController: ShadeHeaderController private lateinit var carrierIconSlots: List<String> private val configurationController = FakeConfigurationController() @@ -150,7 +150,8 @@ class ShadeHeaderControllerTest : SysuiTestCase() { .thenReturn(batteryMeterView) whenever<StatusIconContainer>(view.requireViewById(R.id.statusIcons)).thenReturn(statusIcons) - whenever<View>(view.requireViewById(R.id.shade_header_system_icons)).thenReturn(systemIcons) + whenever<View>(view.requireViewById(R.id.hover_system_icons_container)) + .thenReturn(systemIconsHoverContainer) viewContext = Mockito.spy(context) whenever(view.context).thenReturn(viewContext) @@ -457,12 +458,12 @@ class ShadeHeaderControllerTest : SysuiTestCase() { } @Test - fun testLargeScreenActive_collapseActionRun_onSystemIconsClick() { + fun testLargeScreenActive_collapseActionRun_onSystemIconsHoverContainerClick() { shadeHeaderController.largeScreenActive = true var wasRun = false shadeHeaderController.shadeCollapseAction = Runnable { wasRun = true } - systemIcons.performClick() + systemIconsHoverContainer.performClick() assertThat(wasRun).isTrue() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index dd6ab73b7d11..d4b69fad6097 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFl import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor import com.android.systemui.user.data.model.UserSwitcherSettingsModel @@ -85,6 +86,7 @@ class ShadeInteractorTest : SysuiTestCase() { SharedNotificationContainerInteractor( configurationRepository, mContext, + ResourcesSplitShadeStateController() ) @Mock private lateinit var manager: UserManager @@ -400,6 +402,7 @@ class ShadeInteractorTest : SysuiTestCase() { assertThat(actual).isEqualTo(0.8f) } + @Test fun shadeExpansionWhenInSplitShadeAndQsExpanded() = testScope.runTest { val actual by collectLastValue(underTest.shadeExpansion) @@ -408,27 +411,31 @@ class ShadeInteractorTest : SysuiTestCase() { keyguardRepository.setStatusBarState(StatusBarState.SHADE) overrideResource(R.bool.config_use_split_notification_shade, true) configurationRepository.onAnyConfigurationChange() - runCurrent() shadeRepository.setQsExpansion(.5f) shadeRepository.setLegacyShadeExpansion(.7f) + runCurrent() // THEN legacy shade expansion is passed through assertThat(actual).isEqualTo(.7f) } + @Test fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() = testScope.runTest { val actual by collectLastValue(underTest.shadeExpansion) // WHEN split shade is not enabled and QS is expanded keyguardRepository.setStatusBarState(StatusBarState.SHADE) + overrideResource(R.bool.config_use_split_notification_shade, false) shadeRepository.setQsExpansion(.5f) shadeRepository.setLegacyShadeExpansion(1f) + runCurrent() // THEN shade expansion is zero assertThat(actual).isEqualTo(0f) } + @Test fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() = testScope.runTest { val actual by collectLastValue(underTest.shadeExpansion) @@ -469,7 +476,7 @@ class ShadeInteractorTest : SysuiTestCase() { @Test fun expanding_shadeDraggedDown_expandingTrue() = testScope.runTest() { - val actual by collectLastValue(underTest.anyExpanding) + val actual by collectLastValue(underTest.isAnyExpanding) // GIVEN shade and QS collapsed shadeRepository.setLegacyShadeExpansion(0f) @@ -487,7 +494,7 @@ class ShadeInteractorTest : SysuiTestCase() { @Test fun expanding_qsDraggedDown_expandingTrue() = testScope.runTest() { - val actual by collectLastValue(underTest.anyExpanding) + val actual by collectLastValue(underTest.isAnyExpanding) // GIVEN shade and QS collapsed shadeRepository.setLegacyShadeExpansion(0f) @@ -505,7 +512,7 @@ class ShadeInteractorTest : SysuiTestCase() { @Test fun expanding_shadeDraggedUpAndDown() = testScope.runTest() { - val actual by collectLastValue(underTest.anyExpanding) + val actual by collectLastValue(underTest.isAnyExpanding) // WHEN shade starts collapsed then partially expanded shadeRepository.setLegacyShadeExpansion(0f) @@ -530,7 +537,7 @@ class ShadeInteractorTest : SysuiTestCase() { // THEN anyExpanding is still true assertThat(actual).isTrue() - // WHEN shade fully shadeExpanded + // WHEN shade fully expanded shadeRepository.setLegacyShadeExpansion(1f) runCurrent() @@ -548,7 +555,7 @@ class ShadeInteractorTest : SysuiTestCase() { @Test fun expanding_shadeDraggedDownThenUp_expandingFalse() = testScope.runTest() { - val actual by collectLastValue(underTest.anyExpanding) + val actual by collectLastValue(underTest.isAnyExpanding) // GIVEN shade starts collapsed shadeRepository.setLegacyShadeExpansion(0f) @@ -706,4 +713,227 @@ class ShadeInteractorTest : SysuiTestCase() { // THEN expansion is still 0 assertThat(expansionAmount).isEqualTo(0f) } + + @Test + fun userInteractingWithShade_shadeDraggedUpAndDown() = + testScope.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade collapsed and not tracking input + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged down halfway + shadeRepository.setLegacyShadeExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully expanded but tracking is not stopped + shadeRepository.setLegacyShadeExpansion(1f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully collapsed but tracking is not stopped + shadeRepository.setLegacyShadeExpansion(0f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged halfway and tracking is stopped + shadeRepository.setLegacyShadeExpansion(.6f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade completes expansion stopped + shadeRepository.setLegacyShadeExpansion(1f) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithShade_shadeExpanded() = + testScope.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade collapsed and not tracking input + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged down halfway + shadeRepository.setLegacyShadeExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully expanded and tracking is stopped + shadeRepository.setLegacyShadeExpansion(1f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithShade_shadePartiallyExpanded() = + testScope.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade collapsed and not tracking input + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade partially expanded + shadeRepository.setLegacyShadeExpansion(.4f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN tracking is stopped + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade goes back to collapsed + shadeRepository.setLegacyShadeExpansion(0f) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithShade_shadeCollapsed() = + testScope.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade expanded and not tracking input + shadeRepository.setLegacyShadeExpansion(1f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged up halfway + shadeRepository.setLegacyShadeExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully collapsed and tracking is stopped + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithQs_qsDraggedUpAndDown() = + testScope.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithQs) + // GIVEN qs collapsed and not tracking input + shadeRepository.setQsExpansion(0f) + shadeRepository.setLegacyQsTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN qs tracking starts + shadeRepository.setLegacyQsTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs dragged down halfway + shadeRepository.setQsExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs fully expanded but tracking is not stopped + shadeRepository.setQsExpansion(1f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs fully collapsed but tracking is not stopped + shadeRepository.setQsExpansion(0f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs dragged halfway and tracking is stopped + shadeRepository.setQsExpansion(.6f) + shadeRepository.setLegacyQsTracking(false) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs completes expansion stopped + shadeRepository.setQsExpansion(1f) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt index e086712308b6..19d59fd4f1b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt @@ -151,6 +151,24 @@ class ShadeRepositoryImplTest : SysuiTestCase() { } @Test + fun updateLegacyShadeTracking() = + testScope.runTest { + assertThat(underTest.legacyShadeTracking.value).isEqualTo(false) + + underTest.setLegacyShadeTracking(true) + assertThat(underTest.legacyShadeTracking.value).isEqualTo(true) + } + + @Test + fun updateLegacyQsTracking() = + testScope.runTest { + assertThat(underTest.legacyQsTracking.value).isEqualTo(false) + + underTest.setLegacyQsTracking(true) + assertThat(underTest.legacyQsTracking.value).isEqualTo(true) + } + + @Test fun updateUdfpsTransitionToFullShadeProgress() = testScope.runTest { assertThat(underTest.udfpsTransitionToFullShadeProgress.value).isEqualTo(0f) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt index 8309342d2c60..36f82c2a0e1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt @@ -5,6 +5,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.google.common.truth.Expect import org.junit.Rule import org.junit.Test @@ -23,7 +24,8 @@ class LargeScreenShadeInterpolatorImplTest : SysuiTestCase() { configurationController, context, splitShadeInterpolator, - portraitShadeInterpolator + portraitShadeInterpolator, + ResourcesSplitShadeStateController() ) @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt index d5a1f804e6a0..7737b4313e3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt @@ -9,6 +9,7 @@ import com.android.systemui.shade.ShadeExpansionChangeEvent import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -41,6 +42,7 @@ class ShadeTransitionControllerTest : SysuiTestCase() { context, scrimShadeTransitionController, statusBarStateController, + ResourcesSplitShadeStateController() ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt index 70412626420d..673d63f3a07d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt @@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.qs.QS import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.google.common.truth.Expect import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -59,7 +60,8 @@ class LockscreenShadeQsTransitionControllerTest : SysuiTestCase() { context, configurationController, dumpManager, - qsProvider = { qS } + qsProvider = { qS }, + ResourcesSplitShadeStateController() ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 2446234bb022..2c9dfcc25324 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -39,6 +39,7 @@ import com.android.systemui.statusbar.phone.LSShadeTransitionLogger import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.util.mockito.mock import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent @@ -110,6 +111,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { private val sharedNotificationContainerInteractor = SharedNotificationContainerInteractor( configurationRepository, mContext, + ResourcesSplitShadeStateController() ) private val shadeInteractor = ShadeInteractor( @@ -172,7 +174,8 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { scrimController, context, configurationController, - dumpManager + dumpManager, + ResourcesSplitShadeStateController() ), keyguardTransitionControllerFactory = { notificationPanelController -> LockscreenShadeKeyguardTransitionController( @@ -180,7 +183,8 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { notificationPanelController, context, configurationController, - dumpManager + dumpManager, + ResourcesSplitShadeStateController() ) }, qsTransitionControllerFactory = { qsTransitionController }, @@ -188,6 +192,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { shadeRepository = FakeShadeRepository(), shadeInteractor = shadeInteractor, powerInteractor = powerInteractor, + splitShadeStateController = ResourcesSplitShadeStateController() ) transitionController.addCallback(transitionControllerCallback) whenever(nsslController.view).thenReturn(stackscroller) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index c49f179c925e..a258f67932bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.WallpaperController import com.android.systemui.util.mockito.eq @@ -114,8 +115,9 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { notificationShadeWindowController, dozeParameters, context, + ResourcesSplitShadeStateController(), dumpManager, - configurationController) + configurationController,) notificationShadeDepthController.shadeAnimation = shadeAnimation notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring notificationShadeDepthController.root = root diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt index 580463a3010f..88ddc2d9ef03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.statusbar.disableflags.DisableFlagsLogger import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock @@ -56,6 +57,7 @@ class DisableFlagsRepositoryTest : SysuiTestCase() { RemoteInputQuickSettingsDisabler( context, commandQueue, + ResourcesSplitShadeStateController(), configurationController, ) private val logBuffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt new file mode 100644 index 000000000000..c650e01263bc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.statusbar.notification.row + +import android.graphics.BitmapFactory +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon +import android.net.Uri +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.internal.widget.NotificationDrawableConsumer +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.graphics.ImageLoader +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions + +private const val FREE_IMAGE_DELAY_MS = 4000L + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidTestingRunner::class) +class BigPictureIconManagerTest : SysuiTestCase() { + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + private val testableResources = context.orCreateTestableResources + private val imageLoader: ImageLoader = ImageLoader(context, testDispatcher) + private var mockConsumer: NotificationDrawableConsumer = mock() + private val drawableCaptor = argumentCaptor<Drawable>() + + private lateinit var iconManager: BigPictureIconManager + + private val expectedDrawable by lazy { + context.resources.getDrawable(R.drawable.dessert_zombiegingerbread, context.theme) + } + private val supportedIcon by lazy { + Icon.createWithContentUri( + Uri.parse( + "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}" + ) + ) + } + private val unsupportedIcon by lazy { + Icon.createWithBitmap( + BitmapFactory.decodeResource(context.resources, R.drawable.dessert_zombiegingerbread) + ) + } + private val invalidIcon by lazy { Icon.createWithContentUri(Uri.parse("this.is/broken")) } + + @Before + fun setUp() { + iconManager = + BigPictureIconManager( + context, + imageLoader, + scope = testScope, + mainDispatcher = testDispatcher, + bgDispatcher = testDispatcher + ) + } + + @Test + fun onIconUpdated_supportedType_placeholderLoaded() = + testScope.runTest { + // WHEN update with a supported icon + iconManager.updateIcon(mockConsumer, supportedIcon).run() + + // THEN consumer is updated with a placeholder + verify(mockConsumer).setImageDrawable(drawableCaptor.capture()) + assertIsPlaceHolder(drawableCaptor.value) + assertSize(drawableCaptor.value) + } + + @Test + fun onIconUpdated_notSupportedType_fullImageLoaded() = + testScope.runTest { + // WHEN update with an unsupported icon + iconManager.updateIcon(mockConsumer, unsupportedIcon).run() + + // THEN consumer is updated with the full image + verify(mockConsumer).setImageDrawable(drawableCaptor.capture()) + assertIsFullImage(drawableCaptor.value) + assertSize(drawableCaptor.value) + } + + @Test + fun onIconUpdated_invalidIcon_drawableIsNull() = + testScope.runTest { + // WHEN update with an invalid icon + iconManager.updateIcon(mockConsumer, invalidIcon).run() + + // THEN consumer is updated with null + verify(mockConsumer).setImageDrawable(null) + } + + @Test + fun onIconUpdated_consumerAlreadySet_nothingHappens() = + testScope.runTest { + // GIVEN a consumer is set + val otherConsumer: NotificationDrawableConsumer = mock() + iconManager.updateIcon(mockConsumer, supportedIcon).run() + reset(mockConsumer) + + // WHEN a new consumer is set + iconManager.updateIcon(otherConsumer, unsupportedIcon).run() + + // THEN nothing happens + verifyZeroInteractions(mockConsumer, otherConsumer) + } + + @Test + fun onIconUpdated_iconAlreadySet_loadsNewIcon() = + testScope.runTest { + // GIVEN an icon is set + iconManager.updateIcon(mockConsumer, supportedIcon).run() + reset(mockConsumer) + + // WHEN a new icon is set + iconManager.updateIcon(mockConsumer, unsupportedIcon).run() + + // THEN consumer is updated with the new image + verify(mockConsumer).setImageDrawable(drawableCaptor.capture()) + assertIsFullImage(drawableCaptor.value) + assertSize(drawableCaptor.value) + } + + @Test + fun onIconUpdated_supportedTypeButTooWide_resizedPlaceholderLoaded() = + testScope.runTest { + // GIVEN the max width is smaller than our image + testableResources.addOverride( + com.android.internal.R.dimen.notification_big_picture_max_width, + 20 + ) + iconManager.updateMaxImageSizes() + + // WHEN update with a supported icon + iconManager.updateIcon(mockConsumer, supportedIcon).run() + + // THEN consumer is updated with the resized placeholder + verify(mockConsumer).setImageDrawable(drawableCaptor.capture()) + assertIsPlaceHolder(drawableCaptor.value) + assertSize(drawableCaptor.value, expectedWidth = 20, expectedHeight = 20) + } + + @Test + fun onIconUpdated_supportedTypeButTooHigh_resizedPlaceholderLoaded() = + testScope.runTest { + // GIVEN the max height is smaller than our image + testableResources.addOverride( + com.android.internal.R.dimen.notification_big_picture_max_height, + 20 + ) + iconManager.updateMaxImageSizes() + + // WHEN update with a supported icon + iconManager.updateIcon(mockConsumer, supportedIcon).run() + + // THEN consumer is updated with the resized placeholder + verify(mockConsumer).setImageDrawable(drawableCaptor.capture()) + assertIsPlaceHolder(drawableCaptor.value) + assertSize(drawableCaptor.value, expectedWidth = 20, expectedHeight = 20) + } + + @Test + fun onViewShown_placeholderShowing_fullImageLoaded() = + testScope.runTest { + // GIVEN placeholder is showing + iconManager.updateIcon(mockConsumer, supportedIcon).run() + reset(mockConsumer) + + // WHEN the view is shown + iconManager.onViewShown(true) + runCurrent() + + // THEN full image is set + verify(mockConsumer).setImageDrawable(drawableCaptor.capture()) + assertIsFullImage(drawableCaptor.value) + assertSize(drawableCaptor.value) + } + + @Test + fun onViewHidden_fullImageShowing_placeHolderSet() = + testScope.runTest { + // GIVEN full image is showing and the view is shown + iconManager.updateIcon(mockConsumer, supportedIcon).run() + iconManager.onViewShown(true) + runCurrent() + reset(mockConsumer) + + // WHEN the view goes off the screen + iconManager.onViewShown(false) + // AND we wait a bit + advanceTimeBy(FREE_IMAGE_DELAY_MS) + runCurrent() + + // THEN placeholder is set + verify(mockConsumer).setImageDrawable(drawableCaptor.capture()) + assertIsPlaceHolder(drawableCaptor.value) + assertSize(drawableCaptor.value) + } + + @Test + fun onViewShownToggled_viewShown_nothingHappens() = + testScope.runTest { + // GIVEN full image is showing and the view is shown + iconManager.updateIcon(mockConsumer, supportedIcon).run() + iconManager.onViewShown(true) + runCurrent() + reset(mockConsumer) + + // WHEN the onViewShown is toggled + iconManager.onViewShown(false) + runCurrent() + iconManager.onViewShown(true) + // AND we wait a bit + advanceTimeBy(FREE_IMAGE_DELAY_MS) + runCurrent() + + // THEN nothing happens + verifyZeroInteractions(mockConsumer) + } + + // nice to have tests + + @Test + fun onViewShown_fullImageLoaded_nothingHappens() = + testScope.runTest { + // GIVEN full image is showing + iconManager.updateIcon(mockConsumer, unsupportedIcon).run() + reset(mockConsumer) + + // WHEN the view is shown + iconManager.onViewShown(true) + runCurrent() + + // THEN nothing happens + verifyZeroInteractions(mockConsumer) + } + + @Test + fun onViewHidden_placeholderShowing_nothingHappens() = + testScope.runTest { + // GIVEN placeholder image is showing + iconManager.updateIcon(mockConsumer, unsupportedIcon).run() + reset(mockConsumer) + + // WHEN the view is hidden + iconManager.onViewShown(false) + // AND we wait a bit + advanceTimeBy(FREE_IMAGE_DELAY_MS) + runCurrent() + + // THEN nothing happens + verifyZeroInteractions(mockConsumer) + } + + @Test + fun onViewShown_alreadyShowing_nothingHappens() = + testScope.runTest { + // GIVEN full image is showing and the view is shown + iconManager.updateIcon(mockConsumer, supportedIcon).run() + iconManager.onViewShown(true) + runCurrent() + reset(mockConsumer) + + // WHEN view shown called again + iconManager.onViewShown(true) + runCurrent() + + verifyZeroInteractions(mockConsumer) + } + + @Test + fun onViewHidden_alreadyHidden_nothingHappens() = + testScope.runTest { + // GIVEN placeholder image is showing and the view is hidden + iconManager.updateIcon(mockConsumer, unsupportedIcon).run() + iconManager.onViewShown(false) + advanceTimeBy(FREE_IMAGE_DELAY_MS) + runCurrent() + reset(mockConsumer) + + // WHEN the view is hidden again + iconManager.onViewShown(false) + // AND we wait a bit + advanceTimeBy(FREE_IMAGE_DELAY_MS) + runCurrent() + + // THEN nothing happens + verifyZeroInteractions(mockConsumer) + } + + @Test + fun cancelJobs_freeImageJobRunning_jobCancelled() = + testScope.runTest { + // GIVEN full image is showing + iconManager.updateIcon(mockConsumer, supportedIcon).run() + iconManager.onViewShown(true) + runCurrent() + reset(mockConsumer) + // AND the view has just gone off the screen + iconManager.onViewShown(false) + + // WHEN cancelJobs is called + iconManager.cancelJobs() + // AND we wait a bit + advanceTimeBy(FREE_IMAGE_DELAY_MS) + runCurrent() + + // THEN no more updates are happening + verifyZeroInteractions(mockConsumer) + } + + private fun assertIsPlaceHolder(drawable: Drawable) { + assertThat(drawable).isInstanceOf(PlaceHolderDrawable::class.java) + } + + private fun assertIsFullImage(drawable: Drawable) { + assertThat(drawable).isInstanceOf(BitmapDrawable::class.java) + } + + private fun assertSize( + drawable: Drawable, + expectedWidth: Int = expectedDrawable.intrinsicWidth, + expectedHeight: Int = expectedDrawable.intrinsicHeight + ) { + assertThat(drawable.intrinsicWidth).isEqualTo(expectedWidth) + assertThat(drawable.intrinsicHeight).isEqualTo(expectedHeight) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 79cf932a1880..b3f5ea26f9f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -95,6 +95,7 @@ import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.settings.SecureSettings; @@ -713,7 +714,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mNotificationTargetsHelper, mSecureSettings, mock(NotificationDismissibilityProvider.class), - mActivityStarter + mActivityStarter, + new ResourcesSplitShadeStateController() ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 72fcdec3c44c..4307066e2d44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -87,6 +87,7 @@ import com.android.systemui.statusbar.notification.row.FooterView; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import org.junit.Assert; import org.junit.Before; @@ -322,26 +323,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test - public void setUnlockHintRunning_updatesStackEndHeightOnlyOnFinish() { - final float expansionFraction = 0.5f; - mAmbientState.setStatusBarState(StatusBarState.KEYGUARD); - mStackScroller.setUnlockHintRunning(true); - - // Validate that when the animation is running, we update only the stackHeight - clearInvocations(mAmbientState); - mStackScroller.updateStackEndHeightAndStackHeight(expansionFraction); - verify(mAmbientState, never()).setStackEndHeight(anyFloat()); - verify(mAmbientState).setStackHeight(anyFloat()); - - // Validate that when the animation ends the stackEndHeight is recalculated immediately - clearInvocations(mAmbientState); - mStackScroller.setUnlockHintRunning(false); - verify(mAmbientState).setUnlockHintRunning(eq(false)); - verify(mAmbientState).setStackEndHeight(anyFloat()); - verify(mAmbientState).setStackHeight(anyFloat()); - } - - @Test public void testNotDimmedOnKeyguard() { when(mBarState.getState()).thenReturn(StatusBarState.SHADE); mStackScroller.setDimmed(true /* dimmed */, false /* animate */); @@ -865,6 +846,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { public void testSplitShade_hasTopOverscroll() { mTestableResources .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true); + mStackScroller.passSplitShadeStateController(new ResourcesSplitShadeStateController()); mStackScroller.updateSplitNotificationShade(); mAmbientState.setExpansionFraction(1f); @@ -880,6 +862,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { public void testNormalShade_hasNoTopOverscroll() { mTestableResources .addOverride(R.bool.config_use_split_notification_shade, /* value= */ false); + mStackScroller.passSplitShadeStateController(new ResourcesSplitShadeStateController()); mStackScroller.updateSplitNotificationShade(); mAmbientState.setExpansionFraction(1f); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt index 5279740a8702..bc12bb0fca4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.nullable @@ -70,7 +71,8 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { statusBarStateController = sysuiStatusBarStateController, lockscreenShadeTransitionController = lockscreenShadeTransitionController, mediaDataManager = mediaDataManager, - testableResources.resources + testableResources.resources, + ResourcesSplitShadeStateController() ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt index 7bbb09483b5f..7fc399b44795 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt @@ -23,6 +23,7 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -43,6 +44,7 @@ class SharedNotificationContainerInteractorTest : SysuiTestCase() { SharedNotificationContainerInteractor( configurationRepository, mContext, + ResourcesSplitShadeStateController() ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 521069f15569..bfc09103ba41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCa import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -106,6 +107,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { SharedNotificationContainerInteractor( configurationRepository, mContext, + ResourcesSplitShadeStateController() ) shadeInteractor = ShadeInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index 3ad3c15f158a..c71c0c579e3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -50,6 +50,7 @@ import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.QSHost; +import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.QuickSettingsController; @@ -79,6 +80,7 @@ import java.util.Optional; public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Mock private CentralSurfaces mCentralSurfaces; + @Mock private ScreenPinningRequest mScreenPinningRequest; @Mock private ShadeController mShadeController; @Mock private CommandQueue mCommandQueue; @Mock private QuickSettingsController mQuickSettingsController; @@ -116,6 +118,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { mQuickSettingsController, mContext, mContext.getResources(), + mScreenPinningRequest, mShadeController, mCommandQueue, mShadeViewController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index bd3fb9f1cae3..6b944aed9368 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -116,7 +116,6 @@ import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; -import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; @@ -285,7 +284,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private PluginManager mPluginManager; @Mock private ViewMediatorCallback mViewMediatorCallback; @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; - @Mock private ScreenPinningRequest mScreenPinningRequest; @Mock private PluginDependencyProvider mPluginDependencyProvider; @Mock private ExtensionController mExtensionController; @Mock private UserInfoControllerImpl mUserInfoControllerImpl; @@ -508,7 +506,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mDozeServiceHost, mBackActionInteractor, mPowerManager, - mScreenPinningRequest, mDozeScrimController, mVolumeComponent, mCommandQueue, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index d100c687d802..79f8dbd687ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -30,6 +30,8 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -51,6 +53,8 @@ import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.battery.BatteryMeterViewController; +import com.android.systemui.flags.FakeFeatureFlagsClassic; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeViewStateProvider; import com.android.systemui.statusbar.CommandQueue; @@ -79,6 +83,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { + private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic(); @Mock private CarrierTextController mCarrierTextController; @Mock @@ -131,6 +136,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { @Before public void setup() throws Exception { + mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, false); mShadeViewStateProvider = new TestShadeViewStateProvider(); MockitoAnnotations.initMocks(this); @@ -166,6 +172,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mBiometricUnlockController, mStatusBarStateController, mStatusBarContentInsetsProvider, + mFeatureFlags, mUserManager, mStatusBarUserChipViewModel, mSecureSettings, @@ -228,6 +235,30 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { } @Test + public void onViewReAttached_flagOff_iconManagerNotReRegistered() { + mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, false); + mController.onViewAttached(); + mController.onViewDetached(); + reset(mStatusBarIconController); + + mController.onViewAttached(); + + verify(mStatusBarIconController, never()).addIconGroup(any()); + } + + @Test + public void onViewReAttached_flagOn_iconManagerReRegistered() { + mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, true); + mController.onViewAttached(); + mController.onViewDetached(); + reset(mStatusBarIconController); + + mController.onViewAttached(); + + verify(mStatusBarIconController).addIconGroup(any()); + } + + @Test public void setBatteryListening_true_callbackAdded() { mController.setBatteryListening(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 798c3f95b47b..bac857910329 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -268,7 +268,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Test public void onPanelExpansionChanged_neverShowsDuringHintAnimation() { - when(mShadeViewController.isUnlockHintRunning()).thenReturn(true); mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT); verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat()); } @@ -977,4 +976,23 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { verify(mKeyguardMessageAreaController).setIsVisible(eq(false)); verify(mKeyguardMessageAreaController).setMessage(eq("")); } + + @Test + public void testShowBouncerOrKeyguard_needsFullScreen() { + when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( + KeyguardSecurityModel.SecurityMode.SimPin); + mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false); + verify(mCentralSurfaces).hideKeyguard(); + verify(mPrimaryBouncerInteractor).show(true); + } + + @Test + public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() { + when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( + KeyguardSecurityModel.SecurityMode.SimPin); + when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true); + mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false); + verify(mCentralSurfaces, never()).hideKeyguard(); + verify(mPrimaryBouncerInteractor, never()).show(true); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt index a0d4d1390b2c..bbf048d6697f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt @@ -22,6 +22,7 @@ import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.testing.ViewUtils import android.view.View +import android.view.ViewGroup import android.widget.ImageView import androidx.test.filters.SmallTest import com.android.systemui.R @@ -138,7 +139,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { ViewUtils.attachView(view) testableLooper.processAllMessages() - assertThat(view.getIconGroupView().visibility).isEqualTo(View.GONE) + assertThat(view.getIconGroupView().visibility).isEqualTo(View.INVISIBLE) assertThat(view.getDotView().visibility).isEqualTo(View.VISIBLE) ViewUtils.detachView(view) @@ -153,8 +154,36 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { ViewUtils.attachView(view) testableLooper.processAllMessages() - assertThat(view.getIconGroupView().visibility).isEqualTo(View.GONE) - assertThat(view.getDotView().visibility).isEqualTo(View.GONE) + assertThat(view.getIconGroupView().visibility).isEqualTo(View.INVISIBLE) + assertThat(view.getDotView().visibility).isEqualTo(View.INVISIBLE) + + ViewUtils.detachView(view) + } + + /* Regression test for b/296864006. When STATE_HIDDEN we need to ensure the wifi view width + * would not break the StatusIconContainer translation calculation. */ + @Test + fun setVisibleState_hidden_keepWidth() { + val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) + + view.setVisibleState(STATE_ICON, /* animate= */ false) + + // get the view width when it's in visible state + ViewUtils.attachView(view) + val lp = view.layoutParams + lp.width = ViewGroup.LayoutParams.WRAP_CONTENT + lp.height = ViewGroup.LayoutParams.WRAP_CONTENT + view.layoutParams = lp + testableLooper.processAllMessages() + val currentWidth = view.width + + view.setVisibleState(STATE_HIDDEN, /* animate= */ false) + testableLooper.processAllMessages() + + // the view width when STATE_HIDDEN should be at least the width when STATE_ICON. Because + // when STATE_HIDDEN the invisible dot view width might be larger than group view width, + // then the wifi view width would be enlarged. + assertThat(view.width).isAtLeast(currentWidth) ViewUtils.detachView(view) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java index cdeb59278851..58d93c98f015 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java @@ -17,12 +17,10 @@ package com.android.systemui.statusbar.policy; import static android.os.BatteryManager.EXTRA_PRESENT; - import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker; import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS; - import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -91,6 +89,7 @@ public class BatteryControllerTest extends SysuiTestCase { mBroadcastDispatcher, mDemoModeController, mock(DumpManager.class), + mock(BatteryControllerLogger.class), new Handler(), new Handler()); // Can throw if updateEstimate is called on the main thread diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt index 1ab0582a6c5b..cfb48a8f6995 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt @@ -49,7 +49,9 @@ class RemoteInputQuickSettingsDisablerTest : SysuiTestCase() { remoteInputQuickSettingsDisabler = RemoteInputQuickSettingsDisabler( mContext, - commandQueue, Mockito.mock(ConfigurationController::class.java) + commandQueue, + ResourcesSplitShadeStateController(), + Mockito.mock(ConfigurationController::class.java), ) configuration = Configuration(mContext.resources.configuration) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java index 7f3d4b7f9f76..66c5aaa3ed07 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java @@ -132,12 +132,6 @@ public class ZenModeControllerImplTest extends SysuiTestCase { } @Test - public void testAddNullCallback() { - mController.addCallback(null); - mController.fireConfigChanged(null); - } - - @Test public void testModeChange() { List<Integer> states = List.of( Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, 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 65cac6efad1c..3152fd14bf48 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -44,6 +44,7 @@ import android.media.AudioManager; import android.os.SystemClock; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Log; import android.view.Gravity; import android.view.InputDevice; import android.view.MotionEvent; @@ -98,6 +99,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { private ConfigurationController mConfigurationController; private int mOriginalOrientation; + private static final String TAG = "VolumeDialogImplTest"; + @Mock VolumeDialogController mVolumeDialogController; @Mock @@ -674,12 +677,20 @@ public class VolumeDialogImplTest extends SysuiTestCase { @After public void teardown() { + // Detailed logs to track down timeout issues in b/299491332 + Log.d(TAG, "teardown: entered"); setOrientation(mOriginalOrientation); + Log.d(TAG, "teardown: after setOrientation"); mAnimatorTestRule.advanceTimeBy(mLongestHideShowAnimationDuration); + Log.d(TAG, "teardown: after advanceTimeBy"); mTestableLooper.moveTimeForward(mLongestHideShowAnimationDuration); + Log.d(TAG, "teardown: after moveTimeForward"); mTestableLooper.processAllMessages(); + Log.d(TAG, "teardown: after processAllMessages"); reset(mPostureController); + Log.d(TAG, "teardown: after reset"); cleanUp(mDialog); + Log.d(TAG, "teardown: after cleanUp"); } private void cleanUp(VolumeDialogImpl dialog) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeRearDisplayStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt index fd9139165c85..60291eece70b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeRearDisplayStateRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt @@ -17,15 +17,23 @@ package com.android.systemui.biometrics.data.repository +import com.android.systemui.biometrics.shared.model.DisplayRotation import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -class FakeRearDisplayStateRepository : RearDisplayStateRepository { +class FakeDisplayStateRepository : DisplayStateRepository { private val _isInRearDisplayMode = MutableStateFlow<Boolean>(false) override val isInRearDisplayMode: StateFlow<Boolean> = _isInRearDisplayMode.asStateFlow() + private val _currentRotation = MutableStateFlow<DisplayRotation>(DisplayRotation.ROTATION_0) + override val currentRotation: StateFlow<DisplayRotation> = _currentRotation.asStateFlow() + fun setIsInRearDisplayMode(isInRearDisplayMode: Boolean) { _isInRearDisplayMode.value = isInRearDisplayMode } + + fun setCurrentRotation(currentRotation: DisplayRotation) { + _currentRotation.value = currentRotation + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt index 2b13dcabb640..322fb284dad7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt @@ -56,20 +56,7 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository { _isLockedOut.value = isLockedOut } - private val faceAuthPaused = MutableStateFlow(false) - override fun pauseFaceAuth() { - faceAuthPaused.value = true - } - - override fun resumeFaceAuth() { - faceAuthPaused.value = false - } - - fun isFaceAuthPaused(): Boolean { - return faceAuthPaused.value - } - - override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { + override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { _runningAuthRequest.value = uiEvent to fallbackToDetection _isAuthRunning.value = true } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt index 08152a3c49cd..e72544a24740 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt @@ -40,6 +40,23 @@ class FakeShadeRepository : ShadeRepository { @Deprecated("Use ShadeInteractor instead") override val legacyShadeExpansion = _legacyShadeExpansion + private val _legacyShadeTracking = MutableStateFlow(false) + @Deprecated("Use ShadeInteractor instead") + override val legacyShadeTracking = _legacyShadeTracking + + private val _legacyQsTracking = MutableStateFlow(false) + @Deprecated("Use ShadeInteractor instead") override val legacyQsTracking = _legacyQsTracking + + @Deprecated("Should only be called by NPVC and tests") + override fun setLegacyQsTracking(legacyQsTracking: Boolean) { + _legacyQsTracking.value = legacyQsTracking + } + + @Deprecated("Should only be called by NPVC and tests") + override fun setLegacyShadeTracking(tracking: Boolean) { + _legacyShadeTracking.value = tracking + } + fun setShadeModel(model: ShadeModel) { _shadeModel.value = model } diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 05b6eb4a64c1..44ffb51452e1 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -56,7 +56,6 @@ import android.graphics.ParcelableColorSpace; import android.graphics.Region; import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; -import android.hardware.display.DisplayManagerInternal; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -98,7 +97,6 @@ import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.function.pooled.PooledLambda; -import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection; import com.android.server.accessibility.magnification.MagnificationProcessor; import com.android.server.inputmethod.InputMethodManagerInternal; @@ -1441,19 +1439,24 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, callback); return; } - final long identity = Binder.clearCallingIdentity(); try { - mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { - final ScreenshotHardwareBuffer screenshotBuffer = LocalServices - .getService(DisplayManagerInternal.class).userScreenshot(displayId); - if (screenshotBuffer != null) { - sendScreenshotSuccess(screenshotBuffer, callback); - } else { - sendScreenshotFailure( - AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, callback); - } - }, null).recycleOnUse()); + ScreenCapture.ScreenCaptureListener screenCaptureListener = new + ScreenCapture.ScreenCaptureListener( + (screenshotBuffer, result) -> { + if (screenshotBuffer != null && result == 0) { + sendScreenshotSuccess(screenshotBuffer, callback); + } else { + sendScreenshotFailure( + AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, + callback); + } + } + ); + mWindowManagerService.captureDisplay(displayId, null, screenCaptureListener); + } catch (Exception e) { + sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, + callback); } finally { Binder.restoreCallingIdentity(identity); } @@ -1461,22 +1464,24 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ private void sendScreenshotSuccess(ScreenshotHardwareBuffer screenshotBuffer, RemoteCallback callback) { - final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer(); - final ParcelableColorSpace colorSpace = - new ParcelableColorSpace(screenshotBuffer.getColorSpace()); - - final Bundle payload = new Bundle(); - payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS, - AccessibilityService.TAKE_SCREENSHOT_SUCCESS); - payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, - hardwareBuffer); - payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace); - payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP, - SystemClock.uptimeMillis()); - - // Send back the result. - callback.sendResult(payload); - hardwareBuffer.close(); + mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { + final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer(); + final ParcelableColorSpace colorSpace = + new ParcelableColorSpace(screenshotBuffer.getColorSpace()); + + final Bundle payload = new Bundle(); + payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS, + AccessibilityService.TAKE_SCREENSHOT_SUCCESS); + payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, + hardwareBuffer); + payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace); + payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP, + SystemClock.uptimeMillis()); + + // Send back the result. + callback.sendResult(payload); + hardwareBuffer.close(); + }, null).recycleOnUse()); } private void sendScreenshotFailure(@AccessibilityService.ScreenshotErrorCode int errorCode, diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig index 1e44de61bdd7..92e00ee0a477 100644 --- a/services/autofill/features.aconfig +++ b/services/autofill/features.aconfig @@ -1 +1,8 @@ -package: "android.service.autofill"
\ No newline at end of file +package: "android.service.autofill" + +flag { + name: "autofill_credman_integration" + namespace: "autofill" + description: "Guards Autofill Framework against Autofill-Credman integration" + bug: "296907283" +}
\ No newline at end of file diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 330e35b5aeca..ba4533960db4 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -887,6 +887,11 @@ public class CompanionDeviceManagerService extends SystemService { @Override public PermissionSyncRequest getPermissionSyncRequest(int associationId) { + // TODO: temporary fix, will remove soon + AssociationInfo association = mAssociationStore.getAssociationById(associationId); + if (association == null) { + return null; + } getAssociationWithCallerChecks(associationId); return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId); } diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java index 42b5a8ba8a4d..e5c847acad6e 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java @@ -139,7 +139,7 @@ public class SystemDataTransferProcessor { @UserIdInt int userId, int associationId) { if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) { Slog.i(LOG_TAG, "User consent Intent should be skipped. Returning null."); - // Auto enable perm sync for the whitelisted packages, but don't override user decision + // Auto enable perm sync for the allowlisted packages, but don't override user decision PermissionSyncRequest request = getPermissionSyncRequest(associationId); if (request == null) { PermissionSyncRequest newRequest = new PermissionSyncRequest(associationId); @@ -224,20 +224,30 @@ public class SystemDataTransferProcessor { * Enable perm sync for the association */ public void enablePermissionsSync(int associationId) { - int userId = mAssociationStore.getAssociationById(associationId).getUserId(); - PermissionSyncRequest request = new PermissionSyncRequest(associationId); - request.setUserConsented(true); - mSystemDataTransferRequestStore.writeRequest(userId, request); + final long callingIdentityToken = Binder.clearCallingIdentity(); + try { + int userId = mAssociationStore.getAssociationById(associationId).getUserId(); + PermissionSyncRequest request = new PermissionSyncRequest(associationId); + request.setUserConsented(true); + mSystemDataTransferRequestStore.writeRequest(userId, request); + } finally { + Binder.restoreCallingIdentity(callingIdentityToken); + } } /** * Disable perm sync for the association */ public void disablePermissionsSync(int associationId) { - int userId = mAssociationStore.getAssociationById(associationId).getUserId(); - PermissionSyncRequest request = new PermissionSyncRequest(associationId); - request.setUserConsented(false); - mSystemDataTransferRequestStore.writeRequest(userId, request); + final long callingIdentityToken = Binder.clearCallingIdentity(); + try { + int userId = mAssociationStore.getAssociationById(associationId).getUserId(); + PermissionSyncRequest request = new PermissionSyncRequest(associationId); + request.setUserConsented(false); + mSystemDataTransferRequestStore.writeRequest(userId, request); + } finally { + Binder.restoreCallingIdentity(callingIdentityToken); + } } /** @@ -245,23 +255,34 @@ public class SystemDataTransferProcessor { */ @Nullable public PermissionSyncRequest getPermissionSyncRequest(int associationId) { - int userId = mAssociationStore.getAssociationById(associationId).getUserId(); - List<SystemDataTransferRequest> requests = - mSystemDataTransferRequestStore.readRequestsByAssociationId(userId, associationId); - for (SystemDataTransferRequest request : requests) { - if (request instanceof PermissionSyncRequest) { - return (PermissionSyncRequest) request; + final long callingIdentityToken = Binder.clearCallingIdentity(); + try { + int userId = mAssociationStore.getAssociationById(associationId).getUserId(); + List<SystemDataTransferRequest> requests = + mSystemDataTransferRequestStore.readRequestsByAssociationId(userId, + associationId); + for (SystemDataTransferRequest request : requests) { + if (request instanceof PermissionSyncRequest) { + return (PermissionSyncRequest) request; + } } + return null; + } finally { + Binder.restoreCallingIdentity(callingIdentityToken); } - return null; } /** * Remove perm sync request for the association. */ public void removePermissionSyncRequest(int associationId) { - int userId = mAssociationStore.getAssociationById(associationId).getUserId(); - mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId); + final long callingIdentityToken = Binder.clearCallingIdentity(); + try { + int userId = mAssociationStore.getAssociationById(associationId).getUserId(); + mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId); + } finally { + Binder.restoreCallingIdentity(callingIdentityToken); + } } private void onReceivePermissionRestore(byte[] message) { diff --git a/services/core/java/com/android/server/SmartStorageMaintIdler.java b/services/core/java/com/android/server/SmartStorageMaintIdler.java index 899692611086..44f1e76f7a22 100644 --- a/services/core/java/com/android/server/SmartStorageMaintIdler.java +++ b/services/core/java/com/android/server/SmartStorageMaintIdler.java @@ -25,6 +25,7 @@ import android.content.Context; import android.util.Slog; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; public class SmartStorageMaintIdler extends JobService { private static final String TAG = "SmartStorageMaintIdler"; @@ -34,15 +35,15 @@ public class SmartStorageMaintIdler extends JobService { private static final int SMART_MAINT_JOB_ID = 2808; - private boolean mStarted; + private final AtomicBoolean mStarted = new AtomicBoolean(false); private JobParameters mJobParams; private final Runnable mFinishCallback = new Runnable() { @Override public void run() { Slog.i(TAG, "Got smart storage maintenance service completion callback"); - if (mStarted) { + if (mStarted.get()) { jobFinished(mJobParams, false); - mStarted = false; + mStarted.set(false); } // ... and try again in a next period scheduleSmartIdlePass(SmartStorageMaintIdler.this, @@ -52,18 +53,26 @@ public class SmartStorageMaintIdler extends JobService { @Override public boolean onStartJob(JobParameters params) { - mJobParams = params; - StorageManagerService ms = StorageManagerService.sSelf; - if (ms != null) { - mStarted = true; - ms.runSmartIdleMaint(mFinishCallback); + final StorageManagerService ms = StorageManagerService.sSelf; + if (mStarted.compareAndSet(false, true)) { + new Thread() { + public void run() { + mJobParams = params; + if (ms != null) { + ms.runSmartIdleMaint(mFinishCallback); + } else { + mStarted.set(false); + } + } + }.start(); + return ms != null; } - return ms != null; + return false; } @Override public boolean onStopJob(JobParameters params) { - mStarted = false; + mStarted.set(false); return false; } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index a78764456d8b..25ca509cb949 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -2842,7 +2842,7 @@ class StorageManagerService extends IStorageManager.Stub return true; } - void runSmartIdleMaint(Runnable callback) { + synchronized void runSmartIdleMaint(Runnable callback) { enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS); try { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b5911f69b156..50be1288ded9 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -315,6 +315,7 @@ import android.net.ConnectivityManager; import android.net.Proxy; import android.net.Uri; import android.os.AppZygote; +import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Binder; import android.os.BinderProxy; @@ -4070,21 +4071,6 @@ public class ActivityManagerService extends IActivityManager.Stub profile.addPss(mi.getTotalPss(), mi.getTotalUss(), mi.getTotalRss(), false, ProcessStats.ADD_PSS_EXTERNAL_SLOW, duration); - proc.getPkgList().forEachPackageProcessStats(holder -> { - final ProcessState state = holder.state; - FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED, - proc.info.uid, - state != null ? state.getName() : proc.processName, - state != null ? state.getPackage() : proc.info.packageName, - mi.getTotalPss(), - mi.getTotalUss(), - mi.getTotalRss(), - ProcessStats.ADD_PSS_EXTERNAL_SLOW, - duration, - holder.appVersion, - profile.getCurrentHostingComponentTypes(), - profile.getHistoricalHostingComponentTypes()); - }); } } } @@ -4131,20 +4117,6 @@ public class ActivityManagerService extends IActivityManager.Stub // Record this for posterity if the process has been stable. profile.addPss(pi, tmpUss[0], tmpUss[2], false, ProcessStats.ADD_PSS_EXTERNAL, duration); - proc.getPkgList().forEachPackageProcessStats(holder -> { - FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED, - proc.info.uid, - holder.state.getName(), - holder.state.getPackage(), - pi, - tmpUss[0], - tmpUss[2], - ProcessStats.ADD_PSS_EXTERNAL, - duration, - holder.appVersion, - profile.getCurrentHostingComponentTypes(), - profile.getHistoricalHostingComponentTypes()); - }); } } } @@ -9851,6 +9823,10 @@ public class ActivityManagerService extends IActivityManager.Stub PriorityDump.dump(mPriorityDumper, fd, pw, args); } + private static final String TICK = + "---------------------------------------" + + "----------------------------------------"; + private void dumpEverything(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage, int displayIdFilter, boolean dumpClient, boolean dumpNormalPriority, int dumpAppId, boolean dumpProxies) { @@ -9906,6 +9882,11 @@ public class ActivityManagerService extends IActivityManager.Stub sdumper.dumpLocked(); } } + + // No need to hold the lock. + pw.println(TICK); + AnrTimer.dump(pw, false); + // We drop the lock here because we can't call dumpWithClient() with the lock held; // if the caller wants a consistent state for the !dumpClient case, it can call this // method with the lock held. @@ -10351,6 +10332,8 @@ public class ActivityManagerService extends IActivityManager.Stub mOomAdjuster.dumpCachedAppOptimizerSettings(pw); mOomAdjuster.dumpCacheOomRankerSettings(pw); } + } else if ("timers".equals(cmd)) { + AnrTimer.dump(pw, true); } else if ("services".equals(cmd) || "s".equals(cmd)) { if (dumpClient) { ActiveServices.ServiceDumper dumper; @@ -12077,17 +12060,6 @@ public class ActivityManagerService extends IActivityManager.Stub // Record this for posterity if the process has been stable. r.mProfile.addPss(myTotalPss, myTotalUss, myTotalRss, true, reportType, endTime - startTime); - r.getPkgList().forEachPackageProcessStats(holder -> { - FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED, - r.info.uid, - holder.state.getName(), - holder.state.getPackage(), - myTotalPss, myTotalUss, myTotalRss, reportType, - endTime-startTime, - holder.appVersion, - r.mProfile.getCurrentHostingComponentTypes(), - r.mProfile.getHistoricalHostingComponentTypes()); - }); } } @@ -12723,16 +12695,6 @@ public class ActivityManagerService extends IActivityManager.Stub // Record this for posterity if the process has been stable. r.mProfile.addPss(myTotalPss, myTotalUss, myTotalRss, true, reportType, endTime - startTime); - r.getPkgList().forEachPackageProcessStats(holder -> { - FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED, - r.info.uid, - holder.state.getName(), - holder.state.getPackage(), - myTotalPss, myTotalUss, myTotalRss, reportType, endTime-startTime, - holder.appVersion, - r.mProfile.getCurrentHostingComponentTypes(), - r.mProfile.getHistoricalHostingComponentTypes()); - }); } } @@ -15134,6 +15096,16 @@ public class ActivityManagerService extends IActivityManager.Stub } } + // STOPSHIP(b/298884211): Remove this logging + if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { + final int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + if (level < 0) { + Slog.wtf(BroadcastQueue.TAG, "Unexpected broadcast: " + intent + + "; callingUid: " + callingUid + ", callingPid: " + callingPid, + new Throwable()); + } + } + int[] users; if (userId == UserHandle.USER_ALL) { // Caller wants broadcast to go to all started users. @@ -15866,7 +15838,15 @@ public class ActivityManagerService extends IActivityManager.Stub activeInstr.mWatcher = watcher; activeInstr.mUiAutomationConnection = uiAutomationConnection; activeInstr.mResultClass = className; - activeInstr.mHasBackgroundActivityStartsPermission = false; + activeInstr.mHasBackgroundActivityStartsPermission = + isSdkInSandbox + // TODO(b/261864298): consider using START_ACTIVITIES_FROM_BACKGROUND. + && checkPermission( + android.Manifest.permission + .START_ACTIVITIES_FROM_SDK_SANDBOX, + Binder.getCallingPid(), + Binder.getCallingUid()) + == PackageManager.PERMISSION_GRANTED; activeInstr.mHasBackgroundForegroundServiceStartsPermission = false; // Instrumenting sdk sandbox without a restart is not supported activeInstr.mNoRestart = false; diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 942d35a1d842..a057f32386f6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -4095,6 +4095,7 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" lru: raw LRU process list"); pw.println(" binder-proxies: stats on binder objects and IPCs"); pw.println(" settings: currently applied config settings"); + pw.println(" timers: the current ANR timer state"); pw.println(" service [COMP_SPEC]: service client-side state"); pw.println(" package [PACKAGE_NAME]: all state related to given package"); pw.println(" all: dump all activities"); diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java index 7d984434284c..e0a224629174 100644 --- a/services/core/java/com/android/server/am/AnrHelper.java +++ b/services/core/java/com/android/server/am/AnrHelper.java @@ -20,6 +20,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import android.content.pm.ApplicationInfo; +import android.os.Process; import android.os.SystemClock; import android.os.Trace; import android.util.ArraySet; @@ -267,6 +268,7 @@ class AnrHelper { private class AnrRecord { final ProcessRecord mApp; final int mPid; + final int mUid; final String mActivityShortComponentName; final String mParentShortComponentName; final TimeoutRecord mTimeoutRecord; @@ -283,6 +285,7 @@ class AnrHelper { Future<File> firstPidFilePromise) { mApp = anrProcess; mPid = anrProcess.mPid; + mUid = anrProcess.uid; mActivityShortComponentName = activityShortComponentName; mParentShortComponentName = parentShortComponentName; mTimeoutRecord = timeoutRecord; diff --git a/services/core/java/com/android/server/am/AnrTimer.java b/services/core/java/com/android/server/am/AnrTimer.java new file mode 100644 index 000000000000..cd6f009a4106 --- /dev/null +++ b/services/core/java/com/android/server/am/AnrTimer.java @@ -0,0 +1,834 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.text.TextUtils.formatSimple; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.SystemClock; +import android.os.Trace; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.IndentingPrintWriter; +import android.util.Log; +import android.util.MathUtils; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.Keep; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.ProcessCpuTracker; + +import java.io.PrintWriter; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * This class managers AnrTimers. An AnrTimer is a substitute for a delayed Message. In legacy + * mode, the timer just sends a delayed message. In modern mode, the timer is implemented in + * native code; on expiration, the message is sent without delay. + * + * <p>There are four external operations on a timer: + * <ul> + * + * <li>{@link #start} starts a timer. The timer is started with an object that the message + * argument. The timer is also given the pid and uid of the target. A timer that is started must + * be canceled, accepted, or discarded. + * + * <li>{@link #cancel} stops a timer and removes any in-flight expiration messages. + * + * <li>{@link #accept} acknowledges that the timer has expired, and that an ANR should be + * generated. This clears bookkeeping information for the timer. + * + * <li>{@link #discard} acknowledges that the timer has expired but, for other reasons, no ANR + * will be generated. This clears bookkeeping information for the timer. + * + *</li></p> + * + * <p>There is one internal operation on a timer: {@link #expire}. A timer may have automatic + * extensions enabled. If so, the extension is computed and if the extension is non-zero, the timer + * is restarted with the extension timeout. If extensions are disabled or if the extension is zero, + * the client process is notified of the expiration. + * + * @hide + */ +abstract class AnrTimer<V> { + + /** + * The log tag. + */ + final static String TAG = "AnrTimer"; + + /** + * The trace track for these events. There is a single track for all AnrTimer instances. The + * tracks give a sense of handler latency: the time between timer expiration and ANR + * collection. + */ + private final static String TRACK = "AnrTimer"; + + /** + * Enable debug messages. + */ + private static boolean DEBUG = false; + + /** + * The trace tag. + */ + private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER; + + /** + * Enable tracing from the time a timer expires until it is accepted or discarded. This is + * used to diagnose long latencies in the client. + */ + private static final boolean ENABLE_TRACING = false; + + /** + * The status of an ANR timer. TIMER_INVALID status is returned when an error is detected. + */ + private static final int TIMER_INVALID = 0; + private static final int TIMER_RUNNING = 1; + private static final int TIMER_EXPIRED = 2; + + @IntDef(prefix = { "TIMER_" }, value = { + TIMER_INVALID, TIMER_RUNNING, TIMER_EXPIRED + }) + private @interface TimerStatus {} + + /** + * A static list of all known AnrTimer instances, used for dumping and testing. + */ + @GuardedBy("sAnrTimerList") + private static final ArrayList<WeakReference<AnrTimer>> sAnrTimerList = new ArrayList<>(); + + /** + * An error is defined by its issue, the operation that detected the error, the tag of the + * affected service, a short stack of the bad call, and the stringified arg associated with + * the error. + */ + private static final class Error { + /** The issue is the kind of error that was detected. This is a free-form string. */ + final String issue; + /** The operation that detected the error: start, cancel, accept, or discard. */ + final String operation; + /** The argument (stringified) passed in to the operation. */ + final String arg; + /** The tag of the associated AnrTimer. */ + final String tag; + /** A partial stack that localizes the caller of the operation. */ + final StackTraceElement[] stack; + + Error(@NonNull String issue, @NonNull String operation, @NonNull String tag, + @NonNull StackTraceElement[] stack, @NonNull String arg) { + this.issue = issue; + this.operation = operation; + this.tag = tag; + this.stack = stack; + this.arg = arg; + } + } + + /** + * A list of errors detected during processing. Errors correspond to "timer not found" + * conditions. The stack trace identifies the source of the call. The list is + * first-in/first-out, and the size is limited to MAX_SAVED_ERROR_COUNT. + */ + @GuardedBy("sErrors") + private static final ArrayList<Error> sErrors = new ArrayList<>(); + + /** + * The maximum number of errors that are saved in the sErrors list. + */ + private static final int MAX_SAVED_ERROR_COUNT = 20; + + /** + * A record of a single anr timer. The pid and uid are retained for reference but they do not + * participate in the equality tests. A {@link Timer} is bound to its parent {@link AnrTimer} + * through the owner field. Access to timer fields is guarded by the mLock of the owner. + */ + private static class Timer { + /** The AnrTimer that is managing this Timer. */ + final AnrTimer owner; + + /** The argument that uniquely identifies the Timer in the context of its current owner. */ + final Object arg; + /** The pid of the process being tracked by this Timer. */ + final int pid; + /** The uid of the process being tracked by this Timer as reported by the kernel. */ + final int uid; + /** The original timeout. */ + final long timeoutMs; + + /** The status of the Timer. */ + @GuardedBy("owner.mLock") + @TimerStatus + int status; + + /** The absolute time the timer was startd */ + final long startedMs; + + /** Fields used by the native timer service. */ + + /** The timer ID: used to exchange information with the native service. */ + int timerId; + + /** Fields used by the legacy timer service. */ + + /** + * The process's cpu delay time when the timer starts . It is meaningful only if + * extendable is true. The cpu delay is cumulative, so the incremental delay that occurs + * during a timer is the delay at the end of the timer minus this value. Units are in + * milliseconds. + */ + @GuardedBy("owner.mLock") + long initialCpuDelayMs; + + /** True if the timer has been extended. */ + @GuardedBy("owner.mLock") + boolean extended; + + /** + * Fetch a new Timer. This is private. Clients should get a new timer using the obtain() + * method. + */ + private Timer(int pid, int uid, @Nullable Object arg, long timeoutMs, + @NonNull AnrTimer service) { + this.arg = arg; + this.pid = pid; + this.uid = uid; + this.timerId = 0; + this.timeoutMs = timeoutMs; + this.startedMs = now(); + this.owner = service; + this.initialCpuDelayMs = 0; + this.extended = false; + this.status = TIMER_INVALID; + } + + /** Get a timer. This implementation constructs a new timer. */ + static Timer obtain(int pid, int uid, @Nullable Object arg, long timeout, + @NonNull AnrTimer service) { + return new Timer(pid, uid, arg, timeout, service); + } + + /** Release a timer. This implementation simply drops the timer. */ + void release() { + } + + /** Return the age of the timer. This is used for debugging. */ + long age() { + return now() - startedMs; + } + + /** + * The hash code is generated from the owner and the argument. By definition, the + * combination must be unique for the lifetime of an in-use Timer. + */ + @Override + public int hashCode() { + return Objects.hash(owner, arg); + } + + /** + * The equality check compares the owner and the argument. By definition, the combination + * must be unique for the lifetime of an in-use Timer. + */ + @Override + public boolean equals(Object r) { + if (r instanceof Timer) { + Timer t = (Timer) r; + return Objects.equals(owner, t.owner) && Objects.equals(arg, t.arg); + } + return false; + } + + @Override + public String toString() { + final int myStatus; + synchronized (owner.mLock) { + myStatus = status; + } + return "timerId=" + timerId + " pid=" + pid + " uid=" + uid + + " " + statusString(myStatus) + " " + owner.mLabel; + } + } + + /** A lock for the AnrTimer instance. */ + private final Object mLock = new Object(); + + /** + * The map from client argument to the associated timer. + */ + @GuardedBy("mLock") + private final ArrayMap<V, Timer> mTimerMap = new ArrayMap<>(); + + /** The highwater mark of started, but not closed, timers. */ + @GuardedBy("mLock") + private int mMaxStarted = 0; + + /** + * The total number of timers started. + */ + @GuardedBy("mLock") + private int mTotalStarted = 0; + + /** + * The total number of errors detected. + */ + @GuardedBy("mLock") + private int mTotalErrors = 0; + + /** + * The total number of timers that have expired. + */ + @GuardedBy("mLock") + private int mTotalExpired = 0; + + /** + * A TimerService that generates a timeout event <n> milliseconds in the future. See the + * class documentation for an explanation of the operations. + */ + private abstract class TimerService { + /** Start a timer. The timeout must be initialized. */ + abstract boolean start(@NonNull Timer timer); + + abstract void cancel(@NonNull Timer timer); + + abstract void accept(@NonNull Timer timer); + + abstract void discard(@NonNull Timer timer); + } + + /** + * A class to assist testing. All methods are null by default but can be overridden as + * necessary for a test. + */ + @VisibleForTesting + static class Injector { + /** + * Return a handler for the given Callback. + */ + Handler getHandler(@NonNull Handler.Callback callback) { + return null; + }; + + /** + * Return a CpuTracker. + */ + CpuTracker getTracker() { + return null; + } + } + + /** + * A helper class to measure CPU delays. Given a process ID, this class will return the + * cumulative CPU delay for the PID, since process inception. This class is defined to assist + * testing. + */ + @VisibleForTesting + static class CpuTracker { + /** + * The parameter to ProcessCpuTracker indicates that statistics should be collected on a + * single process and not on the collection of threads associated with that process. + */ + private final ProcessCpuTracker mCpu = new ProcessCpuTracker(false); + + /** A simple wrapper to fetch the delay. This method can be overridden for testing. */ + long delay(int pid) { + return mCpu.getCpuDelayTimeForPid(pid); + } + } + + /** + * The "user-space" implementation of the timer service. This service uses its own message + * handler to create timeouts. + */ + private class HandlerTimerService extends TimerService { + /** The lock for this handler */ + private final Object mLock = new Object(); + + /** The message handler for scheduling future events. */ + private final Handler mHandler; + + /** The interface to fetch process statistics that might extend an ANR timeout. */ + private final CpuTracker mCpu; + + /** Create a HandlerTimerService based on the input handler. */ + HandlerTimerService(@NonNull Handler handler) { + mHandler = new Handler(handler.getLooper(), this::expires); + mCpu = new CpuTracker(); + } + + /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */ + @VisibleForTesting + HandlerTimerService(@NonNull Injector injector) { + mHandler = injector.getHandler(this::expires); + mCpu = injector.getTracker(); + } + + /** Post a message with the specified timeout. The timer is not modified. */ + private void post(@NonNull Timer t, long timeoutMillis) { + final Message msg = mHandler.obtainMessage(); + msg.obj = t; + mHandler.sendMessageDelayed(msg, timeoutMillis); + } + + /** + * The local expiration handler first attempts to compute a timer extension. If the timer + * should be extended, it is rescheduled in the future (granting more time to the + * associated process). If the timer should not be extended then the timeout is delivered + * to the client. + * + * A process is extended to account for the time the process was swapped out and was not + * runnable through no fault of its own. A timer can only be extended once and only if + * the AnrTimer permits extensions. Finally, a timer will never be extended by more than + * the original timeout, so the total timeout will never be more than twice the originally + * configured timeout. + */ + private boolean expires(Message msg) { + Timer t = (Timer) msg.obj; + synchronized (mLock) { + long extension = 0; + if (mExtend && !t.extended) { + extension = mCpu.delay(t.pid) - t.initialCpuDelayMs; + if (extension < 0) extension = 0; + if (extension > t.timeoutMs) extension = t.timeoutMs; + t.extended = true; + } + if (extension > 0) { + post(t, extension); + } else { + onExpiredLocked(t, now()); + } + } + return true; + } + + @GuardedBy("mLock") + @Override + boolean start(@NonNull Timer t) { + if (mExtend) { + t.initialCpuDelayMs = mCpu.delay(t.pid); + } + post(t, t.timeoutMs); + return true; + } + + @Override + void cancel(@NonNull Timer t) { + mHandler.removeMessages(0, t); + } + + @Override + void accept(@NonNull Timer t) { + // Nothing to do. + } + + @Override + void discard(@NonNull Timer t) { + // Nothing to do. + } + + /** The string identifies this subclass of AnrTimerService as being based on handlers. */ + @Override + public String toString() { + return "handler"; + } + } + + /** + * The handler for messages sent from this instance. + */ + private final Handler mHandler; + + /** + * The message type for messages sent from this interface. + */ + private final int mWhat; + + /** + * A label that identifies the AnrTimer associated with a Timer in log messages. + */ + private final String mLabel; + + /** + * Whether this timer instance supports extending timeouts. + */ + private final boolean mExtend; + + /** + * The timer service to use for this AnrTimer. + */ + private final TimerService mTimerService; + + /** + * Whether or not canceling a non-existent timer is an error. Clients often cancel freely + * preemptively, without knowing if the timer was ever started. Keeping this variable true + * means that such behavior is not an error. + */ + private final boolean mLenientCancel = true; + + /** + * The common constructor. A null injector results in a normal, production timer. + */ + @VisibleForTesting + AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend, + @Nullable Injector injector) { + mHandler = handler; + mWhat = what; + mLabel = label; + mExtend = extend; + if (injector == null) { + mTimerService = new HandlerTimerService(handler); + } else { + mTimerService = new HandlerTimerService(injector); + } + synchronized (sAnrTimerList) { + sAnrTimerList.add(new WeakReference(this)); + } + Log.i(TAG, formatSimple("created %s label: \"%s\"", mTimerService.toString(), label)); + } + + /** + * Create one timer instance for production. The client can ask for extensible timeouts. + */ + AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) { + this(handler, what, label, extend, null); + } + + /** + * Create one timer instance for production. There are no extensible timeouts. + */ + AnrTimer(@NonNull Handler handler, int what, @NonNull String label) { + this(handler, what, label, false, null); + } + + /** + * Start a trace on the timer. The trace is laid down in the AnrTimerTrack. + */ + private void traceBegin(Timer t, String what) { + if (ENABLE_TRACING) { + final String label = formatSimple("%s(%d,%d,%s)", what, t.pid, t.uid, mLabel); + final int cookie = t.hashCode(); + Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK, label, cookie); + } + } + + /** + * End a trace on the timer. + */ + private void traceEnd(Timer t) { + if (ENABLE_TRACING) { + final int cookie = t.hashCode(); + Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK, cookie); + } + } + + /** + * Return the string representation for a timer status. + */ + private static String statusString(int s) { + switch (s) { + case TIMER_INVALID: return "invalid"; + case TIMER_RUNNING: return "running"; + case TIMER_EXPIRED: return "expired"; + } + return formatSimple("unknown: %d", s); + } + + /** + * Delete the timer associated with arg from the maps and return it. Return null if the timer + * was not found. + */ + @GuardedBy("mLock") + private Timer removeLocked(V arg) { + Timer timer = mTimerMap.remove(arg); + return timer; + } + + /** + * Return the number of timers currently running. + */ + @VisibleForTesting + static int sizeOfTimerList() { + synchronized (sAnrTimerList) { + int totalTimers = 0; + for (int i = 0; i < sAnrTimerList.size(); i++) { + AnrTimer client = sAnrTimerList.get(i).get(); + if (client != null) totalTimers += client.mTimerMap.size(); + } + return totalTimers; + } + } + + /** + * Clear out all existing timers. This will lead to unexpected behavior if used carelessly. + * It is available only for testing. It returns the number of times that were actually + * erased. + */ + @VisibleForTesting + static int resetTimerListForHermeticTest() { + synchronized (sAnrTimerList) { + int mapLen = 0; + for (int i = 0; i < sAnrTimerList.size(); i++) { + AnrTimer client = sAnrTimerList.get(i).get(); + if (client != null) { + mapLen += client.mTimerMap.size(); + client.mTimerMap.clear(); + } + } + if (mapLen > 0) { + Log.w(TAG, formatSimple("erasing timer list: clearing %d timers", mapLen)); + } + return mapLen; + } + } + + /** + * Report something about a timer. + */ + private void report(@NonNull Timer timer, @NonNull String msg) { + Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg)); + } + + /** + * Start a timer. + */ + boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { + final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, this); + synchronized (mLock) { + Timer old = mTimerMap.get(arg); + if (old != null) { + // There is an existing timer. This is a protocol error in the client. Record + // the error and then clean up by canceling running timers and discarding expired + // timers. + restartedLocked(old.status, arg); + if (old.status == TIMER_EXPIRED) { + discard(arg); + } else { + cancel(arg); + } + } + if (mTimerService.start(timer)) { + timer.status = TIMER_RUNNING; + mTimerMap.put(arg, timer); + mTotalStarted++; + mMaxStarted = Math.max(mMaxStarted, mTimerMap.size()); + if (DEBUG) report(timer, "start"); + return true; + } else { + Log.e(TAG, "AnrTimer.start failed"); + return false; + } + } + } + + /** + * Cancel a timer. Return false if the timer was not found. + */ + boolean cancel(@NonNull V arg) { + synchronized (mLock) { + Timer timer = removeLocked(arg); + if (timer == null) { + if (!mLenientCancel) notFoundLocked("cancel", arg); + return false; + } + mTimerService.cancel(timer); + // There may be an expiration message in flight. Cancel it. + mHandler.removeMessages(mWhat, arg); + if (DEBUG) report(timer, "cancel"); + timer.release(); + return true; + } + } + + /** + * Accept a timer in the framework-level handler. The timeout has been accepted and the + * timeout handler is executing. Return false if the timer was not found. + */ + boolean accept(@NonNull V arg) { + synchronized (mLock) { + Timer timer = removeLocked(arg); + if (timer == null) { + notFoundLocked("accept", arg); + return false; + } + mTimerService.accept(timer); + traceEnd(timer); + if (DEBUG) report(timer, "accept"); + timer.release(); + return true; + } + } + + /** + * Discard a timer in the framework-level handler. For whatever reason, the timer is no + * longer interesting. No statistics are collected. Return false if the time was not found. + */ + boolean discard(@NonNull V arg) { + synchronized (mLock) { + Timer timer = removeLocked(arg); + if (timer == null) { + notFoundLocked("discard", arg); + return false; + } + mTimerService.discard(timer); + traceEnd(timer); + if (DEBUG) report(timer, "discard"); + timer.release(); + return true; + } + } + + /** + * The notifier that a timer has fired. The timer is not modified. + */ + @GuardedBy("mLock") + private void onExpiredLocked(@NonNull Timer timer, long when) { + if (DEBUG) report(timer, "expire"); + traceBegin(timer, "expired"); + mHandler.sendMessage(Message.obtain(mHandler, mWhat, timer.arg)); + synchronized (mLock) { + mTotalExpired++; + } + } + + /** + * Dump a single AnrTimer. + */ + private void dump(IndentingPrintWriter pw) { + synchronized (mLock) { + pw.format("timer: %s\n", mLabel); + pw.increaseIndent(); + pw.format("started=%d maxStarted=%d running=%d expired=%d error=%d\n", + mTotalStarted, mMaxStarted, mTimerMap.size(), + mTotalExpired, mTotalErrors); + pw.decreaseIndent(); + } + } + + /** + * Enable or disable debugging. + */ + static void debug(boolean f) { + DEBUG = f; + } + + /** + * The current time in milliseconds. + */ + private static long now() { + return SystemClock.uptimeMillis(); + } + + /** + * Log an error. A limited stack trace leading to the client call that triggered the error is + * recorded. The stack trace assumes that this method is not called directly. + * + * If DEBUG is true, a log message is generated as well. + */ + @GuardedBy("mLock") + private void recordErrorLocked(String operation, String errorMsg, Object arg) { + StackTraceElement[] s = Thread.currentThread().getStackTrace(); + final String what = Objects.toString(arg); + // The copy range starts at the caller of the timer operation, and includes three levels. + // This should be enough to isolate the location of the call. + StackTraceElement[] location = Arrays.copyOfRange(s, 6, 9); + synchronized (sErrors) { + // Ensure the error list does not grow beyond the limit. + while (sErrors.size() >= MAX_SAVED_ERROR_COUNT) { + sErrors.remove(0); + } + // Add the new error to the list. + sErrors.add(new Error(errorMsg, operation, mLabel, location, what)); + } + if (DEBUG) Log.w(TAG, operation + " " + errorMsg + " " + mLabel + " timer " + what); + mTotalErrors++; + } + + /** + * Log an error about a timer not found. + */ + @GuardedBy("mLock") + private void notFoundLocked(String operation, Object arg) { + recordErrorLocked(operation, "notFound", arg); + } + + /** + * Log an error about a timer that is started when there is an existing timer. + */ + @GuardedBy("mLock") + private void restartedLocked(@TimerStatus int status, Object arg) { + recordErrorLocked("start", status == TIMER_EXPIRED ? "autoDiscard" : "autoCancel", arg); + } + + /** + * Dump a single error to the output stream. + */ + private static void dump(IndentingPrintWriter ipw, int seq, Error err) { + ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag, + err.issue, err.arg); + ipw.increaseIndent(); + for (int i = 0; i < err.stack.length; i++) { + ipw.println(" " + err.stack[i].toString()); + } + ipw.decreaseIndent(); + } + + /** + * Dump all errors to the output stream. + */ + private static void dumpErrors(IndentingPrintWriter ipw) { + ArrayList<Error> errors; + synchronized (sErrors) { + if (sErrors.size() == 0) return; + errors = (ArrayList<Error>) sErrors.clone(); + } + ipw.println("Errors"); + ipw.increaseIndent(); + for (int i = 0; i < errors.size(); i++) { + dump(ipw, i, errors.get(i)); + } + ipw.decreaseIndent(); + } + + /** + * Dumpsys output. + */ + static void dump(@NonNull PrintWriter pw, boolean verbose) { + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw); + ipw.println("AnrTimer statistics"); + ipw.increaseIndent(); + synchronized (sAnrTimerList) { + for (int i = 0; i < sAnrTimerList.size(); i++) { + AnrTimer client = sAnrTimerList.get(i).get(); + if (client != null) client.dump(ipw); + } + } + if (verbose) dumpErrors(ipw); + ipw.format("AnrTimerEnd\n"); + ipw.decreaseIndent(); + } +} diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index 46e5523ac9a6..928b5d8f3ca7 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -770,17 +770,6 @@ public class AppProfiler { swapPss * 1024, rss * 1024, statType, procState, pssDuration); profile.setLastPssTime(now); profile.addPss(pss, uss, rss, true, statType, pssDuration); - proc.getPkgList().forEachPackageProcessStats(holder -> { - FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED, - proc.info.uid, - holder.state.getName(), - holder.state.getPackage(), - pss, uss, rss, - statType, pssDuration, - holder.appVersion, - profile.getCurrentHostingComponentTypes(), - profile.getHistoricalHostingComponentTypes()); - }); if (DEBUG_PSS) { Slog.d(TAG_PSS, "pss of " + proc.toShortString() + ": " + pss diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index fc8175b76ecd..c35a3b2474aa 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -101,10 +101,9 @@ class BroadcastProcessQueue { boolean runningOomAdjusted; /** - * Snapshotted value of {@link ProcessRecord#getCpuDelayTime()}, typically - * used when deciding if we should extend the soft ANR timeout. + * True if a timer has been started against this queue. */ - long lastCpuDelayTime; + private boolean mTimeoutScheduled; /** * Snapshotted value of {@link ProcessStateRecord#getCurProcState()} before @@ -1344,6 +1343,21 @@ class BroadcastProcessQueue { return head; } + /** + * Set the timeout flag to indicate that an ANR timer has been started. A value of true means a + * timer is running; a value of false means there is no timer running. + */ + void setTimeoutScheduled(boolean timeoutStarted) { + mTimeoutScheduled = timeoutStarted; + } + + /** + * Get the timeout flag + */ + boolean timeoutScheduled() { + return mTimeoutScheduled; + } + @Override public String toString() { if (mCachedToString == null) { diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 8d2edaaadb63..6a41628601b4 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -59,6 +59,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.os.BatteryManager; import android.os.Bundle; import android.os.BundleMerger; import android.os.Handler; @@ -149,6 +150,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // We configure runnable size only once at boot; it'd be too complex to // try resizing dynamically at runtime mRunning = new BroadcastProcessQueue[mConstants.getMaxRunningQueues()]; + + mAnrTimer = new BroadcastAnrTimer(mLocalHandler); } /** @@ -242,14 +245,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue { */ private @UptimeMillisLong long mLastTestFailureTime; + /** + * The ANR timer service for broadcasts. + */ + @GuardedBy("mService") + private final BroadcastAnrTimer mAnrTimer; + private static final int MSG_UPDATE_RUNNING_LIST = 1; - private static final int MSG_DELIVERY_TIMEOUT_SOFT = 2; - private static final int MSG_DELIVERY_TIMEOUT_HARD = 3; - private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 4; - private static final int MSG_CHECK_HEALTH = 5; - private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 6; - private static final int MSG_PROCESS_FREEZABLE_CHANGED = 7; - private static final int MSG_UID_STATE_CHANGED = 8; + private static final int MSG_DELIVERY_TIMEOUT = 2; + private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 3; + private static final int MSG_CHECK_HEALTH = 4; + private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 5; + private static final int MSG_PROCESS_FREEZABLE_CHANGED = 6; + private static final int MSG_UID_STATE_CHANGED = 7; private void enqueueUpdateRunningList() { mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST); @@ -264,12 +272,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { updateRunningList(); return true; } - case MSG_DELIVERY_TIMEOUT_SOFT: { - deliveryTimeoutSoft((BroadcastProcessQueue) msg.obj, msg.arg1); - return true; - } - case MSG_DELIVERY_TIMEOUT_HARD: { - deliveryTimeoutHard((BroadcastProcessQueue) msg.obj); + case MSG_DELIVERY_TIMEOUT: { + deliveryTimeout((BroadcastProcessQueue) msg.obj); return true; } case MSG_BG_ACTIVITY_START_TIMEOUT: { @@ -1024,12 +1028,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // immediately assume delivery success final boolean assumeDelivered = r.isAssumedDelivered(index); if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) { - queue.lastCpuDelayTime = queue.app.getCpuDelayTime(); - + queue.setTimeoutScheduled(true); final int softTimeoutMillis = (int) (r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT); - mLocalHandler.sendMessageDelayed(Message.obtain(mLocalHandler, - MSG_DELIVERY_TIMEOUT_SOFT, softTimeoutMillis, 0, queue), softTimeoutMillis); + mAnrTimer.start(queue, softTimeoutMillis); + } else { + queue.setTimeoutScheduled(false); } if (r.mBackgroundStartPrivileges.allowsAny()) { @@ -1074,6 +1078,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue { queue.lastProcessState = app.mState.getCurProcState(); if (receiver instanceof BroadcastFilter) { notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver); + // STOPSHIP(b/298884211): Remove this logging + if (Intent.ACTION_BATTERY_CHANGED.equals(receiverIntent.getAction())) { + int level = receiverIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + if (level < 0) { + Slog.wtf(TAG, "Dispatching unexpected broadcast: " + receiverIntent + + " to " + receiver + + "; callingUid: " + r.callingUid + + ", callingPid: " + r.callingPid); + } + } thread.scheduleRegisteredReceiver( ((BroadcastFilter) receiver).receiverList.receiver, receiverIntent, r.resultCode, r.resultData, r.resultExtras, @@ -1107,7 +1121,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // If we were trying to deliver a manifest broadcast, throw the error as we need // to try redelivering the broadcast to this receiver. if (receiver instanceof ResolveInfo) { - mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue); + mAnrTimer.cancel(queue); throw new BroadcastDeliveryFailedException(e); } finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, @@ -1156,39 +1170,27 @@ class BroadcastQueueModernImpl extends BroadcastQueue { r.resultTo = null; } - private void deliveryTimeoutSoft(@NonNull BroadcastProcessQueue queue, - int softTimeoutMillis) { + private void deliveryTimeout(@NonNull BroadcastProcessQueue queue) { synchronized (mService) { - deliveryTimeoutSoftLocked(queue, softTimeoutMillis); + deliveryTimeoutLocked(queue); } } - private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue, - int softTimeoutMillis) { - if (queue.app != null) { - // Instead of immediately triggering an ANR, extend the timeout by - // the amount of time the process was runnable-but-waiting; we're - // only willing to do this once before triggering an hard ANR - final long cpuDelayTime = queue.app.getCpuDelayTime() - queue.lastCpuDelayTime; - final long hardTimeoutMillis = MathUtils.constrain(cpuDelayTime, 0, softTimeoutMillis); - mLocalHandler.sendMessageDelayed( - Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue), - hardTimeoutMillis); - } else { - deliveryTimeoutHardLocked(queue); - } + private void deliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue) { + finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT, + "deliveryTimeoutLocked"); + demoteFromRunningLocked(queue); } - private void deliveryTimeoutHard(@NonNull BroadcastProcessQueue queue) { - synchronized (mService) { - deliveryTimeoutHardLocked(queue); + private class BroadcastAnrTimer extends AnrTimer<BroadcastProcessQueue> { + BroadcastAnrTimer(Handler handler) { + super(Objects.requireNonNull(handler), + MSG_DELIVERY_TIMEOUT, "BROADCAST_TIMEOUT", true); } - } - private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) { - finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT, - "deliveryTimeoutHardLocked"); - demoteFromRunningLocked(queue); + void start(@NonNull BroadcastProcessQueue queue, long timeoutMillis) { + start(queue, queue.app.getPid(), queue.app.uid, timeoutMillis); + } } @Override @@ -1292,14 +1294,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) { r.anrCount++; if (app != null && !app.isDebugging()) { + mAnrTimer.accept(queue); final String packageName = getReceiverPackageName(receiver); final String className = getReceiverClassName(receiver); mService.appNotResponding(queue.app, TimeoutRecord.forBroadcastReceiver(r.intent, packageName, className)); + } else { + mAnrTimer.discard(queue); } - } else { - mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue); - mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue); + } else if (queue.timeoutScheduled()) { + mAnrTimer.cancel(queue); } // Given that a receiver just finished, check if the "waitingFor" conditions are met. diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index 2ae3118d7bfa..b9ccbfbf55e7 100644 --- a/services/core/java/com/android/server/biometrics/AuthSession.java +++ b/services/core/java/com/android/server/biometrics/AuthSession.java @@ -275,7 +275,8 @@ public final class AuthSession implements IBinder.DeathRecipient { } sensor.goToStateWaitingForCookie(requireConfirmation, mToken, mOperationId, mUserId, mSensorReceiver, mOpPackageName, mRequestId, cookie, - mPromptInfo.isAllowBackgroundAuthentication()); + mPromptInfo.isAllowBackgroundAuthentication(), + mPromptInfo.isForLegacyFingerprintManager()); } } @@ -747,7 +748,7 @@ public final class AuthSession implements IBinder.DeathRecipient { Slog.v(TAG, "Confirmed! Modality: " + statsModality() + ", User: " + mUserId + ", IsCrypto: " + isCrypto() - + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT + + ", Client: " + getStatsClient() + ", RequireConfirmation: " + mPreAuthInfo.confirmationRequested + ", State: " + FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED + ", Latency: " + latency @@ -758,7 +759,7 @@ public final class AuthSession implements IBinder.DeathRecipient { mOperationContext, statsModality(), BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, + getStatsClient(), mDebugEnabled, latency, FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED, @@ -784,7 +785,7 @@ public final class AuthSession implements IBinder.DeathRecipient { + ", User: " + mUserId + ", IsCrypto: " + isCrypto() + ", Action: " + BiometricsProtoEnums.ACTION_AUTHENTICATE - + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT + + ", Client: " + getStatsClient() + ", Reason: " + reason + ", Error: " + error + ", Latency: " + latency @@ -796,7 +797,7 @@ public final class AuthSession implements IBinder.DeathRecipient { mOperationContext, statsModality(), BiometricsProtoEnums.ACTION_AUTHENTICATE, - BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, + getStatsClient(), mDebugEnabled, latency, error, @@ -998,6 +999,12 @@ public final class AuthSession implements IBinder.DeathRecipient { } } + private int getStatsClient() { + return mPromptInfo.isForLegacyFingerprintManager() + ? BiometricsProtoEnums.CLIENT_FINGERPRINT_MANAGER + : BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT; + } + @Override public String toString() { return "State: " + mState diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStats.java b/services/core/java/com/android/server/biometrics/AuthenticationStats.java index 707240bf41f8..3c1cc006ffda 100644 --- a/services/core/java/com/android/server/biometrics/AuthenticationStats.java +++ b/services/core/java/com/android/server/biometrics/AuthenticationStats.java @@ -16,12 +16,16 @@ package com.android.server.biometrics; +import android.util.Slog; + /** * Utility class for on-device biometric authentication data, including total authentication, * rejections, and the number of sent enrollment notifications. */ public class AuthenticationStats { + private static final String TAG = "AuthenticationStats"; + private static final float FRR_NOT_ENOUGH_ATTEMPTS = -1.0f; private final int mUserId; @@ -88,6 +92,7 @@ public class AuthenticationStats { public void resetData() { mTotalAttempts = 0; mRejectedAttempts = 0; + Slog.d(TAG, "Reset Counters."); } /** Update enrollment notification counter after sending a notification. */ diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java index 937e3f8f8668..42dd36a5c35a 100644 --- a/services/core/java/com/android/server/biometrics/BiometricSensor.java +++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java @@ -106,12 +106,13 @@ public abstract class BiometricSensor { void goToStateWaitingForCookie(boolean requireConfirmation, IBinder token, long sessionId, int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName, - long requestId, int cookie, boolean allowBackgroundAuthentication) + long requestId, int cookie, boolean allowBackgroundAuthentication, + boolean isForLegacyFingerprintManager) throws RemoteException { mCookie = cookie; impl.prepareForAuthentication(requireConfirmation, token, sessionId, userId, sensorReceiver, opPackageName, requestId, mCookie, - allowBackgroundAuthentication); + allowBackgroundAuthentication, isForLegacyFingerprintManager); mSensorState = STATE_WAITING_FOR_COOKIE; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java index fb64bcc3abc1..22110037890f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java @@ -62,7 +62,8 @@ public final class FaceAuthenticator extends IBiometricAuthenticator.Stub { @Override public void prepareForAuthentication(boolean requireConfirmation, IBinder token, long operationId, int userId, IBiometricSensorReceiver sensorReceiver, - String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication) + String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication, + boolean isForLegacyFingerprintManager) throws RemoteException { mFaceService.prepareForAuthentication(requireConfirmation, token, operationId, sensorReceiver, new FaceAuthenticateOptions.Builder() diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java index d47a57ad6742..b6fa0c126cd6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java @@ -62,7 +62,8 @@ public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub @Override public void prepareForAuthentication(boolean requireConfirmation, IBinder token, long operationId, int userId, IBiometricSensorReceiver sensorReceiver, - String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication) + String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication, + boolean isForLegacyFingerprintManager) throws RemoteException { mFingerprintService.prepareForAuthentication(token, operationId, sensorReceiver, new FingerprintAuthenticateOptions.Builder() @@ -70,7 +71,7 @@ public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub .setUserId(userId) .setOpPackageName(opPackageName) .build(), - requestId, cookie, allowBackgroundAuthentication); + requestId, cookie, allowBackgroundAuthentication, isForLegacyFingerprintManager); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 7cc6940f4b9d..5ce0c8b384ef 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -464,7 +464,8 @@ public class FingerprintService extends SystemService { public void prepareForAuthentication(IBinder token, long operationId, IBiometricSensorReceiver sensorReceiver, @NonNull FingerprintAuthenticateOptions options, - long requestId, int cookie, boolean allowBackgroundAuthentication) { + long requestId, int cookie, boolean allowBackgroundAuthentication, + boolean isForLegacyFingerprintManager) { super.prepareForAuthentication_enforcePermission(); final ServiceProvider provider = mRegistry.getProviderForSensor(options.getSensorId()); @@ -473,10 +474,13 @@ public class FingerprintService extends SystemService { return; } + final int statsClient = + isForLegacyFingerprintManager ? BiometricsProtoEnums.CLIENT_FINGERPRINT_MANAGER + : BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT; final boolean restricted = true; // BiometricPrompt is always restricted provider.scheduleAuthenticate(token, operationId, cookie, new ClientMonitorCallbackConverter(sensorReceiver), options, requestId, - restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, + restricted, statsClient, allowBackgroundAuthentication); } diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java index 5c0c3626037a..01d1e378a735 100644 --- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java +++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java @@ -59,7 +59,8 @@ public final class IrisAuthenticator extends IBiometricAuthenticator.Stub { @Override public void prepareForAuthentication(boolean requireConfirmation, IBinder token, long sessionId, int userId, IBiometricSensorReceiver sensorReceiver, - String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication) + String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication, + boolean isForLegacyFingerprintManager) throws RemoteException { } diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java index d4232ab60eec..6bed42b54bd9 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java @@ -323,10 +323,15 @@ final class ConversionUtils { static ProgramIdentifier identifierToHalProgramIdentifier(ProgramSelector.Identifier id) { ProgramIdentifier hwId = new ProgramIdentifier(); hwId.type = id.getType(); - if (hwId.type == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) { + if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) { hwId.type = IdentifierType.DAB_SID_EXT; } - hwId.value = id.getValue(); + long value = id.getValue(); + if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT) { + hwId.value = (value & 0xFFFF) | ((value >>> 16) << 32); + } else { + hwId.value = value; + } return hwId; } @@ -584,6 +589,9 @@ final class ConversionUtils { || isNewIdentifierInU(info.getPhysicallyTunedTo())) { return false; } + if (info.getRelatedContent() == null) { + return true; + } Iterator<ProgramSelector.Identifier> relatedContentIt = info.getRelatedContent().iterator(); while (relatedContentIt.hasNext()) { if (isNewIdentifierInU(relatedContentIt.next())) { diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java index 4bfc09075448..21273e0f2785 100644 --- a/services/core/java/com/android/server/display/BrightnessRangeController.java +++ b/services/core/java/com/android/server/display/BrightnessRangeController.java @@ -17,10 +17,11 @@ package com.android.server.display; import android.hardware.display.BrightnessInfo; +import android.os.Handler; import android.os.IBinder; -import android.provider.DeviceConfigInterface; -import com.android.server.display.feature.DeviceConfigParameterProvider; +import com.android.server.display.brightness.clamper.HdrClamper; +import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; import java.util.function.BooleanSupplier; @@ -31,23 +32,30 @@ class BrightnessRangeController { private final NormalBrightnessModeController mNormalBrightnessModeController = new NormalBrightnessModeController(); + private final HdrClamper mHdrClamper; + private final Runnable mModeChangeCallback; private final boolean mUseNbmController; + private final boolean mUseHdrClamper; + BrightnessRangeController(HighBrightnessModeController hbmController, - Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig) { + Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler, + DisplayManagerFlags flags) { this(hbmController, modeChangeCallback, displayDeviceConfig, - new DeviceConfigParameterProvider(DeviceConfigInterface.REAL)); + new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags); } BrightnessRangeController(HighBrightnessModeController hbmController, Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, - DeviceConfigParameterProvider configParameterProvider) { + HdrClamper hdrClamper, DisplayManagerFlags flags) { mHbmController = hbmController; mModeChangeCallback = modeChangeCallback; - mUseNbmController = configParameterProvider.isNormalBrightnessControllerFeatureEnabled(); + mUseHdrClamper = false; + mUseNbmController = flags.isNbmControllerEnabled(); mNormalBrightnessModeController.resetNbmData(displayDeviceConfig.getLuxThrottlingData()); + mHdrClamper = hdrClamper; } void dump(PrintWriter pw) { @@ -55,7 +63,6 @@ class BrightnessRangeController { pw.println(" mUseNormalBrightnessController=" + mUseNbmController); mHbmController.dump(pw); mNormalBrightnessModeController.dump(pw); - } void onAmbientLuxChange(float ambientLux) { @@ -63,6 +70,9 @@ class BrightnessRangeController { () -> mNormalBrightnessModeController.onAmbientLuxChange(ambientLux), () -> mHbmController.onAmbientLuxChange(ambientLux) ); + if (mUseHdrClamper) { + mHdrClamper.onAmbientLuxChange(ambientLux); + } } float getNormalBrightnessMax() { @@ -118,7 +128,8 @@ class BrightnessRangeController { } float getHdrBrightnessValue() { - return mHbmController.getHdrBrightnessValue(); + float hdrBrightness = mHbmController.getHdrBrightnessValue(); + return Math.min(hdrBrightness, mHdrClamper.getMaxBrightness()); } float getTransitionPoint() { @@ -138,4 +149,8 @@ class BrightnessRangeController { hbmChangesFunc.run(); } } + + public float getHdrTransitionRate() { + return mHdrClamper.getTransitionRate(); + } } diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java index 2d763bc486bb..cd867f60ebd0 100644 --- a/services/core/java/com/android/server/display/ColorFade.java +++ b/services/core/java/com/android/server/display/ColorFade.java @@ -43,6 +43,7 @@ import android.window.ScreenCapture; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy; +import com.android.server.wm.WindowManagerInternal; import libcore.io.Streams; @@ -573,8 +574,21 @@ final class ColorFade { } private ScreenCapture.ScreenshotHardwareBuffer captureScreen() { - ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = - mDisplayManagerInternal.systemScreenshot(mDisplayId); + WindowManagerInternal windowManagerService = LocalServices.getService( + WindowManagerInternal.class); + ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer; + ScreenCapture.SynchronousScreenCaptureListener screenCaptureListener = + ScreenCapture.createSyncCaptureListener(); + ScreenCapture.CaptureArgs captureArgs = new ScreenCapture.CaptureArgs.Builder<>() + .setCaptureSecureLayers(true) + .setAllowProtected(true) + .build(); + try { + windowManagerService.captureDisplay(mDisplayId, captureArgs, screenCaptureListener); + screenshotBuffer = screenCaptureListener.getBuffer(); + } catch (Exception e) { + screenshotBuffer = null; + } if (screenshotBuffer == null) { Slog.e(TAG, "Failed to take screenshot. Buffer is null"); return null; diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index e3dafa4a4cc0..3a6e5f93861b 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -53,6 +53,7 @@ import com.android.server.display.config.DisplayBrightnessPoint; import com.android.server.display.config.DisplayConfiguration; import com.android.server.display.config.DisplayQuirks; import com.android.server.display.config.HbmTiming; +import com.android.server.display.config.HdrBrightnessData; import com.android.server.display.config.HighBrightnessMode; import com.android.server.display.config.IntegerArray; import com.android.server.display.config.LuxThrottling; @@ -232,7 +233,22 @@ import javax.xml.datatype.DatatypeConfigurationException; * </point> * </sdrHdrRatioMap> * </highBrightnessMode> - * + * <hdrBrightnessConfig> + * <brightnessMap> + * <point> + * <first>500</first> + * <second>0.3</second> + * </point> + * <point> + * <first>1200</first> + * <second>0.6</second> + * </point> + * </brightnessMap> + * <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis> + * <brightnessIncreaseDurationMillis>10000</brightnessIncreaseDurationMillis> + * <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis> + * <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis> + * </hdrBrightnessConfig> * <luxThrottling> * <brightnessLimitMap> * <type>default</type> @@ -769,6 +785,9 @@ public class DisplayDeviceConfig { @Nullable private HostUsiVersion mHostUsiVersion; + @Nullable + private HdrBrightnessData mHdrBrightnessData; + @VisibleForTesting DisplayDeviceConfig(Context context) { mContext = context; @@ -1544,6 +1563,14 @@ public class DisplayDeviceConfig { } /** + * @return HDR brightness related configuration + */ + @Nullable + public HdrBrightnessData getHdrBrightnessData() { + return mHdrBrightnessData; + } + + /** * @return Refresh rate range for specific profile id or null */ @Nullable @@ -1759,7 +1786,8 @@ public class DisplayDeviceConfig { + "mScreenOffBrightnessSensorValueToLux=" + Arrays.toString( mScreenOffBrightnessSensorValueToLux) + "\n" - + "mUsiVersion= " + mHostUsiVersion + + "mUsiVersion= " + mHostUsiVersion + "\n" + + "mHdrBrightnessData" + mHdrBrightnessData + "}"; } @@ -1823,6 +1851,7 @@ public class DisplayDeviceConfig { loadRefreshRateSetting(config); loadScreenOffBrightnessSensorValueToLuxFromDdc(config); loadUsiVersion(config); + mHdrBrightnessData = HdrBrightnessData.loadConfig(config); } else { Slog.w(TAG, "DisplayDeviceConfig file is null"); } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 90c7ce7d5bc8..540ddd235e8c 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -136,7 +136,6 @@ import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.RefreshRateRange; import android.window.DisplayWindowPolicyController; -import android.window.ScreenCapture; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -2668,42 +2667,6 @@ public final class DisplayManagerService extends SystemService { return null; } - private ScreenCapture.ScreenshotHardwareBuffer systemScreenshotInternal(int displayId) { - final ScreenCapture.DisplayCaptureArgs captureArgs; - synchronized (mSyncRoot) { - final IBinder token = getDisplayToken(displayId); - if (token == null) { - return null; - } - final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId); - if (logicalDisplay == null) { - return null; - } - - final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked(); - captureArgs = new ScreenCapture.DisplayCaptureArgs.Builder(token) - .setSize(displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight()) - .setCaptureSecureLayers(true) - .setAllowProtected(true) - .build(); - } - return ScreenCapture.captureDisplay(captureArgs); - } - - private ScreenCapture.ScreenshotHardwareBuffer userScreenshotInternal(int displayId) { - synchronized (mSyncRoot) { - final IBinder token = getDisplayToken(displayId); - if (token == null) { - return null; - } - - final ScreenCapture.DisplayCaptureArgs captureArgs = - new ScreenCapture.DisplayCaptureArgs.Builder(token) - .build(); - return ScreenCapture.captureDisplay(captureArgs); - } - } - @VisibleForTesting DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributesInternal( int displayId) { @@ -3267,12 +3230,12 @@ public final class DisplayManagerService extends SystemService { displayPowerController = new DisplayPowerController2( mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler, mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, - () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted); + () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted, mFlags); } else { displayPowerController = new DisplayPowerController( mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler, mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, - () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted); + () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted, mFlags); } mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController); return displayPowerController; @@ -4471,16 +4434,6 @@ public final class DisplayManagerService extends SystemService { } @Override - public ScreenCapture.ScreenshotHardwareBuffer systemScreenshot(int displayId) { - return systemScreenshotInternal(displayId); - } - - @Override - public ScreenCapture.ScreenshotHardwareBuffer userScreenshot(int displayId) { - return userScreenshotInternal(displayId); - } - - @Override public DisplayInfo getDisplayInfo(int displayId) { return getDisplayInfoInternal(displayId, Process.myUid()); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 40dbabf29807..25f3237e9b8b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -75,6 +75,7 @@ import com.android.server.display.brightness.BrightnessEvent; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal; import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.layout.Layout; import com.android.server.display.utils.SensorUtils; import com.android.server.display.whitebalance.DisplayWhiteBalanceController; @@ -592,7 +593,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay, BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting, Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata, - boolean bootCompleted) { + boolean bootCompleted, DisplayManagerFlags flags) { mInjector = injector != null ? injector : new Injector(); mClock = mInjector.getClock(); @@ -677,7 +678,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback); mBrightnessRangeController = new BrightnessRangeController(hbmController, - modeChangeCallback, mDisplayDeviceConfig); + modeChangeCallback, mDisplayDeviceConfig, mHandler, flags); mBrightnessThrottler = createBrightnessThrottlerLocked(); diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 460c351c4027..595abfd81c23 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -77,6 +77,7 @@ import com.android.server.display.brightness.clamper.BrightnessClamperController import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy; import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal; import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.layout.Layout; import com.android.server.display.state.DisplayStateController; import com.android.server.display.utils.SensorUtils; @@ -472,7 +473,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay, BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting, Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata, - boolean bootCompleted) { + boolean bootCompleted, DisplayManagerFlags flags) { mInjector = injector != null ? injector : new Injector(); mClock = mInjector.getClock(); @@ -539,8 +540,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal modeChangeCallback); mBrightnessThrottler = createBrightnessThrottlerLocked(); - mBrightnessRangeController = new BrightnessRangeController(hbmController, - modeChangeCallback, mDisplayDeviceConfig); + mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController, + modeChangeCallback, mDisplayDeviceConfig, mHandler, flags); mDisplayBrightnessController = new DisplayBrightnessController(context, null, @@ -1497,6 +1498,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // allowed range. float animateValue = clampScreenBrightness(brightnessState); + // custom transition duration + float customTransitionRate = -1f; + // If there are any HDR layers on the screen, we have a special brightness value that we // use instead. We still preserve the calculated brightness for Standard Dynamic Range // (SDR) layers, but the main brightness value will be the one for HDR. @@ -1511,6 +1515,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // We want to scale HDR brightness level with the SDR level, we also need to restore // SDR brightness immediately when entering dim or low power mode. animateValue = mBrightnessRangeController.getHdrBrightnessValue(); + customTransitionRate = mBrightnessRangeController.getHdrTransitionRate(); } final float currentBrightness = mPowerState.getScreenBrightness(); @@ -1523,6 +1528,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal || !isDisplayContentVisible || brightnessIsTemporary) { animateScreenBrightness(animateValue, sdrAnimateValue, SCREEN_ANIMATION_RATE_MINIMUM); + } else if (customTransitionRate > 0) { + animateScreenBrightness(animateValue, sdrAnimateValue, + customTransitionRate); } else { boolean isIncreasing = animateValue > currentBrightness; final float rampSpeed; @@ -2968,6 +2976,14 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal hbmChangeCallback, hbmMetadata, context); } + BrightnessRangeController getBrightnessRangeController( + HighBrightnessModeController hbmController, Runnable modeChangeCallback, + DisplayDeviceConfig displayDeviceConfig, Handler handler, + DisplayManagerFlags flags) { + return new BrightnessRangeController(hbmController, + modeChangeCallback, displayDeviceConfig, handler, flags); + } + DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler, SensorManager sensorManager, Resources resources) { return DisplayWhiteBalanceFactory.create(handler, diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java index 52b92c4c7ca6..378cdba09520 100644 --- a/services/core/java/com/android/server/display/RampAnimator.java +++ b/services/core/java/com/android/server/display/RampAnimator.java @@ -31,7 +31,12 @@ class RampAnimator<T> { private final FloatProperty<T> mProperty; private float mCurrentValue; - private float mTargetValue; + + // target in HLG space + private float mTargetHlgValue; + + // target in linear space + private float mTargetLinearValue; private float mRate; private float mAnimationIncreaseMaxTimeSecs; private float mAnimationDecreaseMaxTimeSecs; @@ -78,7 +83,8 @@ class RampAnimator<T> { if (mFirstTime || target != mCurrentValue) { mFirstTime = false; mRate = 0; - mTargetValue = target; + mTargetHlgValue = target; + mTargetLinearValue = targetLinear; mCurrentValue = target; setPropertyValue(target); mAnimating = false; @@ -105,13 +111,14 @@ class RampAnimator<T> { // Otherwise, continue at the previous rate. if (!mAnimating || rate > mRate - || (target <= mCurrentValue && mCurrentValue <= mTargetValue) - || (mTargetValue <= mCurrentValue && mCurrentValue <= target)) { + || (target <= mCurrentValue && mCurrentValue <= mTargetHlgValue) + || (mTargetHlgValue <= mCurrentValue && mCurrentValue <= target)) { mRate = rate; } - final boolean changed = (mTargetValue != target); - mTargetValue = target; + final boolean changed = (mTargetHlgValue != target); + mTargetHlgValue = target; + mTargetLinearValue = targetLinear; // Start animating. if (!mAnimating && target != mCurrentValue) { @@ -135,7 +142,11 @@ class RampAnimator<T> { * into linear space. */ private void setPropertyValue(float val) { - final float linearVal = BrightnessUtils.convertGammaToLinear(val); + // To avoid linearVal inconsistency when converting to HLG and back to linear space + // used original target linear value for final animation step + float linearVal = + val == mTargetHlgValue ? mTargetLinearValue : BrightnessUtils.convertGammaToLinear( + val); mProperty.setValue(mObject, linearVal); } @@ -150,13 +161,13 @@ class RampAnimator<T> { final float scale = ValueAnimator.getDurationScale(); if (scale == 0) { // Animation off. - mAnimatedValue = mTargetValue; + mAnimatedValue = mTargetHlgValue; } else { final float amount = timeDelta * mRate / scale; - if (mTargetValue > mCurrentValue) { - mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetValue); + if (mTargetHlgValue > mCurrentValue) { + mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetHlgValue); } else { - mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetValue); + mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetHlgValue); } } final float oldCurrentValue = mCurrentValue; @@ -164,7 +175,7 @@ class RampAnimator<T> { if (oldCurrentValue != mCurrentValue) { setPropertyValue(mCurrentValue); } - if (mTargetValue == mCurrentValue) { + if (mTargetHlgValue == mCurrentValue) { mAnimating = false; } } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 693611263d66..d910e16de8e8 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -167,6 +167,8 @@ public class VirtualDisplayAdapter extends DisplayAdapter { int width, int height, int densityDpi) { VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken); if (device != null) { + Slog.v(TAG, "Resize VirtualDisplay " + device.mName + " to " + width + + " " + height); device.resizeLocked(width, height, densityDpi); } } @@ -183,6 +185,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { public void setVirtualDisplaySurfaceLocked(IBinder appToken, Surface surface) { VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken); if (device != null) { + Slog.v(TAG, "Update surface for VirtualDisplay " + device.mName); device.setSurfaceLocked(surface); } } @@ -197,6 +200,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken) { VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken); if (device != null) { + Slog.v(TAG, "Release VirtualDisplay " + device.mName); device.destroyLocked(true); appToken.unlinkToDeath(device, 0); } diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java new file mode 100644 index 000000000000..079a196234a8 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.clamper; + +import android.os.Handler; +import android.os.PowerManager; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.HashMap; +import java.util.Map; + +public class HdrClamper { + + private final Configuration mConfiguration = new Configuration(); + + private final BrightnessClamperController.ClamperChangeListener mClamperChangeListener; + + private final Handler mHandler; + + private final Runnable mDebouncer; + + private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX; + + // brightness change speed, in units per seconds, + private float mTransitionRate = -1f; + + private float mDesiredMaxBrightness = PowerManager.BRIGHTNESS_MAX; + + private float mDesiredTransitionDuration = -1; // in seconds + + public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener, + Handler handler) { + mClamperChangeListener = clamperChangeListener; + mHandler = handler; + mDebouncer = () -> { + mTransitionRate = Math.abs((mMaxBrightness - mDesiredMaxBrightness) + / mDesiredTransitionDuration); + mMaxBrightness = mDesiredMaxBrightness; + mClamperChangeListener.onChanged(); + }; + } + + // Called in same looper: mHandler.getLooper() + public float getMaxBrightness() { + return mMaxBrightness; + } + + // Called in same looper: mHandler.getLooper() + public float getTransitionRate() { + return mTransitionRate; + } + + + /** + * Updates brightness cap in response to ambient lux change. + * Called by ABC in same looper: mHandler.getLooper() + */ + public void onAmbientLuxChange(float ambientLux) { + float expectedMaxBrightness = findBrightnessLimit(ambientLux); + if (mMaxBrightness == expectedMaxBrightness) { + mDesiredMaxBrightness = mMaxBrightness; + mDesiredTransitionDuration = -1; + mTransitionRate = -1f; + mHandler.removeCallbacks(mDebouncer); + } else if (mDesiredMaxBrightness != expectedMaxBrightness) { + mDesiredMaxBrightness = expectedMaxBrightness; + long debounceTime; + if (mDesiredMaxBrightness > mMaxBrightness) { + debounceTime = mConfiguration.mIncreaseConfig.mDebounceTimeMillis; + mDesiredTransitionDuration = + (float) mConfiguration.mIncreaseConfig.mTransitionTimeMillis / 1000; + } else { + debounceTime = mConfiguration.mDecreaseConfig.mDebounceTimeMillis; + mDesiredTransitionDuration = + (float) mConfiguration.mDecreaseConfig.mTransitionTimeMillis / 1000; + } + + mHandler.removeCallbacks(mDebouncer); + mHandler.postDelayed(mDebouncer, debounceTime); + } + } + + @VisibleForTesting + Configuration getConfiguration() { + return mConfiguration; + } + + private float findBrightnessLimit(float ambientLux) { + float foundAmbientBoundary = Float.MAX_VALUE; + float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX; + for (Map.Entry<Float, Float> brightnessPoint : + mConfiguration.mMaxBrightnessLimits.entrySet()) { + float ambientBoundary = brightnessPoint.getKey(); + // find ambient lux upper boundary closest to current ambient lux + if (ambientBoundary > ambientLux && ambientBoundary < foundAmbientBoundary) { + foundMaxBrightness = brightnessPoint.getValue(); + foundAmbientBoundary = ambientBoundary; + } + } + return foundMaxBrightness; + } + + @VisibleForTesting + static class Configuration { + final Map<Float, Float> mMaxBrightnessLimits = new HashMap<>(); + final TransitionConfiguration mIncreaseConfig = new TransitionConfiguration(); + + final TransitionConfiguration mDecreaseConfig = new TransitionConfiguration(); + } + + @VisibleForTesting + static class TransitionConfiguration { + long mDebounceTimeMillis; + + long mTransitionTimeMillis; + } +} diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java new file mode 100644 index 000000000000..06d3c5b87520 --- /dev/null +++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.config; + +import android.annotation.Nullable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Brightness config for HDR content + */ +public class HdrBrightnessData { + + /** + * Lux to brightness map + */ + public final Map<Float, Float> mMaxBrightnessLimits; + + /** + * Debounce time for brightness increase + */ + public final long mBrightnessIncreaseDebounceMillis; + + /** + * Brightness increase animation duration + */ + public final long mBrightnessIncreaseDurationMillis; + + /** + * Debounce time for brightness decrease + */ + public final long mBrightnessDecreaseDebounceMillis; + + /** + * Brightness decrease animation duration + */ + public final long mBrightnessDecreaseDurationMillis; + + private HdrBrightnessData(Map<Float, Float> maxBrightnessLimits, + long brightnessIncreaseDebounceMillis, long brightnessIncreaseDurationMillis, + long brightnessDecreaseDebounceMillis, long brightnessDecreaseDurationMillis) { + mMaxBrightnessLimits = maxBrightnessLimits; + mBrightnessIncreaseDebounceMillis = brightnessIncreaseDebounceMillis; + mBrightnessIncreaseDurationMillis = brightnessIncreaseDurationMillis; + mBrightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis; + mBrightnessDecreaseDurationMillis = brightnessDecreaseDurationMillis; + } + + @Override + public String toString() { + return "HdrBrightnessData {" + + "mMaxBrightnessLimits: " + mMaxBrightnessLimits + + ", mBrightnessIncreaseDebounceMillis: " + mBrightnessIncreaseDebounceMillis + + ", mBrightnessIncreaseDurationMillis: " + mBrightnessIncreaseDurationMillis + + ", mBrightnessDecreaseDebounceMillis: " + mBrightnessDecreaseDebounceMillis + + ", mBrightnessDecreaseDurationMillis: " + mBrightnessDecreaseDurationMillis + + "} "; + } + + /** + * Loads HdrBrightnessData from DisplayConfiguration + */ + @Nullable + public static HdrBrightnessData loadConfig(DisplayConfiguration config) { + HdrBrightnessConfig hdrConfig = config.getHdrBrightnessConfig(); + if (hdrConfig == null) { + return null; + } + + List<NonNegativeFloatToFloatPoint> points = hdrConfig.getBrightnessMap().getPoint(); + Map<Float, Float> brightnessLimits = new HashMap<>(); + for (NonNegativeFloatToFloatPoint point: points) { + brightnessLimits.put(point.getFirst().floatValue(), point.getSecond().floatValue()); + } + + return new HdrBrightnessData(brightnessLimits, + hdrConfig.getBrightnessIncreaseDebounceMillis().longValue(), + hdrConfig.getBrightnessIncreaseDurationMillis().longValue(), + hdrConfig.getBrightnessDecreaseDebounceMillis().longValue(), + hdrConfig.getBrightnessDecreaseDurationMillis().longValue()); + } +} 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 fddac6dcf874..aebd8a08ee3f 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -30,43 +30,68 @@ import java.util.function.Supplier; public class DisplayManagerFlags { private static final boolean DEBUG = false; private static final String TAG = "DisplayManagerFlags"; - private boolean mIsConnectedDisplayManagementEnabled = false; - private boolean mIsConnectedDisplayManagementEnabledSet = false; - private boolean flagOrSystemProperty(Supplier<Boolean> flagFunction, String flagName) { - // TODO(b/299462337) Remove when the infrastructure is ready. - if ((Build.IS_ENG || Build.IS_USERDEBUG) - && SystemProperties.getBoolean("persist.sys." + flagName, false)) { - return true; - } - try { - return flagFunction.get(); - } catch (Throwable ex) { - if (DEBUG) { - Slog.i(TAG, "Flags not ready yet. Return false for " + flagName, ex); - } - return false; - } - } + private final FlagState mConnectedDisplayManagementFlagState = new FlagState( + Flags.FLAG_ENABLE_CONNECTED_DISPLAY_MANAGEMENT, + Flags::enableConnectedDisplayManagement); + + private final FlagState mNbmControllerFlagState = new FlagState( + Flags.FLAG_ENABLE_NBM_CONTROLLER, + Flags::enableNbmController); - // TODO(b/297159910): Simplify using READ-ONLY flags when available. /** Returns whether connected display management is enabled or not. */ public boolean isConnectedDisplayManagementEnabled() { - if (mIsConnectedDisplayManagementEnabledSet) { + return mConnectedDisplayManagementFlagState.isEnabled(); + } + + /** Returns whether hdr clamper is enabled on not*/ + public boolean isNbmControllerEnabled() { + return mNbmControllerFlagState.isEnabled(); + } + + private static class FlagState { + + private final String mName; + + private final Supplier<Boolean> mFlagFunction; + private boolean mEnabledSet; + private boolean mEnabled; + + private FlagState(String name, Supplier<Boolean> flagFunction) { + mName = name; + mFlagFunction = flagFunction; + } + + // TODO(b/297159910): Simplify using READ-ONLY flags when available. + private boolean isEnabled() { + if (mEnabledSet) { + if (DEBUG) { + Slog.d(TAG, mName + ": mEnabled. Recall = " + mEnabled); + } + return mEnabled; + } + mEnabled = flagOrSystemProperty(mFlagFunction, mName); if (DEBUG) { - Slog.d(TAG, "isConnectedDisplayManagementEnabled. Recall = " - + mIsConnectedDisplayManagementEnabled); + Slog.d(TAG, mName + ": mEnabled. Flag value = " + mEnabled); } - return mIsConnectedDisplayManagementEnabled; + mEnabledSet = true; + return mEnabled; } - mIsConnectedDisplayManagementEnabled = - flagOrSystemProperty(Flags::enableConnectedDisplayManagement, - Flags.FLAG_ENABLE_CONNECTED_DISPLAY_MANAGEMENT); - if (DEBUG) { - Slog.d(TAG, "isConnectedDisplayManagementEnabled. Flag value = " - + mIsConnectedDisplayManagementEnabled); + + private boolean flagOrSystemProperty(Supplier<Boolean> flagFunction, String flagName) { + // TODO(b/299462337) Remove when the infrastructure is ready. + if ((Build.IS_ENG || Build.IS_USERDEBUG) + && SystemProperties.getBoolean("persist.sys." + flagName, false)) { + return true; + } + try { + return flagFunction.get(); + } catch (Throwable ex) { + if (DEBUG) { + Slog.i(TAG, "Flags not ready yet. Return false for " + flagName, ex); + } + return false; + } } - mIsConnectedDisplayManagementEnabledSet = true; - return mIsConnectedDisplayManagementEnabled; } } 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 2c3c66e2dfc9..12306b039225 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 @@ -9,3 +9,11 @@ flag { bug: "280739508" is_fixed_read_only: true } + +flag { + name: "enable_nbm_controller" + namespace: "display_manager" + description: "Feature flag for Normal Brightness Mode Controller" + bug: "277877297" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java index a2c8748a9142..2ede56dcecd9 100644 --- a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java +++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java @@ -62,10 +62,10 @@ class GestureMonitorSpyWindow { mWindowHandle.ownerUid = uid; mWindowHandle.scaleFactor = 1.0f; mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */); - mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.SPY; + mWindowHandle.inputConfig = + InputConfig.NOT_FOCUSABLE | InputConfig.SPY | InputConfig.TRUSTED_OVERLAY; final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mWindowHandle.setTrustedOverlay(t, mInputSurface, true); t.setInputWindowInfo(mInputSurface, mWindowHandle); t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_GESTURE_MONITOR); t.setPosition(mInputSurface, 0, 0); diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 62660c4f3c6d..6b399def4d73 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -95,6 +95,7 @@ import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputMonitor; +import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.PointerIcon; import android.view.Surface; @@ -682,6 +683,12 @@ public class InputManagerService extends IInputManager.Stub return mNative.getKeyCodeForKeyLocation(deviceId, locationKeyCode); } + @Override // Binder call + public KeyCharacterMap getKeyCharacterMap(@NonNull String layoutDescriptor) { + Objects.requireNonNull(layoutDescriptor, "layoutDescriptor must not be null"); + return mKeyboardLayoutManager.getKeyCharacterMap(layoutDescriptor); + } + /** * Transfer the current touch gesture to the provided window. * diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index a5162c09f838..0eb620f3f4df 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -63,6 +63,7 @@ import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.view.InputDevice; +import android.view.KeyCharacterMap; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; @@ -430,6 +431,23 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { return result[0]; } + @AnyThread + public KeyCharacterMap getKeyCharacterMap(@NonNull String layoutDescriptor) { + final String[] overlay = new String[1]; + visitKeyboardLayout(layoutDescriptor, + (resources, keyboardLayoutResId, layout) -> { + try (InputStreamReader stream = new InputStreamReader( + resources.openRawResource(keyboardLayoutResId))) { + overlay[0] = Streams.readFully(stream); + } catch (IOException | Resources.NotFoundException ignored) { + } + }); + if (TextUtils.isEmpty(overlay[0])) { + return KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); + } + return KeyCharacterMap.load(layoutDescriptor, overlay[0]); + } + private void visitAllKeyboardLayouts(KeyboardLayoutVisitor visitor) { final PackageManager pm = mContext.getPackageManager(); Intent intent = new Intent(InputManager.ACTION_QUERY_KEYBOARD_LAYOUTS); diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java index dbbbed31df76..7726f40fa2ae 100644 --- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java +++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java @@ -57,13 +57,13 @@ final class HandwritingEventReceiverSurface { InputConfig.NOT_FOCUSABLE | InputConfig.NOT_TOUCHABLE | InputConfig.SPY - | InputConfig.INTERCEPTS_STYLUS; + | InputConfig.INTERCEPTS_STYLUS + | InputConfig.TRUSTED_OVERLAY; // Configure the surface to receive stylus events across the entire display. mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */); final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mWindowHandle.setTrustedOverlay(t, mInputSurface, true); t.setInputWindowInfo(mInputSurface, mWindowHandle); t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_HANDWRITING_SURFACE); t.setPosition(mInputSurface, 0, 0); diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java index 66985e0b2533..ddeeacc76579 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteController.java +++ b/services/core/java/com/android/server/media/BluetoothRouteController.java @@ -24,6 +24,8 @@ import android.content.Context; import android.media.MediaRoute2Info; import android.os.UserHandle; +import com.android.media.flags.Flags; + import java.util.Collections; import java.util.List; import java.util.Objects; @@ -53,15 +55,10 @@ import java.util.Objects; return new NoOpBluetoothRouteController(); } - MediaFeatureFlagManager flagManager = MediaFeatureFlagManager.getInstance(); - boolean isUsingLegacyController = flagManager.getBoolean( - MediaFeatureFlagManager.FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER, - true); - - if (isUsingLegacyController) { - return new LegacyBluetoothRouteController(context, btAdapter, listener); - } else { + if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) { return new AudioPoliciesBluetoothRouteController(context, btAdapter, listener); + } else { + return new LegacyBluetoothRouteController(context, btAdapter, listener); } } diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java index 3875c84e618b..e17f4a3fd42f 100644 --- a/services/core/java/com/android/server/media/DeviceRouteController.java +++ b/services/core/java/com/android/server/media/DeviceRouteController.java @@ -25,6 +25,8 @@ import android.media.IAudioService; import android.media.MediaRoute2Info; import android.os.ServiceManager; +import com.android.media.flags.Flags; + /** * Controls device routes. * @@ -44,18 +46,13 @@ import android.os.ServiceManager; IAudioService audioService = IAudioService.Stub.asInterface( ServiceManager.getService(Context.AUDIO_SERVICE)); - MediaFeatureFlagManager flagManager = MediaFeatureFlagManager.getInstance(); - boolean isUsingLegacyController = flagManager.getBoolean( - MediaFeatureFlagManager.FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER, - true); - - if (isUsingLegacyController) { - return new LegacyDeviceRouteController(context, + if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) { + return new AudioPoliciesDeviceRouteController(context, audioManager, audioService, onDeviceRouteChangedListener); } else { - return new AudioPoliciesDeviceRouteController(context, + return new LegacyDeviceRouteController(context, audioManager, audioService, onDeviceRouteChangedListener); diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java index e31a7fc5d5ff..198040378dc6 100644 --- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java +++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java @@ -508,7 +508,11 @@ class LegacyBluetoothRouteController implements BluetoothRouteController { case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED: clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP); if (device != null) { - addActiveRoute(mBluetoothRoutes.get(device.getAddress())); + if (DEBUG) { + Log.d(TAG, "Setting active a2dp devices. device=" + device); + } + + addActiveDevices(device); } notifyBluetoothRoutesUpdated(); break; diff --git a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java b/services/core/java/com/android/server/media/MediaFeatureFlagManager.java index f55550509f46..f90f64a19301 100644 --- a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java +++ b/services/core/java/com/android/server/media/MediaFeatureFlagManager.java @@ -36,7 +36,6 @@ import java.lang.annotation.Target; @StringDef( prefix = "FEATURE_", value = { - FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER, FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE }) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @@ -44,14 +43,6 @@ import java.lang.annotation.Target; /* package */ @interface MediaFeatureFlag {} /** - * Whether to use old legacy implementation of BluetoothRouteController or new - * 'Audio Strategies'-aware controller. - */ - /* package */ static final @MediaFeatureFlag String - FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER = - "BluetoothRouteController__enable_legacy_bluetooth_routes_controller"; - - /** * Whether to use IMPORTANCE_FOREGROUND (i.e. 100) or IMPORTANCE_FOREGROUND_SERVICE (i.e. 125) * as the minimum package importance for scanning. */ diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 515c7fb09ab0..13d166294603 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -932,6 +932,7 @@ public final class MediaProjectionManagerService extends SystemService if (callback == null) { throw new IllegalArgumentException("callback must not be null"); } + Slog.v(TAG, "Start the token instance " + this); // Cache result of calling into ActivityManagerService outside of the lock, to prevent // deadlock with WindowManagerService. final boolean hasFGS = mActivityManagerInternal.hasRunningForegroundService( @@ -951,9 +952,6 @@ public final class MediaProjectionManagerService extends SystemService throw new SecurityException("Media projections require a foreground service" + " of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION"); } - - mCallback = callback; - registerCallback(mCallback); try { mToken = callback.asBinder(); mDeathEater = () -> { @@ -998,6 +996,11 @@ public final class MediaProjectionManagerService extends SystemService } } startProjectionLocked(this); + + // Register new callbacks after stop has been dispatched to previous session. + mCallback = callback; + registerCallback(mCallback); + // Mark this token as used when the app gets the MediaProjection instance. mCountStarts++; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c80661692217..bada8165766c 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2826,9 +2826,6 @@ public class NotificationManagerService extends SystemService { mAssistants.onBootPhaseAppsCanStart(); mConditionProviders.onBootPhaseAppsCanStart(); mHistoryManager.onBootPhaseAppsCanStart(); - if (expireBitmaps()) { - NotificationBitmapJobService.scheduleJob(getContext()); - } registerDeviceConfigChange(); migrateDefaultNAS(); maybeShowInitialReviewPermissionsNotification(); @@ -2837,6 +2834,10 @@ public class NotificationManagerService extends SystemService { } else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) { mPreferencesHelper.updateFixedImportance(mUm.getUsers()); mPreferencesHelper.migrateNotificationPermissions(mUm.getUsers()); + } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { + if (expireBitmaps()) { + NotificationBitmapJobService.scheduleJob(getContext()); + } } } @@ -6630,8 +6631,7 @@ public class NotificationManagerService extends SystemService { } }; - @VisibleForTesting - static boolean isBigPictureWithBitmapOrIcon(Notification n) { + private static boolean isBigPictureWithBitmapOrIcon(Notification n) { final boolean isBigPicture = n.isStyle(Notification.BigPictureStyle.class); if (!isBigPicture) { return false; @@ -6649,15 +6649,12 @@ public class NotificationManagerService extends SystemService { return false; } - @VisibleForTesting - // TODO(b/298414239) Unit test via public API - static boolean isBitmapExpired(long timePostedMs, long timeNowMs, long timeToLiveMs) { + private static boolean isBitmapExpired(long timePostedMs, long timeNowMs, long timeToLiveMs) { final long timeDiff = timeNowMs - timePostedMs; return timeDiff > timeToLiveMs; } - @VisibleForTesting - void removeBitmapAndRepost(NotificationRecord r) { + private void removeBitmapAndRepost(NotificationRecord r) { if (!isBigPictureWithBitmapOrIcon(r.getNotification())) { return; } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index a700d3235ea4..71562dc1ed86 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -1022,6 +1022,8 @@ public class ZenModeHelper { @VisibleForTesting protected void setZenModeSetting(int zen) { Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen); + ZenLog.traceSetZenMode(Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, -1), + "updated setting"); showZenUpgradeNotification(zen); } diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index f95f7bc0d165..bd9be30b681b 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -17,7 +17,6 @@ package com.android.server.pm; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; - import static com.android.server.pm.PackageManagerService.TAG; import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal; import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; @@ -228,7 +227,7 @@ public class AppDataHelper { userId, flags, appId, seInfo, targetSdkVersion, usesSdk); args.previousAppId = previousAppId; - return batch.createAppData(args).whenComplete((ceDataInode, e) -> { + return batch.createAppData(args).whenComplete((createAppDataResult, e) -> { // Note: this code block is executed with the Installer lock // already held, since it's invoked as a side-effect of // executeBatchLI() @@ -237,7 +236,7 @@ public class AppDataHelper { + ", but trying to recover: " + e); destroyAppDataLeafLIF(pkg, userId, flags); try { - ceDataInode = mInstaller.createAppData(args).ceDataInode; + createAppDataResult = mInstaller.createAppData(args); logCriticalInfo(Log.DEBUG, "Recovery succeeded!"); } catch (Installer.InstallerException e2) { logCriticalInfo(Log.DEBUG, "Recovery failed!"); @@ -279,12 +278,19 @@ public class AppDataHelper { } } + final long ceDataInode = createAppDataResult.ceDataInode; + final long deDataInode = createAppDataResult.deDataInode; + if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) { - // TODO: mark this structure as dirty so we persist it! synchronized (mPm.mLock) { ps.setCeDataInode(ceDataInode, userId); } } + if ((flags & StorageManager.FLAG_STORAGE_DE) != 0 && deDataInode != -1) { + synchronized (mPm.mLock) { + ps.setDeDataInode(deDataInode, userId); + } + } prepareAppDataContentsLeafLIF(pkg, ps, userId, flags); }); @@ -609,7 +615,7 @@ public class AppDataHelper { destroyAppDataLeafLIF(pkg, userId, flags); } - public void destroyAppDataLeafLIF(AndroidPackage pkg, int userId, int flags) { + private void destroyAppDataLeafLIF(AndroidPackage pkg, int userId, int flags) { final Computer snapshot = mPm.snapshotComputer(); final PackageStateInternal packageStateInternal = snapshot.getPackageStateInternal(pkg.getPackageName()); diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index ffa2af1e2f81..316c4aca1891 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -1024,7 +1024,7 @@ public class ComputerEngine implements Computer { if ("android".equals(packageName) || "system".equals(packageName)) { return androidApplication(); } - if ((flags & MATCH_KNOWN_PACKAGES) != 0) { + if ((flags & (MATCH_KNOWN_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0) { // Already generates the external package name return generateApplicationInfoFromSettings(packageName, flags, filterCallingUid, userId); @@ -1518,7 +1518,6 @@ public class ComputerEngine implements Computer { pi.sharedUserId = (sharedUser != null) ? sharedUser.getName() : null; pi.firstInstallTime = state.getFirstInstallTimeMillis(); pi.lastUpdateTime = ps.getLastUpdateTime(); - pi.isArchived = isArchived(state); ApplicationInfo ai = new ApplicationInfo(); ai.packageName = ps.getPackageName(); diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index b5a373e35571..7d59210044d0 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -569,6 +569,7 @@ final class DeletePackageHelper { ps.setUserState(nextUserId, ps.getCeDataInode(nextUserId), + ps.getDeDataInode(nextUserId), COMPONENT_ENABLED_STATE_DEFAULT, false /*installed*/, true /*stopped*/, diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 468b3a705bf1..e1e5e6db104a 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -44,6 +44,7 @@ import static android.os.incremental.IncrementalManager.isIncrementalPath; import static android.os.storage.StorageManager.FLAG_STORAGE_CE; import static android.os.storage.StorageManager.FLAG_STORAGE_DE; import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL; + import static com.android.server.pm.DexOptHelper.useArtService; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet; @@ -56,7 +57,6 @@ import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE; import static com.android.server.pm.PackageManagerService.DEBUG_UPGRADE; import static com.android.server.pm.PackageManagerService.DEBUG_VERIFY; -import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY; import static com.android.server.pm.PackageManagerService.MIN_INSTALLABLE_TARGET_SDK; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static com.android.server.pm.PackageManagerService.POST_INSTALL; @@ -112,6 +112,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.ApplicationInfo; +import android.content.pm.ArchivedPackageParcel; import android.content.pm.DataLoaderType; import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; @@ -180,7 +181,6 @@ import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.permission.Permission; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedLibraryWrapper; import com.android.server.pm.pkg.component.ComponentMutateUtils; @@ -1144,15 +1144,18 @@ final class InstallPackageHelper { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage"); final ParsedPackage parsedPackage; + final ArchivedPackageParcel archivedPackage; try (PackageParser2 pp = mPm.mInjector.getPreparingPackageParser()) { if (request.getPackageLite() == null || !request.isArchived()) { // TODO: pass packageLite from install request instead of reparsing the package parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false); AndroidPackageUtils.validatePackageDexMetadata(parsedPackage); + archivedPackage = null; } else { // Archived install mode, no APK. parsedPackage = pp.parsePackageFromPackageLite(request.getPackageLite(), parseFlags); + archivedPackage = request.getPackageLite().getArchivedPackage(); } } catch (PackageManagerException e) { throw new PrepareFailure("Failed parse during installPackageLI", e); @@ -1245,6 +1248,7 @@ final class InstallPackageHelper { boolean systemApp = false; boolean replace = false; synchronized (mPm.mLock) { + final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName); // Check if installing already existing package if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { String oldName = mPm.mSettings.getRenamedPackageLPr(pkgName); @@ -1261,16 +1265,15 @@ final class InstallPackageHelper { Slog.d(TAG, "Replacing existing renamed package: oldName=" + oldName + " pkgName=" + pkgName); } - } else if (mPm.mPackages.containsKey(pkgName)) { + } else if (ps != null) { // This package, under its official name, already exists // on the device; we should replace it. replace = true; if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing package: " + pkgName); } - - if (replace) { + final AndroidPackage oldPackage = mPm.mPackages.get(pkgName); + if (replace && oldPackage != null) { // Prevent apps opting out from runtime permissions - AndroidPackage oldPackage = mPm.mPackages.get(pkgName); final int oldTargetSdk = oldPackage.getTargetSdkVersion(); final int newTargetSdk = parsedPackage.getTargetSdkVersion(); if (oldTargetSdk > Build.VERSION_CODES.LOLLIPOP_MR1 @@ -1292,7 +1295,6 @@ final class InstallPackageHelper { } } - PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName); PackageSetting signatureCheckPs = ps; // SDK libs can have other major versions with different package names. @@ -1368,8 +1370,8 @@ final class InstallPackageHelper { if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps); systemApp = ps.isSystem(); - request.setOriginUsers( - ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true)); + request.setOriginUsers(ps.queryUsersInstalledOrHasData( + mPm.mUserManager.getUserIds())); } final int numGroups = ArrayUtils.size(parsedPackage.getPermissionGroups()); @@ -1595,7 +1597,7 @@ final class InstallPackageHelper { boolean shouldCloseFreezerBeforeReturn = true; try { - final PackageState oldPackageState; + final PackageSetting oldPackageState; final AndroidPackage oldPackage; String renamedPackage; boolean sysPkg = false; @@ -1648,7 +1650,7 @@ final class InstallPackageHelper { } } else { SigningDetails parsedPkgSigningDetails = parsedPackage.getSigningDetails(); - SigningDetails oldPkgSigningDetails = oldPackage.getSigningDetails(); + SigningDetails oldPkgSigningDetails = oldPackageState.getSigningDetails(); // default to original signature matching if (!parsedPkgSigningDetails.checkCapability(oldPkgSigningDetails, SigningDetails.CertCapabilities.INSTALLED_DATA) @@ -1668,7 +1670,8 @@ final class InstallPackageHelper { } // don't allow a system upgrade unless the upgrade hash matches - if (oldPackage.getRestrictUpdateHash() != null && oldPackageState.isSystem()) { + if (oldPackage != null && oldPackage.getRestrictUpdateHash() != null + && oldPackageState.isSystem()) { final byte[] digestBytes; try { final MessageDigest digest = MessageDigest.getInstance("SHA-512"); @@ -1691,23 +1694,26 @@ final class InstallPackageHelper { parsedPackage.setRestrictUpdateHash(oldPackage.getRestrictUpdateHash()); } - // APK should not change its sharedUserId declarations - final var oldSharedUid = oldPackage.getSharedUserId() != null - ? oldPackage.getSharedUserId() : "<nothing>"; - final var newSharedUid = parsedPackage.getSharedUserId() != null - ? parsedPackage.getSharedUserId() : "<nothing>"; - if (!oldSharedUid.equals(newSharedUid)) { - throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED, - "Package " + parsedPackage.getPackageName() - + " shared user changed from " - + oldSharedUid + " to " + newSharedUid); - } + if (oldPackage != null) { + // APK should not change its sharedUserId declarations + final var oldSharedUid = oldPackage.getSharedUserId() != null + ? oldPackage.getSharedUserId() : "<nothing>"; + final var newSharedUid = parsedPackage.getSharedUserId() != null + ? parsedPackage.getSharedUserId() : "<nothing>"; + if (!oldSharedUid.equals(newSharedUid)) { + throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED, + "Package " + parsedPackage.getPackageName() + + " shared user changed from " + + oldSharedUid + " to " + newSharedUid); + } - // APK should not re-join shared UID - if (oldPackage.isLeavingSharedUser() && !parsedPackage.isLeavingSharedUser()) { - throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED, - "Package " + parsedPackage.getPackageName() - + " attempting to rejoin " + newSharedUid); + // APK should not re-join shared UID + if (oldPackage.isLeavingSharedUser() + && !parsedPackage.isLeavingSharedUser()) { + throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED, + "Package " + parsedPackage.getPackageName() + + " attempting to rejoin " + newSharedUid); + } } // In case of rollback, remember per-user/profile install state @@ -1740,8 +1746,8 @@ final class InstallPackageHelper { // Update what is removed PackageRemovedInfo removedInfo = new PackageRemovedInfo(mPm); - removedInfo.mUid = oldPackage.getUid(); - removedInfo.mRemovedPackage = oldPackage.getPackageName(); + removedInfo.mUid = ps.getAppId(); + removedInfo.mRemovedPackage = ps.getPackageName(); removedInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName; removedInfo.mIsStaticSharedLib = @@ -1760,8 +1766,8 @@ final class InstallPackageHelper { removedInfo.mUninstallReasons.put(userId, ps.getUninstallReason(userId)); } - removedInfo.mIsExternal = oldPackage.isExternalStorage(); - removedInfo.mRemovedPackageVersionCode = oldPackage.getLongVersionCode(); + removedInfo.mIsExternal = oldPackageState.isExternalStorage(); + removedInfo.mRemovedPackageVersionCode = oldPackageState.getVersionCode(); request.setRemovedInfo(removedInfo); sysPkg = oldPackageState.isSystem(); @@ -1801,7 +1807,7 @@ final class InstallPackageHelper { } else { // new package install ps = null; disabledPs = null; - oldPackage = null; + oldPackageState = null; // Remember this for later, in case we need to rollback this install String pkgName1 = parsedPackage.getPackageName(); @@ -1832,8 +1838,8 @@ final class InstallPackageHelper { shouldCloseFreezerBeforeReturn = false; request.setPrepareResult(replace, targetScanFlags, targetParseFlags, - oldPackage, parsedPackage, replace /* clearCodeCache */, sysPkg, - ps, disabledPs); + oldPackageState, parsedPackage, archivedPackage, + replace /* clearCodeCache */, sysPkg, ps, disabledPs); } finally { request.setFreezer(freezer); if (shouldCloseFreezerBeforeReturn) { @@ -2077,7 +2083,7 @@ final class InstallPackageHelper { // Set the update and install times PackageStateInternal deletedPkgSetting = mPm.snapshotComputer() - .getPackageStateInternal(oldPackage.getPackageName()); + .getPackageStateInternal(packageName); // TODO(b/225756739): For rebootless APEX, consider using lastUpdateMillis provided // by apexd to be more accurate. installRequest.setScannedPackageSettingFirstInstallTimeFromReplaced( @@ -2126,8 +2132,10 @@ final class InstallPackageHelper { if (oldCodePaths == null) { oldCodePaths = new ArraySet<>(); } - Collections.addAll(oldCodePaths, oldPackage.getBaseApkPath()); - Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths()); + if (oldPackage != null) { + Collections.addAll(oldCodePaths, oldPackage.getBaseApkPath()); + Collections.addAll(oldCodePaths, oldPackage.getSplitCodePaths()); + } ps1.setOldCodePaths(oldCodePaths); } else { ps1.setOldCodePaths(null); @@ -2161,6 +2169,9 @@ final class InstallPackageHelper { } } if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) { + mPm.createArchiveStateIfNeeded(ps, + installRequest.getArchivedPackage(), + installRequest.getNewUsers()); mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers()); mPm.updateInstantAppInstallerLocked(packageName); } @@ -2852,46 +2863,11 @@ final class InstallPackageHelper { mPm.notifyInstantAppPackageInstalled(request.getPkg().getPackageName(), request.getNewUsers()); - // Determine the set of users who are adding this package for - // the first time vs. those who are seeing an update. - int[] firstUserIds = EMPTY_INT_ARRAY; - int[] firstInstantUserIds = EMPTY_INT_ARRAY; - int[] updateUserIds = EMPTY_INT_ARRAY; - int[] instantUserIds = EMPTY_INT_ARRAY; - final boolean allNewUsers = request.getOriginUsers() == null - || request.getOriginUsers().length == 0; - for (int newUser : request.getNewUsers()) { - final boolean isInstantApp = pkgSetting.getUserStateOrDefault(newUser) - .isInstantApp(); - if (allNewUsers) { - if (isInstantApp) { - firstInstantUserIds = ArrayUtils.appendInt(firstInstantUserIds, newUser); - } else { - firstUserIds = ArrayUtils.appendInt(firstUserIds, newUser); - } - continue; - } - boolean isNew = true; - for (int origUser : request.getOriginUsers()) { - if (origUser == newUser) { - isNew = false; - break; - } - } - if (isNew) { - if (isInstantApp) { - firstInstantUserIds = ArrayUtils.appendInt(firstInstantUserIds, newUser); - } else { - firstUserIds = ArrayUtils.appendInt(firstUserIds, newUser); - } - } else { - if (isInstantApp) { - instantUserIds = ArrayUtils.appendInt(instantUserIds, newUser); - } else { - updateUserIds = ArrayUtils.appendInt(updateUserIds, newUser); - } - } - } + request.populateBroadcastUsers(); + final int[] firstUserIds = request.getFirstTimeBroadcastUserIds(); + final int[] firstInstantUserIds = request.getFirstTimeBroadcastInstantUserIds(); + final int[] updateUserIds = request.getUpdateBroadcastUserIds(); + final int[] instantUserIds = request.getUpdateBroadcastInstantUserIds(); Bundle extras = new Bundle(); extras.putInt(Intent.EXTRA_UID, request.getAppId()); @@ -2969,12 +2945,10 @@ final class InstallPackageHelper { } // If package installer is defined, notify package installer about new // app installed - if (mPm.mRequiredInstallerPackage != null) { - mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, - extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/, - mPm.mRequiredInstallerPackage, null /*finishedReceiver*/, - firstUserIds, instantUserIds, null /* broadcastAllowList */, null); - } + mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, + extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/, + mPm.mRequiredInstallerPackage, null /*finishedReceiver*/, + firstUserIds, instantUserIds, null /* broadcastAllowList */, null); // Send replaced for users that don't see the package for the first time if (update) { @@ -3070,7 +3044,7 @@ final class InstallPackageHelper { } } - if (allNewUsers && !update) { + if (request.isAllNewUsers() && !update) { mPm.notifyPackageAdded(packageName, request.getAppId()); } else { mPm.notifyPackageChanged(packageName, request.getAppId()); diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index e1cfc418bfe5..ff347acdfd96 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -21,6 +21,8 @@ import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN; import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; import static android.os.Process.INVALID_UID; + +import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY; import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP; import static com.android.server.pm.PackageManagerService.TAG; @@ -28,6 +30,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.apex.ApexInfo; import android.app.AppOpsManager; +import android.content.pm.ArchivedPackageParcel; import android.content.pm.DataLoaderType; import android.content.pm.IPackageInstallObserver2; import android.content.pm.PackageInstaller; @@ -43,6 +46,7 @@ import android.util.ArrayMap; import android.util.ExceptionUtils; import android.util.Slog; +import com.android.internal.util.ArrayUtils; import com.android.server.art.model.DexoptResult; import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; @@ -52,6 +56,7 @@ import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; final class InstallRequest { @@ -69,11 +74,13 @@ final class InstallRequest { private int mParseFlags; private boolean mReplace; - @Nullable /* The original Package if it is being replaced, otherwise {@code null} */ - private AndroidPackage mExistingPackage; + @Nullable /* The original package's name if it is being replaced, otherwise {@code null} */ + private String mExistingPackageName; /** parsed package to be scanned */ @Nullable private ParsedPackage mParsedPackage; + @Nullable + private ArchivedPackageParcel mArchivedPackage; private boolean mClearCodeCache; private boolean mSystem; @Nullable @@ -132,6 +139,15 @@ final class InstallRequest { private int mDexoptStatus; + @NonNull + private int[] mFirstTimeBroadcastUserIds = EMPTY_INT_ARRAY; + @NonNull + private int[] mFirstTimeBroadcastInstantUserIds = EMPTY_INT_ARRAY; + @NonNull + private int[] mUpdateBroadcastUserIds = EMPTY_INT_ARRAY; + @NonNull + private int[] mUpdateBroadcastInstantUserIds = EMPTY_INT_ARRAY; + // New install InstallRequest(InstallingSession params) { mUserId = params.getUser().getIdentifier(); @@ -177,6 +193,7 @@ final class InstallRequest { } mInstallArgs = null; mParsedPackage = parsedPackage; + mArchivedPackage = null; mParseFlags = parseFlags; mScanFlags = scanFlags; mScanResult = scanResult; @@ -413,11 +430,6 @@ final class InstallRequest { } @Nullable - public AndroidPackage getExistingPackage() { - return mExistingPackage; - } - - @Nullable public List<String> getAllowlistedRestrictedPermissions() { return mInstallArgs == null ? null : mInstallArgs.mAllowlistedRestrictedPermissions; } @@ -441,6 +453,9 @@ final class InstallRequest { return mParsedPackage; } + @Nullable + public ArchivedPackageParcel getArchivedPackage() { return mArchivedPackage; } + @ParsingPackageUtils.ParseFlags public int getParseFlags() { return mParseFlags; @@ -453,10 +468,7 @@ final class InstallRequest { @Nullable public String getExistingPackageName() { - if (mExistingPackage != null) { - return mExistingPackage.getPackageName(); - } - return null; + return mExistingPackageName; } @Nullable @@ -627,6 +639,25 @@ final class InstallRequest { return mDexoptStatus; } + public boolean isAllNewUsers() { + return mOrigUsers == null || mOrigUsers.length == 0; + } + public int[] getFirstTimeBroadcastUserIds() { + return mFirstTimeBroadcastUserIds; + } + + public int[] getFirstTimeBroadcastInstantUserIds() { + return mFirstTimeBroadcastInstantUserIds; + } + + public int[] getUpdateBroadcastUserIds() { + return mUpdateBroadcastUserIds; + } + + public int[] getUpdateBroadcastInstantUserIds() { + return mUpdateBroadcastInstantUserIds; + } + public void setScanFlags(int scanFlags) { mScanFlags = scanFlags; } @@ -729,14 +760,17 @@ final class InstallRequest { } public void setPrepareResult(boolean replace, int scanFlags, - int parseFlags, AndroidPackage existingPackage, - ParsedPackage packageToScan, boolean clearCodeCache, boolean system, + int parseFlags, PackageState existingPackageState, + ParsedPackage packageToScan, ArchivedPackageParcel archivedPackage, + boolean clearCodeCache, boolean system, PackageSetting originalPs, PackageSetting disabledPs) { mReplace = replace; mScanFlags = scanFlags; mParseFlags = parseFlags; - mExistingPackage = existingPackage; + mExistingPackageName = + existingPackageState != null ? existingPackageState.getPackageName() : null; mParsedPackage = packageToScan; + mArchivedPackage = archivedPackage; mClearCodeCache = clearCodeCache; mSystem = system; mOriginalPs = originalPs; @@ -769,6 +803,58 @@ final class InstallRequest { } } + /** + * Determine the set of users who are adding this package for the first time vs. those who are + * seeing an update. + */ + public void populateBroadcastUsers() { + assertScanResultExists(); + mFirstTimeBroadcastUserIds = EMPTY_INT_ARRAY; + mFirstTimeBroadcastInstantUserIds = EMPTY_INT_ARRAY; + mUpdateBroadcastUserIds = EMPTY_INT_ARRAY; + mUpdateBroadcastInstantUserIds = EMPTY_INT_ARRAY; + + final boolean allNewUsers = isAllNewUsers(); + if (allNewUsers) { + // App was not currently installed on any user + for (int newUser : mNewUsers) { + final boolean isInstantApp = + mScanResult.mPkgSetting.getUserStateOrDefault(newUser).isInstantApp(); + if (isInstantApp) { + mFirstTimeBroadcastInstantUserIds = + ArrayUtils.appendInt(mFirstTimeBroadcastInstantUserIds, newUser); + } else { + mFirstTimeBroadcastUserIds = + ArrayUtils.appendInt(mFirstTimeBroadcastUserIds, newUser); + } + } + return; + } + // App was already installed on some users, but is new to some other users + for (int newUser : mNewUsers) { + boolean isFirstTimeUser = !ArrayUtils.contains(mOrigUsers, newUser); + final boolean isInstantApp = + mScanResult.mPkgSetting.getUserStateOrDefault(newUser).isInstantApp(); + if (isFirstTimeUser) { + if (isInstantApp) { + mFirstTimeBroadcastInstantUserIds = + ArrayUtils.appendInt(mFirstTimeBroadcastInstantUserIds, newUser); + } else { + mFirstTimeBroadcastUserIds = + ArrayUtils.appendInt(mFirstTimeBroadcastUserIds, newUser); + } + } else { + if (isInstantApp) { + mUpdateBroadcastInstantUserIds = + ArrayUtils.appendInt(mUpdateBroadcastInstantUserIds, newUser); + } else { + mUpdateBroadcastUserIds = + ArrayUtils.appendInt(mUpdateBroadcastUserIds, newUser); + } + } + } + } + public void onPrepareStarted() { if (mPackageMetrics != null) { mPackageMetrics.onStepStarted(PackageMetrics.STEP_PREPARE); diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 6233c9bc4dad..0ebd33b9cd81 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -257,6 +257,7 @@ public class Installer extends SystemService { private static CreateAppDataResult buildPlaceholderCreateAppDataResult() { final CreateAppDataResult result = new CreateAppDataResult(); result.ceDataInode = -1; + result.deDataInode = -1; result.exceptionCode = 0; result.exceptionMessage = null; return result; @@ -361,7 +362,7 @@ public class Installer extends SystemService { private boolean mExecuted; private final List<CreateAppDataArgs> mArgs = new ArrayList<>(); - private final List<CompletableFuture<Long>> mFutures = new ArrayList<>(); + private final List<CompletableFuture<CreateAppDataResult>> mFutures = new ArrayList<>(); /** * Enqueue the given {@code installd} operation to be executed in the @@ -371,11 +372,12 @@ public class Installer extends SystemService { * {@link Installer} object. */ @NonNull - public synchronized CompletableFuture<Long> createAppData(CreateAppDataArgs args) { + public synchronized CompletableFuture<CreateAppDataResult> createAppData( + CreateAppDataArgs args) { if (mExecuted) { throw new IllegalStateException(); } - final CompletableFuture<Long> future = new CompletableFuture<>(); + final CompletableFuture<CreateAppDataResult> future = new CompletableFuture<>(); mArgs.add(args); mFutures.add(future); return future; @@ -402,9 +404,9 @@ public class Installer extends SystemService { final CreateAppDataResult[] results = installer.createAppDataBatched(args); for (int j = 0; j < results.length; j++) { final CreateAppDataResult result = results[j]; - final CompletableFuture<Long> future = mFutures.get(i + j); + final CompletableFuture<CreateAppDataResult> future = mFutures.get(i + j); if (result.exceptionCode == 0) { - future.complete(result.ceDataInode); + future.complete(result); } else { future.completeExceptionally( new InstallerException(result.exceptionMessage)); diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 64cdca3b5784..e8be748a85d1 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -31,12 +31,15 @@ import android.app.BroadcastOptions; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.pm.ArchivedActivityParcel; +import android.content.pm.ArchivedPackageParcel; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -56,6 +59,7 @@ import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserStateInternal; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -158,7 +162,8 @@ public class PackageArchiver { String responsibleInstallerPackage = getResponsibleInstallerPackage(ps); verifyInstaller(responsibleInstallerPackage); - List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps, userId); + List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps.getPackageName(), + userId); final CompletableFuture<ArchiveState> archiveState = new CompletableFuture<>(); mPm.mHandler.post(() -> { try { @@ -172,13 +177,34 @@ public class PackageArchiver { return archiveState; } - private ArchiveState createArchiveStateInternal(String packageName, int userId, + static ArchiveState createArchiveState(@NonNull ArchivedPackageParcel archivedPackage, + int userId, String installerPackage) { + try { + var packageName = archivedPackage.packageName; + var mainActivities = archivedPackage.archivedActivities; + List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.length); + for (int i = 0, size = mainActivities.length; i < size; ++i) { + var mainActivity = mainActivities[i]; + Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i); + ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( + mainActivity.title, iconPath, null); + archiveActivityInfos.add(activityInfo); + } + + return new ArchiveState(archiveActivityInfos, installerPackage); + } catch (IOException e) { + Slog.e(TAG, "Failed to create archive state", e); + return null; + } + } + + ArchiveState createArchiveStateInternal(String packageName, int userId, List<LauncherActivityInfo> mainActivities, String installerPackage) throws IOException { - List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(); - for (int i = 0; i < mainActivities.size(); i++) { + List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size()); + for (int i = 0, size = mainActivities.size(); i < size; i++) { LauncherActivityInfo mainActivity = mainActivities.get(i); - Path iconPath = storeIcon(packageName, mainActivity, userId); + Path iconPath = storeIcon(packageName, mainActivity, userId, i); ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( mainActivity.getLabel().toString(), iconPath, null); archiveActivityInfos.add(activityInfo); @@ -188,17 +214,30 @@ public class PackageArchiver { } // TODO(b/298452477) Handle monochrome icons. + private static Path storeIconForParcel(String packageName, ArchivedActivityParcel mainActivity, + @UserIdInt int userId, int index) throws IOException { + if (mainActivity.iconBitmap == null) { + return null; + } + File iconsDir = createIconsDir(userId); + File iconFile = new File(iconsDir, packageName + "-" + index + ".png"); + try (FileOutputStream out = new FileOutputStream(iconFile)) { + out.write(mainActivity.iconBitmap); + out.flush(); + } + return iconFile.toPath(); + } + @VisibleForTesting Path storeIcon(String packageName, LauncherActivityInfo mainActivity, - @UserIdInt int userId) - throws IOException { + @UserIdInt int userId, int index) throws IOException { int iconResourceId = mainActivity.getActivityInfo().getIconResource(); if (iconResourceId == 0) { // The app doesn't define an icon. No need to store anything. return null; } File iconsDir = createIconsDir(userId); - File iconFile = new File(iconsDir, packageName + "-" + mainActivity.getName() + ".png"); + File iconFile = new File(iconsDir, packageName + "-" + index + ".png"); Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0)); try (FileOutputStream out = new FileOutputStream(iconFile)) { // Note: Quality is ignored for PNGs. @@ -228,6 +267,9 @@ public class PackageArchiver { */ public boolean verifySupportsUnarchival(String installerPackage) { // TODO(b/278553670) Check if installerPackage supports unarchival. + if (TextUtils.isEmpty(installerPackage)) { + return false; + } return true; } @@ -265,6 +307,46 @@ public class PackageArchiver { mPm.mHandler.post(() -> unarchiveInternal(packageName, userHandle, installerPackage)); } + /** + * Returns the icon of an archived app. This is the icon of the main activity of the app. + * + * <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple + * launcher activities, only one of the icons is returned arbitrarily. + */ + public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) { + Objects.requireNonNull(packageName); + Objects.requireNonNull(user); + + Computer snapshot = mPm.snapshotComputer(); + int callingUid = Binder.getCallingUid(); + int userId = user.getIdentifier(); + PackageStateInternal ps; + try { + ps = getPackageState(packageName, snapshot, callingUid, userId); + snapshot.enforceCrossUserPermission(callingUid, userId, true, false, + "getArchivedAppIcon"); + verifyArchived(ps, userId); + } catch (PackageManager.NameNotFoundException e) { + throw new ParcelableException(e); + } + + List<ArchiveActivityInfo> activityInfos = ps.getUserStateOrDefault( + userId).getArchiveState().getActivityInfos(); + if (activityInfos.size() == 0) { + return null; + } + + // TODO(b/298452477) Handle monochrome icons. + // In the rare case the archived app defined more than two launcher activities, we choose + // the first one arbitrarily. + return decodeIcon(activityInfos.get(0)); + } + + @VisibleForTesting + Bitmap decodeIcon(ArchiveActivityInfo archiveActivityInfo) { + return BitmapFactory.decodeFile(archiveActivityInfo.getIconBitmap().toString()); + } + private void verifyArchived(PackageStateInternal ps, int userId) throws PackageManager.NameNotFoundException { PackageUserStateInternal userState = ps.getUserStateOrDefault(userId); @@ -310,16 +392,16 @@ public class PackageArchiver { /* initialExtras= */ null); } - private List<LauncherActivityInfo> getLauncherActivityInfos(PackageStateInternal ps, + List<LauncherActivityInfo> getLauncherActivityInfos(String packageName, int userId) throws PackageManager.NameNotFoundException { List<LauncherActivityInfo> mainActivities = Binder.withCleanCallingIdentity(() -> getLauncherApps().getActivityList( - ps.getPackageName(), + packageName, new UserHandle(userId))); if (mainActivities.isEmpty()) { throw new PackageManager.NameNotFoundException( TextUtils.formatSimple("The app %s does not have a main activity.", - ps.getPackageName())); + packageName)); } return mainActivities; @@ -340,7 +422,7 @@ public class PackageArchiver { return DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS; } - private String getResponsibleInstallerPackage(PackageStateInternal ps) { + static String getResponsibleInstallerPackage(PackageStateInternal ps) { return TextUtils.isEmpty(ps.getInstallSource().mUpdateOwnerPackageName) ? ps.getInstallSource().mInstallerPackageName : ps.getInstallSource().mUpdateOwnerPackageName; @@ -423,7 +505,7 @@ public class PackageArchiver { } } - private File createIconsDir(@UserIdInt int userId) throws IOException { + private static File createIconsDir(@UserIdInt int userId) throws IOException { File iconsDir = getIconsDir(userId); if (!iconsDir.isDirectory()) { iconsDir.delete(); @@ -436,7 +518,7 @@ public class PackageArchiver { return iconsDir; } - private File getIconsDir(int userId) { + private static File getIconsDir(int userId) { return new File(Environment.getDataSystemCeDirectory(userId), ARCHIVE_ICONS_DIR); } @@ -462,4 +544,90 @@ public class PackageArchiver { drawable.draw(canvas); return bitmap; } + + private static byte[] bytesFromBitmapFile(Path path) throws IOException { + if (path == null) { + return null; + } + // Technically we could just read the bytes, but we want to be sure we store the + // right format. + return bytesFromBitmap(BitmapFactory.decodeFile(path.toString())); + } + + private static byte[] bytesFromBitmap(Bitmap bitmap) throws IOException { + if (bitmap == null) { + return null; + } + + try (ByteArrayOutputStream baos = new ByteArrayOutputStream( + bitmap.getByteCount())) { + bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); + return baos.toByteArray(); + } + } + + /** + * Creates serializable archived activities from existing ArchiveState. + */ + static ArchivedActivityParcel[] createArchivedActivities(ArchiveState archiveState) + throws IOException { + var infos = archiveState.getActivityInfos(); + if (infos == null || infos.isEmpty()) { + throw new IllegalArgumentException("No activities in archive state"); + } + + List<ArchivedActivityParcel> activities = new ArrayList<>(infos.size()); + for (int i = 0, size = infos.size(); i < size; ++i) { + var info = infos.get(i); + if (info == null) { + continue; + } + var archivedActivity = new ArchivedActivityParcel(); + archivedActivity.title = info.getTitle(); + archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap()); + archivedActivity.monochromeIconBitmap = bytesFromBitmapFile( + info.getMonochromeIconBitmap()); + activities.add(archivedActivity); + } + + if (activities.isEmpty()) { + throw new IllegalArgumentException( + "Failed to extract title and icon of main activities"); + } + + return activities.toArray(new ArchivedActivityParcel[activities.size()]); + } + + /** + * Creates serializable archived activities from launcher activities. + */ + static ArchivedActivityParcel[] createArchivedActivities(List<LauncherActivityInfo> infos) + throws IOException { + if (infos == null || infos.isEmpty()) { + throw new IllegalArgumentException("No launcher activities"); + } + + List<ArchivedActivityParcel> activities = new ArrayList<>(infos.size()); + for (int i = 0, size = infos.size(); i < size; ++i) { + var info = infos.get(i); + if (info == null) { + continue; + } + var archivedActivity = new ArchivedActivityParcel(); + archivedActivity.title = info.getLabel().toString(); + archivedActivity.iconBitmap = + info.getActivityInfo().getIconResource() == 0 ? null : bytesFromBitmap( + drawableToBitmap(info.getIcon(/* density= */ 0))); + // TODO(b/298452477) Handle monochrome icons. + archivedActivity.monochromeIconBitmap = null; + activities.add(archivedActivity); + } + + if (activities.isEmpty()) { + throw new IllegalArgumentException( + "Failed to extract title and icon of main activities"); + } + + return activities.toArray(new ArchivedActivityParcel[activities.size()]); + } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 9e0a83cda584..512d338b0b48 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -40,6 +40,7 @@ import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_WRONLY; + import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.internal.util.XmlUtils.readBitmapAttribute; import static com.android.internal.util.XmlUtils.readByteArrayAttribute; @@ -1165,11 +1166,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new IllegalArgumentException( "Archived installation can only use Streaming System DataLoader."); } - if (!TextUtils.isEmpty(params.appPackageName) && !isArchivedInstallationAllowed( - params.appPackageName)) { - throw new IllegalArgumentException( - "Archived installation of this package is not allowed."); - } } } @@ -1548,6 +1544,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } var archPkg = metadata.getArchivedPackage(); + if (archPkg == null) { + throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE, + "Metadata does not contain ArchivedPackage: " + file); + } if (archPkg.packageName == null || archPkg.signingDetails == null) { throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE, "ArchivedPackage does not contain required info: " + file); @@ -3395,8 +3395,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { "Archived installation of this package is not allowed."); } - if (!isInstalledByAdb(getInstallSource().mInitiatingPackageName) - && !mPm.mInstallerService.mPackageArchiver.verifySupportsUnarchival( + if (!mPm.mInstallerService.mPackageArchiver.verifySupportsUnarchival( getInstallSource().mInstallerPackageName)) { throw new PackageManagerException( PackageManager.INSTALL_FAILED_SESSION_INVALID, @@ -3641,6 +3640,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private void maybeStageFsveritySignatureLocked(File origFile, File targetFile, boolean fsVerityRequired) throws PackageManagerException { + if (android.security.Flags.deprecateFsvSig()) { + return; + } final File originalSignature = new File( VerityUtils.getFsveritySignatureFilePath(origFile.getPath())); if (originalSignature.exists()) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index d23dcbc5f18e..864ed323697c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -36,6 +36,7 @@ import static android.os.storage.StorageManager.FLAG_STORAGE_CE; import static android.os.storage.StorageManager.FLAG_STORAGE_DE; import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL; import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE; + import static com.android.internal.annotations.VisibleForTesting.Visibility; import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME; import static com.android.server.pm.DexOptHelper.useArtService; @@ -116,11 +117,7 @@ import android.content.pm.UserPackage; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.VersionedPackage; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.parsing.ApkLite; -import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; -import android.content.pm.parsing.result.ParseResult; -import android.content.pm.parsing.result.ParseTypeImpl; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Bitmap; @@ -228,6 +225,7 @@ import com.android.server.pm.permission.LegacyPermissionSettings; import com.android.server.pm.permission.PermissionManagerService; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.ArchiveState; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserState; @@ -1438,6 +1436,92 @@ public class PackageManagerService implements PackageSender, TestUtilityService return extras; } + ArchivedPackageParcel getArchivedPackageInternal(@NonNull String packageName, int userId) { + Objects.requireNonNull(packageName); + int binderUid = Binder.getCallingUid(); + + Computer snapshot = snapshotComputer(); + snapshot.enforceCrossUserPermission(binderUid, userId, true, true, + "getArchivedPackage"); + + ArchivedPackageParcel archPkg = new ArchivedPackageParcel(); + archPkg.packageName = packageName; + + ArchiveState archiveState; + synchronized (mLock) { + PackageSetting ps = mSettings.getPackageLPr(packageName); + if (ps == null) { + return null; + } + var psi = ps.getUserStateOrDefault(userId); + archiveState = psi.getArchiveState(); + if (archiveState == null && !psi.isInstalled()) { + return null; + } + + archPkg.signingDetails = ps.getSigningDetails(); + + long longVersionCode = ps.getVersionCode(); + archPkg.versionCodeMajor = (int) (longVersionCode >> 32); + archPkg.versionCode = (int) longVersionCode; + + // TODO(b/297916136): extract target sdk version. + archPkg.targetSdkVersion = MIN_INSTALLABLE_TARGET_SDK; + + // These get translated in flags important for user data management. + archPkg.defaultToDeviceProtectedStorage = String.valueOf( + ps.isDefaultToDeviceProtectedStorage()); + archPkg.requestLegacyExternalStorage = String.valueOf( + ps.isRequestLegacyExternalStorage()); + archPkg.userDataFragile = String.valueOf(ps.isUserDataFragile()); + } + + try { + if (archiveState != null) { + archPkg.archivedActivities = PackageArchiver.createArchivedActivities( + archiveState); + } else { + var mainActivities = + mInstallerService.mPackageArchiver.getLauncherActivityInfos(packageName, + userId); + archPkg.archivedActivities = PackageArchiver.createArchivedActivities( + mainActivities); + } + } catch (Exception e) { + throw new IllegalArgumentException("Package does not have a main activity", e); + } + + return archPkg; + } + + void createArchiveStateIfNeeded(PackageSetting pkgSetting, ArchivedPackageParcel archivePackage, + int[] userIds) { + if (pkgSetting == null || archivePackage == null + || archivePackage.archivedActivities == null || userIds == null + || userIds.length == 0) { + return; + } + + String responsibleInstallerPackage = PackageArchiver.getResponsibleInstallerPackage( + pkgSetting); + // TODO(b/278553670) Check if responsibleInstallerPackage supports unarchival. + if (TextUtils.isEmpty(responsibleInstallerPackage)) { + Slog.e(TAG, "Can't create archive state: responsible installer is empty"); + return; + } + for (int userId : userIds) { + var archiveState = PackageArchiver.createArchiveState(archivePackage, userId, + responsibleInstallerPackage); + if (archiveState == null) { + continue; + } + pkgSetting + .modifyUserState(userId) + .setArchiveState(archiveState); + } + } + + void scheduleWriteSettings() { // We normally invalidate when we write settings, but in cases where we delay and // coalesce settings writes, this strategy would have us invalidate the cache too late. @@ -6301,35 +6385,13 @@ public class PackageManagerService implements PackageSender, TestUtilityService } @Override - public ArchivedPackageParcel getArchivedPackage(String apkPath) { - ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); - ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(), - new File(apkPath), ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES); - if (result.isError()) { - throw new IllegalArgumentException(result.getErrorMessage(), result.getException()); - } - final ApkLite apk = result.getResult(); - - ArchivedPackageParcel archPkg = new ArchivedPackageParcel(); - archPkg.packageName = apk.getPackageName(); - archPkg.signingDetails = apk.getSigningDetails(); - - archPkg.versionCodeMajor = apk.getVersionCodeMajor(); - archPkg.versionCode = apk.getVersionCode(); - - archPkg.targetSdkVersion = apk.getTargetSdkVersion(); - - // These get translated in flags important for user data management. - archPkg.backupAllowed = String.valueOf(apk.isBackupAllowed()); - archPkg.defaultToDeviceProtectedStorage = String.valueOf( - apk.isDefaultToDeviceProtectedStorage()); - archPkg.requestLegacyExternalStorage = String.valueOf( - apk.isRequestLegacyExternalStorage()); - archPkg.userDataFragile = String.valueOf(apk.isUserDataFragile()); - archPkg.clearUserDataOnFailedRestoreAllowed = String.valueOf( - apk.isClearUserDataOnFailedRestoreAllowed()); + public ArchivedPackageParcel getArchivedPackage(@NonNull String packageName, int userId) { + return getArchivedPackageInternal(packageName, userId); + } - return archPkg; + @Override + public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) { + return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user); } /** diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 1679987211a8..38f241d40d98 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -547,6 +547,9 @@ public class PackageManagerServiceUtils { /** Returns true if standard APK Verity is enabled. */ static boolean isApkVerityEnabled() { + if (android.security.Flags.deprecateFsvSig()) { + return false; + } return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.R || SystemProperties.getInt("ro.apk_verity.mode", FSVERITY_DISABLED) == FSVERITY_ENABLED; diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 8d8208599998..1b30c4b82d82 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -26,6 +26,7 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; import static android.content.pm.PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS; import static android.content.pm.PackageManager.RESTRICTION_HIDE_NOTIFICATIONS; import static android.content.pm.PackageManager.RESTRICTION_NONE; + import static com.android.server.LocalManagerRegistry.ManagerNotFoundException; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; @@ -82,9 +83,9 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; -import android.os.Environment; import android.os.IBinder; import android.os.IUserManager; +import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.AutoCloseInputStream; import android.os.PersistableBundle; @@ -130,6 +131,7 @@ import dalvik.system.DexFile; import libcore.io.IoUtils; import libcore.io.Streams; +import libcore.util.HexEncoding; import java.io.BufferedReader; import java.io.File; @@ -248,8 +250,6 @@ class PackageManagerShellCommand extends ShellCommand { return runStreamingInstall(); case "install-incremental": return runIncrementalInstall(); - case "install-archived": - return runArchivedInstall(); case "install-abandon": case "install-destroy": return runInstallAbandon(); @@ -277,6 +277,10 @@ class PackageManagerShellCommand extends ShellCommand { return runUninstall(); case "clear": return runClear(); + case "get-archived-package-metadata": + return runGetArchivedPackageMetadata(); + case "install-archived": + return runArchivedInstall(); case "enable": return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED); case "disable": @@ -1808,6 +1812,56 @@ class PackageManagerShellCommand extends ShellCommand { return doRemoveSplits(sessionId, splitNames, true /*logSuccess*/); } + private int runGetArchivedPackageMetadata() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + int userId = UserHandle.USER_CURRENT; + + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "--user": + userId = UserHandle.parseUserArg(getNextArgRequired()); + break; + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + + final String packageName = getNextArg(); + if (packageName == null) { + pw.println("Error: package name not specified"); + return 1; + } + final int translatedUserId = translateUserId(userId, UserHandle.USER_NULL, + "runGetArchivedPackageMetadata"); + + try { + var archivedPackage = mInterface.getArchivedPackage(packageName, translatedUserId); + if (archivedPackage == null) { + pw.write("Package not found " + packageName); + return -1; + } + + Parcel parcel = Parcel.obtain(); + byte[] bytes; + try { + parcel.writeParcelable(archivedPackage, 0); + bytes = parcel.marshall(); + } finally { + parcel.recycle(); + } + + String encoded = HexEncoding.encodeToString(bytes); + pw.write(encoded); + } catch (Exception e) { + getErrPrintWriter().println("Failed to get archived package, reason: " + e); + pw.println("Failure [failed to get archived package], reason: " + e); + return -1; + } + return 0; + } + private int runInstallExisting() throws RemoteException { final PrintWriter pw = getOutPrintWriter(); int userId = UserHandle.USER_CURRENT; @@ -4146,28 +4200,24 @@ class PackageManagerShellCommand extends ShellCommand { throw new IllegalArgumentException("Error: Can't open file: " + inPath); } - File tmpFile = null; + final String encoded; final ParcelFileDescriptor fd = fdWithSize.first; + final int size = (int) (long) fdWithSize.second; try (InputStream inStream = new AutoCloseInputStream(fd)) { - final long identity = Binder.clearCallingIdentity(); - try { - File tmpStagingDir = Environment.getDataAppDirectory(null); - tmpFile = new File(tmpStagingDir, "tmdl" + RANDOM.nextInt() + ".tmp"); - - try (OutputStream outStream = new FileOutputStream(tmpFile)) { - Streams.copy(inStream, outStream); - } - - return mInterface.getArchivedPackage(tmpFile.getAbsolutePath()); - } finally { - if (tmpFile != null) { - tmpFile.delete(); - } - Binder.restoreCallingIdentity(identity); - } + byte[] bytes = new byte[size]; + Streams.readFully(inStream, bytes); + encoded = new String(bytes); } catch (IOException e) { - throw new IllegalArgumentException("Error: Can't stage file: " + inPath, e); + throw new IllegalArgumentException("Error: Can't load archived package from: " + inPath, + e); } + + var result = Metadata.readArchivedPackageParcel(HexEncoding.decode(encoded)); + if (result == null) { + throw new IllegalArgumentException( + "Error: Can't parse archived package from: " + inPath); + } + return result; } private void processArgForLocalFile(String arg, PackageInstaller.Session session, diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java index fbe5a51587fc..9e7f04351327 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java @@ -166,16 +166,7 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { /** @hide */ @VisibleForTesting public static Metadata forArchived(ArchivedPackageParcel archivedPackage) { - Parcel parcel = Parcel.obtain(); - byte[] bytes; - try { - parcel.writeParcelable(archivedPackage, 0); - bytes = parcel.marshall(); - } finally { - parcel.recycle(); - } - - return new Metadata(ARCHIVED, bytes, null); + return new Metadata(ARCHIVED, writeArchivedPackageParcel(archivedPackage), null); } static Metadata forDataOnlyStreaming(String fileId) { @@ -270,11 +261,14 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { if (getMode() != ARCHIVED) { throw new IllegalStateException("Not an archived package metadata."); } + return readArchivedPackageParcel(this.mData); + } + static ArchivedPackageParcel readArchivedPackageParcel(byte[] bytes) { Parcel parcel = Parcel.obtain(); ArchivedPackageParcel result; try { - parcel.unmarshall(this.mData, 0, this.mData.length); + parcel.unmarshall(bytes, 0, bytes.length); parcel.setDataPosition(0); result = parcel.readParcelable(ArchivedPackageParcel.class.getClassLoader()); } finally { @@ -282,6 +276,16 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { } return result; } + + static byte[] writeArchivedPackageParcel(ArchivedPackageParcel archivedPackage) { + Parcel parcel = Parcel.obtain(); + try { + parcel.writeParcelable(archivedPackage, 0); + return parcel.marshall(); + } finally { + parcel.recycle(); + } + } } private static class DataLoader implements DataLoaderService.DataLoader { diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 114f80d7998f..2e6006465bd9 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -654,6 +654,16 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return (getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0; } + public boolean isRequestLegacyExternalStorage() { + return (getPrivateFlags() & ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE) + != 0; + } + + public boolean isUserDataFragile() { + return (getPrivateFlags() & ApplicationInfo.PRIVATE_FLAG_HAS_FRAGILE_USER_DATA) + != 0; + } + public SigningDetails getSigningDetails() { return signatures.mSigningDetails; } @@ -844,15 +854,42 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return res; } + int[] queryUsersInstalledOrHasData(int[] users) { + int num = 0; + for (int user : users) { + if (getInstalled(user) || readUserState(user).dataExists()) { + num++; + } + } + int[] res = new int[num]; + num = 0; + for (int user : users) { + if (getInstalled(user) || readUserState(user).dataExists()) { + res[num] = user; + num++; + } + } + return res; + } + long getCeDataInode(int userId) { return readUserState(userId).getCeDataInode(); } + long getDeDataInode(int userId) { + return readUserState(userId).getDeDataInode(); + } + void setCeDataInode(long ceDataInode, int userId) { modifyUserState(userId).setCeDataInode(ceDataInode); onChanged(); } + void setDeDataInode(long deDataInode, int userId) { + modifyUserState(userId).setDeDataInode(deDataInode); + onChanged(); + } + boolean getStopped(int userId) { return readUserState(userId).isStopped(); } @@ -907,17 +944,18 @@ public class PackageSetting extends SettingBase implements PackageStateInternal onChanged(); } - void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped, - boolean notLaunched, boolean hidden, int distractionFlags, - ArrayMap<String, SuspendParams> suspendParams, boolean instantApp, - boolean virtualPreload, String lastDisableAppCaller, - ArraySet<String> enabledComponents, ArraySet<String> disabledComponents, - int installReason, int uninstallReason, - String harmfulAppWarning, String splashScreenTheme, - long firstInstallTime, int aspectRatio, ArchiveState archiveState) { + void setUserState(int userId, long ceDataInode, long deDataInode, int enabled, + boolean installed, boolean stopped, boolean notLaunched, boolean hidden, + int distractionFlags, ArrayMap<String, SuspendParams> suspendParams, + boolean instantApp, boolean virtualPreload, String lastDisableAppCaller, + ArraySet<String> enabledComponents, ArraySet<String> disabledComponents, + int installReason, int uninstallReason, + String harmfulAppWarning, String splashScreenTheme, + long firstInstallTime, int aspectRatio, ArchiveState archiveState) { modifyUserState(userId) .setSuspendParams(suspendParams) .setCeDataInode(ceDataInode) + .setDeDataInode(deDataInode) .setEnabledState(enabled) .setInstalled(installed) .setStopped(stopped) @@ -940,9 +978,9 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } void setUserState(int userId, PackageUserStateInternal otherState) { - setUserState(userId, otherState.getCeDataInode(), otherState.getEnabledState(), - otherState.isInstalled(), otherState.isStopped(), otherState.isNotLaunched(), - otherState.isHidden(), otherState.getDistractionFlags(), + setUserState(userId, otherState.getCeDataInode(), otherState.getDeDataInode(), + otherState.getEnabledState(), otherState.isInstalled(), otherState.isStopped(), + otherState.isNotLaunched(), otherState.isHidden(), otherState.getDistractionFlags(), otherState.getSuspendParams() == null ? null : otherState.getSuspendParams().untrackedStorage(), otherState.isInstantApp(), otherState.isVirtualPreload(), @@ -1677,10 +1715,10 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @DataClass.Generated( - time = 1691185420362L, + time = 1694196905013L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java", - inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") + inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 8dec425f074d..d989c90a0597 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -277,6 +277,7 @@ final class RemovePackageHelper { mAppDataHelper.destroyAppDataLIF(pkg, nextUserId, FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL); ps.setCeDataInode(-1, nextUserId); + ps.setDeDataInode(-1, nextUserId); } mAppDataHelper.clearKeystoreData(nextUserId, ps.getAppId()); preferredActivityHelper.clearPackagePreferredActivities(ps.getPackageName(), diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 111a32d03b4c..c263978edd11 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -327,6 +327,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile private static final String ATTR_VERSION = "version"; private static final String ATTR_CE_DATA_INODE = "ceDataInode"; + private static final String ATTR_DE_DATA_INODE = "deDataInode"; private static final String ATTR_INSTALLED = "inst"; private static final String ATTR_STOPPED = "stopped"; private static final String ATTR_NOT_LAUNCHED = "nl"; @@ -1121,7 +1122,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile + "installed=%b)", pkgName, installUserId, user.toFullString(), installed); } - pkgSetting.setUserState(user.id, 0, COMPONENT_ENABLED_STATE_DEFAULT, + pkgSetting.setUserState(user.id, 0, 0, COMPONENT_ENABLED_STATE_DEFAULT, installed, true /*stopped*/, true /*notLaunched*/, @@ -1798,7 +1799,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile // in the stopped state, but not at first boot. Also // consider all applications to be installed. for (PackageSetting pkg : mPackages.values()) { - pkg.setUserState(userId, 0, COMPONENT_ENABLED_STATE_DEFAULT, + pkg.setUserState(userId, pkg.getCeDataInode(userId), + pkg.getDeDataInode(userId), COMPONENT_ENABLED_STATE_DEFAULT, true /*installed*/, false /*stopped*/, false /*notLaunched*/, @@ -1861,6 +1863,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile final long ceDataInode = parser.getAttributeLong(null, ATTR_CE_DATA_INODE, 0); + final long deDataInode = + parser.getAttributeLong(null, ATTR_DE_DATA_INODE, 0); final boolean installed = parser.getAttributeBoolean(null, ATTR_INSTALLED, true); final boolean stopped = @@ -1989,7 +1993,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile if (blockUninstall) { setBlockUninstallLPw(userId, name, true); } - ps.setUserState(userId, ceDataInode, enabled, installed, stopped, + ps.setUserState( + userId, ceDataInode, deDataInode, enabled, installed, stopped, notLaunched, hidden, distractionFlags, suspendParamsMap, instantApp, virtualPreload, enabledCaller, enabledComponents, disabledComponents, installReason, uninstallReason, @@ -2306,6 +2311,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile serializer.attributeLong(null, ATTR_CE_DATA_INODE, ustate.getCeDataInode()); } + if (ustate.getDeDataInode() != 0) { + serializer.attributeLong(null, ATTR_DE_DATA_INODE, + ustate.getDeDataInode()); + } if (!ustate.isInstalled()) { serializer.attributeBoolean(null, ATTR_INSTALLED, false); } @@ -5172,6 +5181,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile pw.print(prefix); pw.print(" User "); pw.print(user.id); pw.print(": "); pw.print("ceDataInode="); pw.print(userState.getCeDataInode()); + pw.print(" deDataInode="); + pw.print(userState.getDeDataInode()); pw.print(" installed="); pw.print(userState.isInstalled()); pw.print(" hidden="); diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 4eceb7738836..cc8e62409597 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -378,7 +378,6 @@ public class PackageInfoUtils { ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT) | flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD) | flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN); - if ((flags & PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS) != 0 && state.isQuarantined()) { ai.enabled = false; @@ -402,6 +401,14 @@ public class PackageInfoUtils { ai.resourceDirs = overlayPaths.getResourceDirs().toArray(new String[0]); ai.overlayPaths = overlayPaths.getOverlayPaths().toArray(new String[0]); } + ai.isArchived = isArchived(state); + } + + // TODO(b/288142708) Check for userState.isInstalled() here once this bug is fixed. + // If an app has isInstalled() == true - it should not be filtered above in any case, currently + // it is. + private static boolean isArchived(PackageUserState userState) { + return userState.getArchiveState() != null; } @Nullable diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index c44b8852447a..bf206537d912 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -1490,12 +1490,9 @@ final class DefaultPermissionGrantPolicy { if (dir.isDirectory() && dir.canRead()) { Collections.addAll(ret, dir.listFiles()); } - // For IoT devices, we check the oem partition for default permissions for each app. - if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) { - dir = new File(Environment.getOemDirectory(), "etc/default-permissions"); - if (dir.isDirectory() && dir.canRead()) { - Collections.addAll(ret, dir.listFiles()); - } + dir = new File(Environment.getOemDirectory(), "etc/default-permissions"); + if (dir.isDirectory() && dir.canRead()) { + Collections.addAll(ret, dir.listFiles()); } return ret.isEmpty() ? null : ret.toArray(new File[0]); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java index c05b3c2be622..2a81a86d20f6 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java @@ -86,6 +86,13 @@ public interface PackageUserState { long getCeDataInode(); /** + * Device encrypted /data partition inode. + * + * @hide + */ + long getDeDataInode(); + + /** * Fully qualified class names of components explicitly disabled. * * @hide diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java index fc4b6863b9b8..2f4ad2d8fcc6 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java @@ -77,6 +77,11 @@ class PackageUserStateDefault implements PackageUserStateInternal { } @Override + public long getDeDataInode() { + return 0; + } + + @Override public int getDistractionFlags() { return 0; } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java index 0b35d8ae16ea..12795c6b6814 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java @@ -91,6 +91,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt protected WatchedArraySet<String> mEnabledComponentsWatched; private long mCeDataInode; + private long mDeDataInode; private int mDistractionFlags; @PackageManager.EnabledState private int mEnabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; @@ -172,6 +173,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt mSharedLibraryOverlayPaths = other.mSharedLibraryOverlayPaths == null ? null : other.mSharedLibraryOverlayPaths.snapshot(); mCeDataInode = other.mCeDataInode; + mDeDataInode = other.mDeDataInode; mDistractionFlags = other.mDistractionFlags; mEnabledState = other.mEnabledState; mInstallReason = other.mInstallReason; @@ -444,6 +446,12 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt return this; } + public @NonNull PackageUserStateImpl setDeDataInode(long value) { + mDeDataInode = value; + onChanged(); + return this; + } + public @NonNull PackageUserStateImpl setInstalled(boolean value) { setBoolean(Booleans.INSTALLED, value); onChanged(); @@ -687,7 +695,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt @Override public boolean dataExists() { - return getCeDataInode() > 0; + return getCeDataInode() > 0 || getDeDataInode() > 0; } @@ -721,6 +729,11 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } @DataClass.Generated.Member + public long getDeDataInode() { + return mDeDataInode; + } + + @DataClass.Generated.Member public int getDistractionFlags() { return mDistractionFlags; } @@ -849,6 +862,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt && Objects.equals(mDisabledComponentsWatched, that.mDisabledComponentsWatched) && Objects.equals(mEnabledComponentsWatched, that.mEnabledComponentsWatched) && mCeDataInode == that.mCeDataInode + && mDeDataInode == that.mDeDataInode && mDistractionFlags == that.mDistractionFlags && mEnabledState == that.mEnabledState && mInstallReason == that.mInstallReason @@ -878,6 +892,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt _hash = 31 * _hash + Objects.hashCode(mDisabledComponentsWatched); _hash = 31 * _hash + Objects.hashCode(mEnabledComponentsWatched); _hash = 31 * _hash + Long.hashCode(mCeDataInode); + _hash = 31 * _hash + Long.hashCode(mDeDataInode); _hash = 31 * _hash + mDistractionFlags; _hash = 31 * _hash + mEnabledState; _hash = 31 * _hash + mInstallReason; @@ -898,10 +913,10 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } @DataClass.Generated( - time = 1691601685901L, + time = 1694196888631L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java", - inputSignatures = "private int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final int INSTALLED\nprivate static final int STOPPED\nprivate static final int NOT_LAUNCHED\nprivate static final int HIDDEN\nprivate static final int INSTANT_APP\nprivate static final int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") + inputSignatures = "private int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate long mDeDataInode\nprivate int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isQuarantined()\npublic @java.lang.Override boolean dataExists()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final int INSTALLED\nprivate static final int STOPPED\nprivate static final int NOT_LAUNCHED\nprivate static final int HIDDEN\nprivate static final int INSTANT_APP\nprivate static final int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index 812e22833aba..f14941b2d9c8 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -28,6 +28,7 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_ import static android.os.Build.VERSION_CODES.DONUT; import static android.os.Build.VERSION_CODES.O; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; + import static com.android.server.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts; import android.annotation.AnyRes; @@ -509,24 +510,33 @@ public class ParsingPackageUtils { /* Set the global "on SD card" flag */ .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0); + var archivedPackage = lite.getArchivedPackage(); + if (archivedPackage == null) { + return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, + "archivePackage is missing"); + } + // parseBaseAppBasicFlags pkg // Default true - .setBackupAllowed(lite.isBackupAllowed()) + .setBackupAllowed(true) .setClearUserDataAllowed(true) - .setClearUserDataOnFailedRestoreAllowed( - lite.isClearUserDataOnFailedRestoreAllowed()) + .setClearUserDataOnFailedRestoreAllowed(true) .setAllowNativeHeapPointerTagging(true) .setEnabled(true) .setExtractNativeLibrariesRequested(true) // targetSdkVersion gated .setAllowAudioPlaybackCapture(targetSdk >= Build.VERSION_CODES.Q) .setHardwareAccelerated(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) - .setRequestLegacyExternalStorage(lite.isRequestLegacyExternalStorage()) + .setRequestLegacyExternalStorage( + XmlUtils.convertValueToBoolean(archivedPackage.requestLegacyExternalStorage, + targetSdk < Build.VERSION_CODES.Q)) .setCleartextTrafficAllowed(targetSdk < Build.VERSION_CODES.P) // Default false - .setDefaultToDeviceProtectedStorage(lite.isDefaultToDeviceProtectedStorage()) - .setUserDataFragile(lite.isUserDataFragile()) + .setDefaultToDeviceProtectedStorage(XmlUtils.convertValueToBoolean( + archivedPackage.defaultToDeviceProtectedStorage, false)) + .setUserDataFragile( + XmlUtils.convertValueToBoolean(archivedPackage.userDataFragile, false)) // Ints .setCategory(ApplicationInfo.CATEGORY_UNDEFINED) // Floats Default 0f diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java index 3aed6e30cf87..a49df50c1b92 100644 --- a/services/core/java/com/android/server/security/FileIntegrityService.java +++ b/services/core/java/com/android/server/security/FileIntegrityService.java @@ -90,6 +90,13 @@ public class FileIntegrityService extends SystemService { @NonNull String packageName) { checkCallerPermission(packageName); + if (android.security.Flags.deprecateFsvSig()) { + // When deprecated, stop telling the caller that any app source certificate is + // trusted on the current device. This behavior is also consistent with devices + // without this feature support. + return false; + } + try { if (!VerityUtils.isFsVeritySupported()) { return false; diff --git a/services/core/java/com/android/server/security/TEST_MAPPING b/services/core/java/com/android/server/security/TEST_MAPPING index 673456f889e2..29d52fff3eff 100644 --- a/services/core/java/com/android/server/security/TEST_MAPPING +++ b/services/core/java/com/android/server/security/TEST_MAPPING @@ -1,5 +1,5 @@ { - "postsubmit": [ + "presubmit": [ { "name": "CtsSecurityTestCases", "options": [ diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index c526016544a6..a5c0fb3c46af 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -1454,6 +1454,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde boolean hasDesiredDemuxCap = request.desiredFilterTypes != DemuxFilterMainType.UNDEFINED; int smallestNumOfSupportedCaps = Integer.SIZE + 1; + int smallestNumOfSupportedCapsInUse = Integer.SIZE + 1; for (DemuxResource dr : getDemuxResources().values()) { if (!hasDesiredDemuxCap || dr.hasSufficientCaps(request.desiredFilterTypes)) { if (!dr.isInUse()) { @@ -1476,12 +1477,18 @@ public class TunerResourceManagerService extends SystemService implements IBinde currentLowestPriority = priority; isRequestFromSameProcess = (requestClient.getProcessId() == (getClientProfile(dr.getOwnerClientId())).getProcessId()); + + // reset the smallest caps when lower priority resource is found + smallestNumOfSupportedCapsInUse = numOfSupportedCaps; + shouldUpdate = true; - } - // update smallest caps - if (smallestNumOfSupportedCaps > numOfSupportedCaps) { - smallestNumOfSupportedCaps = numOfSupportedCaps; - shouldUpdate = true; + } else { + // This is the case when the priority is the same as previously found + // one. Update smallest caps when priority. + if (smallestNumOfSupportedCapsInUse > numOfSupportedCaps) { + smallestNumOfSupportedCapsInUse = numOfSupportedCaps; + shouldUpdate = true; + } } if (shouldUpdate) { inUseLowestPriorityDrHandle = dr.getHandle(); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index cfc7031d01d7..4c525e902b88 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -256,6 +256,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub || event != CLOSE_WRITE // includes the MOVED_TO case || wallpaper.imageWallpaperPending; + if (isMigration) { + // When separate lock screen engine is supported, migration will be handled by + // WallpaperDestinationChangeHandler. + return; + } + if (!(sysWallpaperChanged || lockWallpaperChanged)) { + return; + } + if (DEBUG) { Slog.v(TAG, "Wallpaper file change: evt=" + event + " path=" + path @@ -270,15 +279,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub + " needsUpdate=" + needsUpdate); } - if (isMigration) { - // When separate lock screen engine is supported, migration will be handled by - // WallpaperDestinationChangeHandler. - return; - } - if (!(sysWallpaperChanged || lockWallpaperChanged)) { - return; - } - int notifyColorsWhich = 0; synchronized (mLock) { notifyCallbacksLocked(wallpaper); @@ -1005,11 +1005,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } - if (!mWallpaper.wallpaperUpdating - && mWallpaper.userId == mCurrentUserId) { + if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) { Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent + ", reverting to built-in wallpaper!"); - clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, null); + int which = mIsLockscreenLiveWallpaperEnabled ? mWallpaper.mWhich : FLAG_SYSTEM; + clearWallpaperLocked(which, mWallpaper.userId, null); } } }; @@ -1189,7 +1189,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } else { // Timeout Slog.w(TAG, "Reverting to built-in wallpaper!"); - clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, null); + clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, null); final String flattened = wpService.flattenToString(); EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED, flattened.substring(0, Math.min(flattened.length(), @@ -1228,7 +1228,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } else { if (mLmkLimitRebindRetries <= 0) { Slog.w(TAG, "Reverting to built-in wallpaper due to lmk!"); - clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, null); + clearWallpaperLocked( + mWallpaper.mWhich, mWallpaper.userId, null); mLmkLimitRebindRetries = LMK_RECONNECT_REBIND_RETRIES; return; } @@ -1470,8 +1471,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mCurrentUserId != getChangingUserId()) { return; } - WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId); - if (wallpaper != null) { + for (WallpaperData wallpaper: getWallpapers()) { final ComponentName wpService = wallpaper.wallpaperComponent; if (wpService != null && wpService.getPackageName().equals(packageName)) { if (DEBUG_LIVE) { @@ -1483,7 +1483,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper, null)) { Slog.w(TAG, "Wallpaper " + wpService + " no longer available; reverting to default"); - clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, null); + int which = mIsLockscreenLiveWallpaperEnabled + ? wallpaper.mWhich : FLAG_SYSTEM; + clearWallpaperLocked(which, wallpaper.userId, null); } } } @@ -1496,13 +1498,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mCurrentUserId != getChangingUserId()) { return; } - WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId); - if (wallpaper != null) { - if (wallpaper.wallpaperComponent == null - || !wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { - return; + for (WallpaperData wallpaper: getWallpapers()) { + if (wallpaper.wallpaperComponent != null + && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { + doPackagesChangedLocked(true, wallpaper); } - doPackagesChangedLocked(true, wallpaper); } } } @@ -1513,8 +1513,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mCurrentUserId != getChangingUserId()) { return; } - WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId); - if (wallpaper != null) { + for (WallpaperData wallpaper: getWallpapers()) { if (wallpaper.wallpaperComponent != null && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { if (DEBUG_LIVE) { @@ -1538,8 +1537,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mCurrentUserId != getChangingUserId()) { return false; } - WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId); - if (wallpaper != null) { + for (WallpaperData wallpaper: getWallpapers()) { boolean res = doPackagesChangedLocked(doit, wallpaper); changed |= res; } @@ -1553,8 +1551,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mCurrentUserId != getChangingUserId()) { return; } - WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId); - if (wallpaper != null) { + for (WallpaperData wallpaper: getWallpapers()) { doPackagesChangedLocked(true, wallpaper); } } @@ -1562,6 +1559,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) { boolean changed = false; + int which = mIsLockscreenLiveWallpaperEnabled ? wallpaper.mWhich : FLAG_SYSTEM; if (wallpaper.wallpaperComponent != null) { int change = isPackageDisappearing(wallpaper.wallpaperComponent .getPackageName()); @@ -1571,7 +1569,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (doit) { Slog.w(TAG, "Wallpaper uninstalled, removing: " + wallpaper.wallpaperComponent); - clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, null); + clearWallpaperLocked(which, wallpaper.userId, null); } } } @@ -1592,7 +1590,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } catch (NameNotFoundException e) { Slog.w(TAG, "Wallpaper component gone, removing: " + wallpaper.wallpaperComponent); - clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, null); + clearWallpaperLocked(which, wallpaper.userId, null); } } if (wallpaper.nextWallpaperComponent != null @@ -1708,7 +1706,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (DEBUG) { Slog.i(TAG, "Unable to regenerate crop; resetting"); } - clearWallpaperLocked(FLAG_SYSTEM, UserHandle.USER_SYSTEM, null); + int which = isLockscreenLiveWallpaperEnabled() ? wallpaper.mWhich : FLAG_SYSTEM; + clearWallpaperLocked(which, UserHandle.USER_SYSTEM, null); } } else { if (DEBUG) { @@ -1988,12 +1987,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData wallpaper, IRemoteCallback reply, ServiceInfo serviceInfo) { if (serviceInfo == null) { - if (wallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)) { - clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, null); - clearWallpaperLocked(FLAG_LOCK, wallpaper.userId, reply); - } else { - clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply); - } + clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply); return; } Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked"); @@ -2014,7 +2008,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub @Override public void clearWallpaper(String callingPackage, int which, int userId) { - if (DEBUG) Slog.v(TAG, "clearWallpaper"); + if (DEBUG) Slog.v(TAG, "clearWallpaper: " + which); checkPermission(android.Manifest.permission.SET_WALLPAPER); if (!isWallpaperSupported(callingPackage) || !isSetWallpaperAllowed(callingPackage)) { return; @@ -2025,7 +2019,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData data = null; synchronized (mLock) { if (mIsLockscreenLiveWallpaperEnabled) { - clearWallpaperLocked(callingPackage, which, userId); + boolean fromForeground = isFromForegroundApp(callingPackage); + clearWallpaperLocked(which, userId, fromForeground, null); } else { clearWallpaperLocked(which, userId, null); } @@ -2045,7 +2040,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private void clearWallpaperLocked(String callingPackage, int which, int userId) { + private void clearWallpaperLocked(int which, int userId, boolean fromForeground, + IRemoteCallback reply) { // Might need to bring it in the first time to establish our rewrite if (!mWallpaperMap.contains(userId)) { @@ -2084,8 +2080,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub finalWhich = which; } - boolean success = withCleanCallingIdentity(() -> setWallpaperComponent( - component, callingPackage, finalWhich, userId)); + // except for the lock case (for which we keep the system wallpaper as-is), force rebind + boolean force = which != FLAG_LOCK; + boolean success = withCleanCallingIdentity(() -> setWallpaperComponentInternal( + component, finalWhich, userId, force, fromForeground, reply)); if (success) return; } catch (IllegalArgumentException e1) { e = e1; @@ -2097,10 +2095,23 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // wallpaper. Slog.e(TAG, "Default wallpaper component not found!", e); withCleanCallingIdentity(() -> clearWallpaperComponentLocked(wallpaper)); + if (reply != null) { + try { + reply.sendResult(null); + } catch (RemoteException e1) { + Slog.w(TAG, "Failed to notify callback after wallpaper clear", e1); + } + } } - // TODO(b/266818039) remove this version of the method + // TODO(b/266818039) remove private void clearWallpaperLocked(int which, int userId, IRemoteCallback reply) { + + if (mIsLockscreenLiveWallpaperEnabled) { + clearWallpaperLocked(which, userId, false, reply); + return; + } + if (which != FLAG_SYSTEM && which != FLAG_LOCK) { throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to clear"); } @@ -2815,6 +2826,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub : new WallpaperData[0]; } + // TODO(b/266818039) remove + private WallpaperData[] getWallpapers() { + WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId); + WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId); + boolean systemValid = systemWallpaper != null; + boolean lockValid = lockWallpaper != null && !isLockscreenLiveWallpaperEnabled(); + return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper} + : systemValid ? new WallpaperData[]{systemWallpaper} + : lockValid ? new WallpaperData[]{lockWallpaper} + : new WallpaperData[0]; + } + private IWallpaperEngine getEngine(int which, int userId, int displayId) { WallpaperData wallpaperData = findWallpaperAtDisplay(userId, displayId); if (wallpaperData == null) return null; @@ -3272,15 +3295,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub boolean setWallpaperComponent(ComponentName name, String callingPackage, @SetWallpaperFlags int which, int userId) { if (mIsLockscreenLiveWallpaperEnabled) { - return setWallpaperComponentInternal(name, callingPackage, which, userId); + boolean fromForeground = isFromForegroundApp(callingPackage); + return setWallpaperComponentInternal(name, which, userId, false, fromForeground, null); } else { setWallpaperComponentInternalLegacy(name, callingPackage, which, userId); return true; } } - private boolean setWallpaperComponentInternal(ComponentName name, String callingPackage, - @SetWallpaperFlags int which, int userIdIn) { + private boolean setWallpaperComponentInternal(ComponentName name, @SetWallpaperFlags int which, + int userIdIn, boolean force, boolean fromForeground, IRemoteCallback reply) { if (DEBUG) { Slog.v(TAG, "Setting new live wallpaper: which=" + which + ", component: " + name); } @@ -3294,7 +3318,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final WallpaperData newWallpaper; synchronized (mLock) { - Slog.v(TAG, "setWallpaperComponent name=" + name); + Slog.v(TAG, "setWallpaperComponent name=" + name + ", which = " + which); final WallpaperData originalSystemWallpaper = mWallpaperMap.get(userId); if (originalSystemWallpaper == null) { throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); @@ -3317,29 +3341,21 @@ public class WallpaperManagerService extends IWallpaperManager.Stub newWallpaper.imageWallpaperPending = false; newWallpaper.mWhich = which; newWallpaper.mSystemWasBoth = systemIsBoth; - newWallpaper.fromForegroundApp = isFromForegroundApp(callingPackage); + newWallpaper.fromForegroundApp = fromForeground; final WallpaperDestinationChangeHandler liveSync = new WallpaperDestinationChangeHandler( newWallpaper); boolean same = changingToSame(name, newWallpaper); - IRemoteCallback.Stub callback = new IRemoteCallback.Stub() { - @Override - public void sendResult(Bundle data) throws RemoteException { - if (DEBUG) { - Slog.d(TAG, "publish system wallpaper changed!"); - } - } - }; /* * If we have a shared system+lock wallpaper, and we reapply the same wallpaper * to system only, force rebind: the current wallpaper will be migrated to lock * and a new engine with the same wallpaper will be applied to system. */ - boolean forceRebind = same && systemIsBoth && which == FLAG_SYSTEM; + boolean forceRebind = force || (same && systemIsBoth && which == FLAG_SYSTEM); bindSuccess = bindWallpaperComponentLocked(name, /* force */ - forceRebind, /* fromUser */ true, newWallpaper, callback); + forceRebind, /* fromUser */ true, newWallpaper, reply); if (bindSuccess) { if (!same) { newWallpaper.primaryColors = null; @@ -3409,7 +3425,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData wallpaper; synchronized (mLock) { - Slog.v(TAG, "setWallpaperComponent name=" + name + ", which=" + which); + Slog.v(TAG, "setWallpaperComponentLegacy name=" + name + ", which=" + which); wallpaper = mWallpaperMap.get(userId); if (wallpaper == null) { throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); @@ -3478,15 +3494,24 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + /** + * Determines if the given component name is the default component. Note: a null name can be + * used to represent the default component. + * @param name The component name to check. + * @return True if the component name matches the default wallpaper component. + */ + private boolean isDefaultComponent(ComponentName name) { + return name == null || name.equals(mDefaultWallpaperComponent); + } + private boolean changingToSame(ComponentName componentName, WallpaperData wallpaper) { if (wallpaper.connection != null) { - if (wallpaper.wallpaperComponent == null) { - if (componentName == null) { - if (DEBUG) Slog.v(TAG, "changingToSame: still using default"); - // Still using default wallpaper. - return true; - } - } else if (wallpaper.wallpaperComponent.equals(componentName)) { + final ComponentName wallpaperName = wallpaper.wallpaperComponent; + if (isDefaultComponent(componentName) && isDefaultComponent(wallpaperName)) { + if (DEBUG) Slog.v(TAG, "changingToSame: still using default"); + // Still using default wallpaper. + return true; + } else if (wallpaperName != null && wallpaperName.equals(componentName)) { // Changing to same wallpaper. if (DEBUG) Slog.v(TAG, "same wallpaper"); return true; @@ -3503,6 +3528,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // Has the component changed? if (!force && changingToSame(componentName, wallpaper)) { try { + if (DEBUG_LIVE) { + Slog.v(TAG, "Changing to the same component, ignoring"); + } if (reply != null) reply.sendResult(null); } catch (RemoteException e) { Slog.e(TAG, "Failed to send callback", e); @@ -3721,7 +3749,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mContext.getMainThreadHandler().removeCallbacks( wallpaper.connection.mTryToRebindRunnable); - mContext.unbindService(wallpaper.connection); + try { + mContext.unbindService(wallpaper.connection); + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Error unbinding wallpaper when detaching", e); + } wallpaper.connection = null; if (wallpaper == mLastWallpaper) { mLastWallpaper = null; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index dca2b6f8f2af..216369bf56b4 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -565,8 +565,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean forceNewConfig; // force re-create with new config next time boolean supportsEnterPipOnTaskSwitch; // This flag is set by the system to indicate that the // activity can enter picture in picture while pausing (only when switching to another task) + // The PiP params used when deferring the entering of picture-in-picture. PictureInPictureParams pictureInPictureArgs = new PictureInPictureParams.Builder().build(); - // The PiP params used when deferring the entering of picture-in-picture. boolean shouldDockBigOverlays; int launchCount; // count of launches since last state long lastLaunchTime; // time of last launch of this activity @@ -1732,6 +1732,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mAnimatingActivityRegistry = registry; } + boolean canAutoEnterPip() { + // beforeStopping=false since the actual pip-ing will take place after startPausing() + final boolean activityCanPip = checkEnterPictureInPictureState( + "startActivityUnchecked", false /* beforeStopping */); + + // check if this activity is about to auto-enter pip + return activityCanPip && pictureInPictureArgs != null + && pictureInPictureArgs.isAutoEnterEnabled(); + } + /** * Sets {@link #mLastParentBeforePip} to the current parent Task, it's caller's job to ensure * {@link #getTask()} is set before this is called. @@ -9180,7 +9190,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A getRequestedOverrideWindowingMode() == WINDOWING_MODE_UNDEFINED ? newParentConfig.windowConfiguration.getWindowingMode() : getRequestedOverrideWindowingMode(); - if (getWindowingMode() != projectedWindowingMode) { + if (getWindowingMode() != projectedWindowingMode + // Do not collect a pip activity about to enter pinned mode + // as a part of WindowOrganizerController#finishTransition(). + // If not checked the activity might be collected for the wrong transition, + // such as a TRANSIT_OPEN transition requested right after TRANSIT_PIP. + && !(mWaitForEnteringPinnedMode + && mTransitionController.inFinishingTransition(this))) { mTransitionController.collect(this); } } diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index a5b1132fe499..25c42b4858a4 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -33,9 +33,7 @@ import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; -import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SANDBOX_ORDER_ID; -import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.KeyguardManager; @@ -240,11 +238,6 @@ class ActivityStartInterceptor { getInterceptorInfo(null /* clearOptionsAnimation */); for (int i = 0; i < callbacks.size(); i++) { - final int orderId = callbacks.keyAt(i); - if (!shouldInterceptActivityLaunch(orderId, interceptorInfo)) { - continue; - } - final ActivityInterceptorCallback callback = callbacks.valueAt(i); final ActivityInterceptResult interceptResult = callback.onInterceptActivityLaunch( interceptorInfo); @@ -543,11 +536,6 @@ class ActivityStartInterceptor { ActivityInterceptorCallback.ActivityInterceptorInfo info = getInterceptorInfo( r::clearOptionsAnimationForSiblings); for (int i = 0; i < callbacks.size(); i++) { - final int orderId = callbacks.keyAt(i); - if (!shouldNotifyOnActivityLaunch(orderId, info)) { - continue; - } - final ActivityInterceptorCallback callback = callbacks.valueAt(i); callback.onActivityLaunched(taskInfo, r.info, info); } @@ -565,21 +553,4 @@ class ActivityStartInterceptor { .build(); } - private boolean shouldInterceptActivityLaunch( - @ActivityInterceptorCallback.OrderedId int orderId, - @NonNull ActivityInterceptorCallback.ActivityInterceptorInfo info) { - if (orderId == MAINLINE_SDK_SANDBOX_ORDER_ID) { - return info.getIntent() != null && info.getIntent().isSandboxActivity(mServiceContext); - } - return true; - } - - private boolean shouldNotifyOnActivityLaunch( - @ActivityInterceptorCallback.OrderedId int orderId, - @NonNull ActivityInterceptorCallback.ActivityInterceptorInfo info) { - if (orderId == MAINLINE_SDK_SANDBOX_ORDER_ID) { - return info.getIntent() != null && info.getIntent().isSandboxActivity(mServiceContext); - } - return true; - } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 6999c6a2a5d8..9c9c63f33903 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -313,6 +313,8 @@ import java.util.Set; public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender"; private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM; + private static final String ENABLE_PIP2_IMPLEMENTATION = + "persist.wm.debug.enable_pip2_implementation"; static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK; static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; @@ -3613,6 +3615,28 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + /** + * Prepare to enter PiP mode after {@link TransitionController#requestStartTransition}. + * + * @param r activity auto entering pip + * @return true if the activity is about to auto-enter pip or is already in pip mode. + */ + boolean prepareAutoEnterPictureAndPictureMode(ActivityRecord r) { + // If the activity is already in picture in picture mode, then just return early + if (r.inPinnedWindowingMode()) { + return true; + } + + if (r.canAutoEnterPip() && getTransitionController().getCollectingTransition() != null) { + // This will be used later to construct TransitionRequestInfo for Shell to resolve. + // It will also be passed into a direct moveActivityToPinnedRootTask() call via + // startTransition() + getTransitionController().getCollectingTransition().setPipActivity(r); + return true; + } + return false; + } + boolean enterPictureInPictureMode(@NonNull ActivityRecord r, @NonNull PictureInPictureParams params, boolean fromClient) { return enterPictureInPictureMode(r, params, fromClient, false /* isAutoEnter */); @@ -7183,4 +7207,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { ActivityTaskManagerService.this.unregisterTaskStackListener(listener); } } + + static boolean isPip2ExperimentEnabled() { + return SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false); + } } diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java index 0a2bbd467ab3..4b463c7e1756 100644 --- a/services/core/java/com/android/server/wm/AsyncRotationController.java +++ b/services/core/java/com/android/server/wm/AsyncRotationController.java @@ -459,7 +459,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume // insets should keep original position before the start transaction is applied. return mTransitionOp != OP_LEGACY && (mTransitionOp == OP_APP_SWITCH || TransitionController.SYNC_METHOD == BLASTSyncEngine.METHOD_BLAST) - && !mIsStartTransactionCommitted && isTargetToken(w.mToken); + && !mIsStartTransactionCommitted && canBeAsync(w.mToken) && isTargetToken(w.mToken); } /** diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index 7cd07d6f3a5f..b499dad53326 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -83,6 +83,11 @@ final class ContentRecorder implements WindowContainerListener { @Nullable private Rect mLastRecordedBounds = null; /** + * The last size of the surface mirrored out to. + */ + @Nullable private Point mLastConsumingSurfaceSize = new Point(0, 0); + + /** * The last configuration orientation. */ @Configuration.Orientation @@ -141,60 +146,64 @@ final class ContentRecorder implements WindowContainerListener { */ void onConfigurationChanged(@Configuration.Orientation int lastOrientation) { // Update surface for MediaProjection, if this DisplayContent is being used for recording. - if (isCurrentlyRecording() && mLastRecordedBounds != null) { - // Recording has already begun, but update recording since the display is now on. - if (mRecordedWindowContainer == null) { + if (!isCurrentlyRecording() || mLastRecordedBounds == null) { + return; + } + + // Recording has already begun, but update recording since the display is now on. + if (mRecordedWindowContainer == null) { + ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, + "Content Recording: Unexpectedly null window container; unable to update " + + "recording for display %d", + mDisplayContent.getDisplayId()); + return; + } + + // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are + // inaccurate. + if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) { + final Task capturedTask = mRecordedWindowContainer.asTask(); + if (capturedTask.inPinnedWindowingMode()) { ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, - "Content Recording: Unexpectedly null window container; unable to update " - + "recording for display %d", + "Content Recording: Display %d was already recording, but " + + "pause capture since the task is in PIP", mDisplayContent.getDisplayId()); + pauseRecording(); return; } + } - // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are - // inaccurate. - if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) { - final Task capturedTask = mRecordedWindowContainer.asTask(); - if (capturedTask.inPinnedWindowingMode()) { - ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, - "Content Recording: Display %d was already recording, but " - + "pause capture since the task is in PIP", - mDisplayContent.getDisplayId()); - pauseRecording(); - return; - } - } - - ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, - "Content Recording: Display %d was already recording, so apply " - + "transformations if necessary", - mDisplayContent.getDisplayId()); - // Retrieve the size of the region to record, and continue with the update - // if the bounds or orientation has changed. - final Rect recordedContentBounds = mRecordedWindowContainer.getBounds(); - @Configuration.Orientation int recordedContentOrientation = - mRecordedWindowContainer.getConfiguration().orientation; - if (!mLastRecordedBounds.equals(recordedContentBounds) - || lastOrientation != recordedContentOrientation) { - Point surfaceSize = fetchSurfaceSizeIfPresent(); - if (surfaceSize != null) { - ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, - "Content Recording: Going ahead with updating recording for display " - + "%d to new bounds %s and/or orientation %d.", - mDisplayContent.getDisplayId(), recordedContentBounds, - recordedContentOrientation); - updateMirroredSurface(mRecordedWindowContainer.getSyncTransaction(), - recordedContentBounds, surfaceSize); - } else { - // If the surface removed, do nothing. We will handle this via onDisplayChanged - // (the display will be off if the surface is removed). - ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, - "Content Recording: Unable to update recording for display %d to new " - + "bounds %s and/or orientation %d, since the surface is not " - + "available.", - mDisplayContent.getDisplayId(), recordedContentBounds, - recordedContentOrientation); - } + ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, + "Content Recording: Display %d was already recording, so apply " + + "transformations if necessary", + mDisplayContent.getDisplayId()); + // Retrieve the size of the region to record, and continue with the update + // if the bounds or orientation has changed. + final Rect recordedContentBounds = mRecordedWindowContainer.getBounds(); + @Configuration.Orientation int recordedContentOrientation = + mRecordedWindowContainer.getConfiguration().orientation; + final Point surfaceSize = fetchSurfaceSizeIfPresent(); + if (!mLastRecordedBounds.equals(recordedContentBounds) + || lastOrientation != recordedContentOrientation + || !mLastConsumingSurfaceSize.equals(surfaceSize)) { + if (surfaceSize != null) { + ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, + "Content Recording: Going ahead with updating recording for display " + + "%d to new bounds %s and/or orientation %d and/or surface " + + "size %s", + mDisplayContent.getDisplayId(), recordedContentBounds, + recordedContentOrientation, surfaceSize); + updateMirroredSurface(mRecordedWindowContainer.getSyncTransaction(), + recordedContentBounds, surfaceSize); + } else { + // If the surface removed, do nothing. We will handle this via onDisplayChanged + // (the display will be off if the surface is removed). + ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, + "Content Recording: Unable to update recording for display %d to new " + + "bounds %s and/or orientation %d and/or surface size %s, " + + "since the surface is not available.", + mDisplayContent.getDisplayId(), recordedContentBounds, + recordedContentOrientation, surfaceSize); } } } @@ -498,10 +507,13 @@ final class ContentRecorder implements WindowContainerListener { } ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, - "Content Recording: Apply transformations of shift %d x %d, scale %f, crop %d x " - + "%d for display %d", + "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka " + + "recorded content size) %d x %d for display %d; display has size %d x " + + "%d; surface has size %d x %d", shiftedX, shiftedY, scale, recordedContentBounds.width(), - recordedContentBounds.height(), mDisplayContent.getDisplayId()); + recordedContentBounds.height(), mDisplayContent.getDisplayId(), + mDisplayContent.getConfiguration().screenWidthDp, + mDisplayContent.getConfiguration().screenHeightDp, surfaceSize.x, surfaceSize.y); transaction // Crop the area to capture to exclude the 'extra' wallpaper that is used @@ -515,6 +527,8 @@ final class ContentRecorder implements WindowContainerListener { // the content will no longer be centered in the output surface. .setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */); mLastRecordedBounds = new Rect(recordedContentBounds); + mLastConsumingSurfaceSize.x = surfaceSize.x; + mLastConsumingSurfaceSize.y = surfaceSize.y; // Request to notify the client about the resize. mMediaProjectionManager.notifyActiveProjectionCapturedContentResized( mLastRecordedBounds.width(), mLastRecordedBounds.height()); @@ -523,6 +537,7 @@ final class ContentRecorder implements WindowContainerListener { /** * Returns a non-null {@link Point} if the surface is present, or null otherwise */ + @Nullable private Point fetchSurfaceSizeIfPresent() { // Retrieve the default size of the surface the app provided to // MediaProjection#createVirtualDisplay. Note the app is the consumer of the surface, diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index daa73db4684d..4309e72c30d7 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -6040,8 +6040,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mOffTokenAcquirer.release(mDisplayId); } ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, - "Content Recording: Display %d state is now (%d), so update recording?", - mDisplayId, displayState); + "Content Recording: Display %d state was (%d), is now (%d), so update " + + "recording?", + mDisplayId, lastDisplayState, displayState); if (lastDisplayState != displayState) { // If state is on due to surface being added, then start recording. // If state is off due to surface being removed, then stop recording. @@ -6505,14 +6506,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } /** - * @return whether AOD is showing on this display - */ - boolean isAodShowing() { - return mRootWindowContainer.mTaskSupervisor - .getKeyguardController().isAodShowing(mDisplayId); - } - - /** * @return whether this display maintains its own focus and touch mode. */ boolean hasOwnFocus() { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 395ab3a641b4..17bfeb453304 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1677,18 +1677,6 @@ public class DisplayPolicy { } private boolean shouldBeHiddenByKeyguard(WindowState win, WindowState imeTarget) { - // If AOD is showing, the IME should be hidden. However, sometimes the AOD is considered - // hidden because it's in the process of hiding, but it's still being shown on screen. - // In that case, we want to continue hiding the IME until the windows have completed - // drawing. This way, we know that the IME can be safely shown since the other windows are - // now shown. - final boolean hideIme = win.mIsImWindow - && (mDisplayContent.isAodShowing() - || (mDisplayContent.isDefaultDisplay && !mWindowManagerDrawComplete)); - if (hideIme) { - return true; - } - if (!mDisplayContent.isDefaultDisplay || !isKeyguardShowing()) { return false; } diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 8519e4d3e53a..0f1a1053716e 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -21,6 +21,7 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP; + import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; import static com.android.server.wm.DragDropController.MSG_ANIMATION_END; @@ -32,6 +33,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACT import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.MY_PID; import static com.android.server.wm.WindowManagerService.MY_UID; + import static java.util.concurrent.CompletableFuture.completedFuture; import android.animation.Animator; @@ -46,6 +48,7 @@ import android.hardware.input.InputManagerGlobal; import android.os.Binder; import android.os.Build; import android.os.IBinder; +import android.os.InputConfig; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -183,13 +186,8 @@ class DragState { // Crop the input surface to the display size. mTmpClipRect.set(0, 0, mDisplaySize.x, mDisplaySize.y); - // Make trusted overlay to not block any touches while D&D ongoing and allowing - // touches to pass through to windows underneath. This allows user to interact with the - // UI to navigate while dragging. - h.setTrustedOverlay(mTransaction, mInputSurface, true); mTransaction.show(mInputSurface) .setInputWindowInfo(mInputSurface, h) - .setTrustedOverlay(mInputSurface, true) .setLayer(mInputSurface, Integer.MAX_VALUE) .setCrop(mInputSurface, mTmpClipRect); @@ -379,6 +377,11 @@ class DragState { mDragWindowHandle.ownerUid = MY_UID; mDragWindowHandle.scaleFactor = 1.0f; + // InputConfig.TRUSTED_OVERLAY: To not block any touches while D&D ongoing and allowing + // touches to pass through to windows underneath. This allows user to interact with the + // UI to navigate while dragging. + mDragWindowHandle.inputConfig = InputConfig.TRUSTED_OVERLAY; + // The drag window cannot receive new touches. mDragWindowHandle.touchableRegion.setEmpty(); diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java index c21930dab5eb..39622c1c5aaf 100644 --- a/services/core/java/com/android/server/wm/InputConsumerImpl.java +++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java @@ -74,7 +74,7 @@ class InputConsumerImpl implements IBinder.DeathRecipient { mWindowHandle.ownerPid = WindowManagerService.MY_PID; mWindowHandle.ownerUid = WindowManagerService.MY_UID; mWindowHandle.scaleFactor = 1.0f; - mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE; + mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.TRUSTED_OVERLAY; mInputSurface = mService.makeSurfaceBuilder( mService.mRoot.getDisplayContent(displayId).getSession()) @@ -129,14 +129,12 @@ class InputConsumerImpl implements IBinder.DeathRecipient { void show(SurfaceControl.Transaction t, WindowContainer w) { t.show(mInputSurface); - mWindowHandle.setTrustedOverlay(t, mInputSurface, true); t.setInputWindowInfo(mInputSurface, mWindowHandle); t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1); } void show(SurfaceControl.Transaction t, int layer) { t.show(mInputSurface); - mWindowHandle.setTrustedOverlay(t, mInputSurface, true); t.setInputWindowInfo(mInputSurface, mWindowHandle); t.setLayer(mInputSurface, layer); } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index f77da6287176..825d38b3eed7 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -40,10 +40,12 @@ import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; + import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.LOGTAG_INPUT_FOCUS; + import static java.lang.Integer.MAX_VALUE; import android.annotation.Nullable; @@ -730,7 +732,7 @@ final class InputMonitor { new InputWindowHandle(null /* inputApplicationHandle */, displayId)); inputWindowHandle.setName(name); inputWindowHandle.setLayoutParamsType(TYPE_SECURE_SYSTEM_OVERLAY); - inputWindowHandle.setTrustedOverlay(t, sc, true); + inputWindowHandle.setTrustedOverlay(true); populateOverlayInputInfo(inputWindowHandle); setInputWindowInfoIfNeeded(t, sc, inputWindowHandle); } diff --git a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java index 90d81bd82087..64b7a6064e45 100644 --- a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java +++ b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java @@ -195,11 +195,6 @@ class InputWindowHandleWrapper { mChanged = true; } - void setTrustedOverlay(SurfaceControl.Transaction t, SurfaceControl sc, - boolean trustedOverlay) { - mHandle.setTrustedOverlay(t, sc, trustedOverlay); - } - void setOwnerPid(int pid) { if (mHandle.ownerPid == pid) { return; diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index 579fd14f8fab..45cf10bd3f5e 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -29,7 +29,6 @@ import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.window.flags.FeatureFlags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -91,6 +90,13 @@ final class LetterboxConfiguration { "enable_app_compat_user_aspect_ratio_fullscreen"; private static final boolean DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN = true; + // Whether the letterbox wallpaper style is enabled by default + private static final String KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER = + "enable_letterbox_background_wallpaper"; + + // TODO(b/290048978): Enable wallpaper as default letterbox background. + private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER = false; + /** * Override of aspect ratio for fixed orientation letterboxing that is set via ADB with * set-fixed-orientation-letterbox-aspect-ratio or via {@link @@ -179,9 +185,6 @@ final class LetterboxConfiguration { @NonNull private final LetterboxConfigurationPersister mLetterboxConfigurationPersister; - @NonNull - private final FeatureFlags mFeatureFlags; - // Aspect ratio of letterbox for fixed orientation, values <= // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored. private float mFixedOrientationLetterboxAspectRatio; @@ -298,8 +301,7 @@ final class LetterboxConfiguration { // Flags dynamically updated with {@link android.provider.DeviceConfig}. @NonNull private final SynchedDeviceConfig mDeviceConfig; - LetterboxConfiguration(@NonNull final Context systemUiContext, - @NonNull FeatureFlags featureFags) { + LetterboxConfiguration(@NonNull final Context systemUiContext) { this(systemUiContext, new LetterboxConfigurationPersister( () -> readLetterboxHorizontalReachabilityPositionFromConfig( systemUiContext, /* forBookMode */ false), @@ -308,16 +310,14 @@ final class LetterboxConfiguration { () -> readLetterboxHorizontalReachabilityPositionFromConfig( systemUiContext, /* forBookMode */ true), () -> readLetterboxVerticalReachabilityPositionFromConfig( - systemUiContext, /* forTabletopMode */ true)), - featureFags); + systemUiContext, /* forTabletopMode */ true))); } @VisibleForTesting LetterboxConfiguration(@NonNull final Context systemUiContext, - @NonNull final LetterboxConfigurationPersister letterboxConfigurationPersister, - @NonNull FeatureFlags featureFags) { + @NonNull final LetterboxConfigurationPersister letterboxConfigurationPersister) { mContext = systemUiContext; - mFeatureFlags = featureFags; + mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat( R.dimen.config_fixedOrientationLetterboxAspectRatio); mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext); @@ -385,6 +385,8 @@ final class LetterboxConfiguration { DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS, mContext.getResources().getBoolean( R.bool.config_appCompatUserAppAspectRatioSettingsIsEnabled)) + .addDeviceConfigEntry(KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER, + DEFAULT_VALUE_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER, /* enabled */ true) .addDeviceConfigEntry(KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN, DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN, mContext.getResources().getBoolean( @@ -542,7 +544,8 @@ final class LetterboxConfiguration { } /** - * Resets letterbox background type value depending on the built time and runtime flags. + * Resets letterbox background type value depending on the + * {@link #KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER} built time and runtime flags. * * <p>If enabled, the letterbox background type value is set toZ * {@link #LETTERBOX_BACKGROUND_WALLPAPER}. When disabled the letterbox background type value @@ -552,11 +555,12 @@ final class LetterboxConfiguration { mLetterboxBackgroundTypeOverride = LETTERBOX_BACKGROUND_OVERRIDE_UNSET; } - // Returns LETTERBOX_BACKGROUND_WALLPAPER if the flag is enabled or the value in - // com.android.internal.R.integer.config_letterboxBackgroundType if the flag is disabled. + // Returns KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER if the DeviceConfig flag is enabled + // or the value in com.android.internal.R.integer.config_letterboxBackgroundType if the flag + // is disabled. @LetterboxBackgroundType private int getDefaultLetterboxBackgroundType() { - return mFeatureFlags.letterboxBackgroundWallpaperFlag() + return mDeviceConfig.getFlagValue(KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER) ? LETTERBOX_BACKGROUND_WALLPAPER : mLetterboxBackgroundType; } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 01786becda61..3639e1b9cb47 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -1126,11 +1126,25 @@ final class LetterboxUiController { return computeAspectRatio(bounds); } + /** + * Whether we should enable users to resize the current app. + */ + boolean shouldEnableUserAspectRatioSettings() { + // We use mBooleanPropertyAllowUserAspectRatioOverride to allow apps to opt-out which has + // effect only if explicitly false. If mBooleanPropertyAllowUserAspectRatioOverride is null, + // the current app doesn't opt-out so the first part of the predicate is true. + return !FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride) + && mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled() + && mActivityRecord.mDisplayContent != null + && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest(); + } + + /** + * Whether we should apply the user aspect ratio override to the min aspect ratio for the + * current app. + */ boolean shouldApplyUserMinAspectRatioOverride() { - if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride) - || !mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled() - || mActivityRecord.mDisplayContent == null - || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) { + if (!shouldEnableUserAspectRatioSettings()) { return false; } diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index bbb85636f1ee..2c142cbed585 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -40,11 +40,9 @@ import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; -import android.os.IBinder; import android.os.Trace; import android.util.Slog; import android.util.proto.ProtoOutputStream; -import android.view.DisplayAddress; import android.view.DisplayInfo; import android.view.Surface; import android.view.Surface.OutOfResourcesException; @@ -57,7 +55,6 @@ import android.window.ScreenCapture; import com.android.internal.R; import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.common.ProtoLog; -import com.android.server.display.DisplayControl; import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; @@ -171,32 +168,10 @@ class ScreenRotationAnimation { try { final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer; if (isSizeChanged) { - final DisplayAddress address = displayInfo.address; - if (!(address instanceof DisplayAddress.Physical)) { - Slog.e(TAG, "Display does not have a physical address: " + displayId); - return; - } - final DisplayAddress.Physical physicalAddress = - (DisplayAddress.Physical) address; - final IBinder displayToken = DisplayControl.getPhysicalDisplayToken( - physicalAddress.getPhysicalDisplayId()); - if (displayToken == null) { - Slog.e(TAG, "Display token is null."); - return; - } // Temporarily not skip screenshot for the rounded corner overlays and screenshot // the whole display to include the rounded corner overlays. setSkipScreenshotForRoundedCornerOverlays(false, t); - mRoundedCornerOverlay = displayContent.findRoundedCornerOverlays(); - final ScreenCapture.DisplayCaptureArgs captureArgs = - new ScreenCapture.DisplayCaptureArgs.Builder(displayToken) - .setSourceCrop(new Rect(0, 0, width, height)) - .setAllowProtected(true) - .setCaptureSecureLayers(true) - .setHintForSeamlessTransition(true) - .build(); - screenshotBuffer = ScreenCapture.captureDisplay(captureArgs); - } else { + } ScreenCapture.LayerCaptureArgs captureArgs = new ScreenCapture.LayerCaptureArgs.Builder( displayContent.getSurfaceControl()) @@ -206,7 +181,6 @@ class ScreenRotationAnimation { .setHintForSeamlessTransition(true) .build(); screenshotBuffer = ScreenCapture.captureLayers(captureArgs); - } if (screenshotBuffer == null) { Slog.w(TAG, "Unable to take screenshot of display " + displayId); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 21526e789c04..de197a164d10 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3478,9 +3478,9 @@ class Task extends TaskFragment { } } // User Aspect Ratio Settings is enabled if the app is not in SCM - info.topActivityEligibleForUserAspectRatioButton = - mWmService.mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled() - && top != null && !info.topActivityInSizeCompat; + info.topActivityEligibleForUserAspectRatioButton = top != null + && !info.topActivityInSizeCompat + && top.mLetterboxUiController.shouldEnableUserAspectRatioSettings(); info.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed(); } @@ -5102,7 +5102,6 @@ class Task extends TaskFragment { void startActivityLocked(ActivityRecord r, @Nullable Task topTask, boolean newTask, boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) { - final ActivityRecord pipCandidate = findEnterPipOnTaskSwitchCandidate(topTask); Task rTask = r.getTask(); final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront(); final boolean isOrhasTask = rTask == this || hasChild(rTask); @@ -5166,8 +5165,10 @@ class Task extends TaskFragment { // supporting picture-in-picture while pausing only if the starting activity // would not be considered an overlay on top of the current activity // (eg. not fullscreen, or the assistant) - enableEnterPipOnTaskSwitch(pipCandidate, - null /* toFrontTask */, r, options); + if (!ActivityTaskManagerService.isPip2ExperimentEnabled()) { + final ActivityRecord pipCandidate = findEnterPipOnTaskSwitchCandidate(topTask); + enableEnterPipOnTaskSwitch(pipCandidate, null /* toFrontTask */, r, options); + } } boolean doShow = true; if (newTask) { @@ -5240,7 +5241,7 @@ class Task extends TaskFragment { * enter PiP while it is pausing (if supported). Only one of {@param toFrontTask} or * {@param toFrontActivity} should be set. */ - private static void enableEnterPipOnTaskSwitch(@Nullable ActivityRecord pipCandidate, + static void enableEnterPipOnTaskSwitch(@Nullable ActivityRecord pipCandidate, @Nullable Task toFrontTask, @Nullable ActivityRecord toFrontActivity, @Nullable ActivityOptions opts) { if (pipCandidate == null) { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 57f44cb599fe..d1b5350bd61a 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1643,7 +1643,17 @@ class TaskFragment extends WindowContainer<WindowContainer> { // next activity. final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState( "shouldAutoPipWhilePausing", userLeaving); - if (userLeaving && resumingOccludesParent && lastResumedCanPip + + if (ActivityTaskManagerService.isPip2ExperimentEnabled()) { + // If a new task is being launched, then mark the existing top activity as + // supporting picture-in-picture while pausing only if the starting activity + // would not be considered an overlay on top of the current activity + // (eg. not fullscreen, or the assistant) + Task.enableEnterPipOnTaskSwitch(prev, resuming.getTask(), + resuming, resuming.getOptions()); + } + if (prev.supportsEnterPipOnTaskSwitch && userLeaving + && resumingOccludesParent && lastResumedCanPip && prev.pictureInPictureArgs.isAutoEnterEnabled()) { shouldAutoPip = true; } else if (!lastResumedCanPip) { @@ -1656,7 +1666,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { } if (prev.attachedToProcess()) { - if (shouldAutoPip) { + if (shouldAutoPip && ActivityTaskManagerService.isPip2ExperimentEnabled()) { + prev.mPauseSchedulePendingForPip = true; + boolean willAutoPip = mAtmService.prepareAutoEnterPictureAndPictureMode(prev); + ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, requesting PIP mode " + + "via requestStartTransition(): %s, willAutoPip: %b", prev, willAutoPip); + } else if (shouldAutoPip) { prev.mPauseSchedulePendingForPip = true; boolean didAutoPip = mAtmService.enterPictureInPictureMode( prev, prev.pictureInPictureArgs, false /* fromClient */); diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index a27cc3ad9973..ea722b61be6f 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -184,14 +184,31 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } void dispose() { + boolean wasVisible = false; for (int i = mOrganizedTaskFragments.size() - 1; i >= 0; i--) { + final TaskFragment taskFragment = mOrganizedTaskFragments.get(i); + if (taskFragment.isVisibleRequested()) { + wasVisible = true; + } // Cleanup the TaskFragmentOrganizer from all TaskFragments it organized before // removing the windows to prevent it from adding any additional TaskFragment // pending event. - final TaskFragment taskFragment = mOrganizedTaskFragments.get(i); taskFragment.onTaskFragmentOrganizerRemoved(); } + final TransitionController transitionController = mAtmService.getTransitionController(); + if (wasVisible && transitionController.isShellTransitionsEnabled() + && !transitionController.isCollecting()) { + final Task task = mOrganizedTaskFragments.get(0).getTask(); + final boolean containsNonEmbeddedActivity = + task != null && task.getActivity(a -> !a.isEmbedded()) != null; + transitionController.requestStartTransition( + transitionController.createTransition(WindowManager.TRANSIT_CLOSE), + // The task will be removed if all its activities are embedded, then the + // task is the trigger. + containsNonEmbeddedActivity ? null : task, + null /* remoteTransition */, null /* displayChange */); + } // Defer to avoid unnecessary layout when there are multiple TaskFragments removal. mAtmService.deferWindowLayout(); try { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 843e6d154ecb..881fdec4549e 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -174,6 +174,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { private final Token mToken; private IApplicationThread mRemoteAnimApp; + private @Nullable ActivityRecord mPipActivity; + /** Only use for clean-up after binder death! */ private SurfaceControl.Transaction mStartTransaction = null; private SurfaceControl.Transaction mFinishTransaction = null; @@ -510,6 +512,21 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } /** + * Set the pip-able activity participating in this transition. + * @param pipActivity activity about to enter pip + */ + void setPipActivity(@Nullable ActivityRecord pipActivity) { + mPipActivity = pipActivity; + } + + /** + * @return pip-able activity participating in this transition. + */ + @Nullable ActivityRecord getPipActivity() { + return mPipActivity; + } + + /** * Only set flag to the parent tasks and activity itself. */ private void setTransientLaunchToChanges(@NonNull WindowContainer wc) { diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 0d7870131133..7d2933a3398a 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -705,13 +705,21 @@ class TransitionController { try { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Requesting StartTransition: %s", transition); - ActivityManager.RunningTaskInfo info = null; + ActivityManager.RunningTaskInfo startTaskInfo = null; + ActivityManager.RunningTaskInfo pipTaskInfo = null; if (startTask != null) { - info = new ActivityManager.RunningTaskInfo(); - startTask.fillTaskInfo(info); + startTaskInfo = startTask.getTaskInfo(); } - final TransitionRequestInfo request = new TransitionRequestInfo( - transition.mType, info, remoteTransition, displayChange, transition.getFlags()); + + // set the pip task in the request if provided + if (mCollectingTransition.getPipActivity() != null) { + pipTaskInfo = mCollectingTransition.getPipActivity().getTask().getTaskInfo(); + } + + final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType, + startTaskInfo, pipTaskInfo, remoteTransition, displayChange, + transition.getFlags()); + transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos(); transition.mLogger.mRequest = request; mTransitionPlayer.requestStartTransition(transition.getToken(), request); diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 805e7ffe7d76..c89360f4ad6b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -45,6 +45,7 @@ import android.view.SurfaceControlViewHost; import android.view.WindowInfo; import android.view.WindowManager.DisplayImePolicy; import android.view.inputmethod.ImeTracker; +import android.window.ScreenCapture; import com.android.internal.policy.KeyInterceptionInfo; import com.android.server.input.InputManagerService; @@ -954,4 +955,12 @@ public abstract class WindowManagerInternal { /** Returns the SurfaceControl accessibility services should use for accessibility overlays. */ public abstract SurfaceControl getA11yOverlayLayer(int displayId); + + /** + * Captures the entire display specified by the displayId using the args provided. If the args + * are null or if the sourceCrop is invalid or null, the entire display bounds will be captured. + */ + public abstract void captureDisplay(int displayId, + @Nullable ScreenCapture.CaptureArgs captureArgs, + ScreenCapture.ScreenCaptureListener listener); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index e720446d7bad..bab38fd40340 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -97,6 +97,7 @@ import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN; import static android.window.WindowProviderService.isWindowProviderService; + import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT; @@ -333,7 +334,6 @@ import com.android.server.policy.WindowManagerPolicy; import com.android.server.policy.WindowManagerPolicy.ScreenOffListener; import com.android.server.power.ShutdownThread; import com.android.server.utils.PriorityDump; -import com.android.window.flags.FeatureFlagsImpl; import dalvik.annotation.optimization.NeverCompile; @@ -1175,8 +1175,7 @@ public class WindowManagerService extends IWindowManager.Stub mLetterboxConfiguration = new LetterboxConfiguration( // Using SysUI context to have access to Material colors extracted from Wallpaper. - ActivityThread.currentActivityThread().getSystemUiContext(), - new FeatureFlagsImpl()); + ActivityThread.currentActivityThread().getSystemUiContext()); mInputManager = inputManager; // Must be before createDisplayContentLocked. mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); @@ -8408,6 +8407,12 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void captureDisplay(int displayId, @Nullable ScreenCapture.CaptureArgs captureArgs, + ScreenCapture.ScreenCaptureListener listener) { + WindowManagerService.this.captureDisplay(displayId, captureArgs, listener); + } + + @Override public void setInputMethodTargetChangeListener(@NonNull ImeTargetChangeListener listener) { synchronized (mGlobalLock) { mImeTargetChangeListener = listener; @@ -8869,6 +8874,11 @@ public class WindowManagerService extends IWindowManager.Stub h.inputConfig |= InputConfig.NOT_FOCUSABLE; } + // Check private trusted overlay flag to set trustedOverlay field of input window handle. + if ((privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0) { + h.inputConfig |= InputConfig.TRUSTED_OVERLAY; + } + h.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS; h.ownerUid = callingUid; h.ownerPid = callingPid; @@ -8888,8 +8898,6 @@ public class WindowManagerService extends IWindowManager.Stub } final SurfaceControl.Transaction t = mTransactionFactory.get(); - // Check private trusted overlay flag to set trustedOverlay field of input window handle. - h.setTrustedOverlay(t, surface, (privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0); t.setInputWindowInfo(surface, h); t.apply(); t.close(); diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index bfe055354b9c..74a0bafd3a4c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -1279,6 +1279,7 @@ public class WindowManagerShellCommand extends ShellCommand { mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadiusPx(); mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier(); + mLetterboxConfiguration.resetLetterboxVerticalPositionMultiplier(); mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled(); mLetterboxConfiguration.resetIsVerticalReachabilityEnabled(); mLetterboxConfiguration.resetEnabledAutomaticReachabilityInBookMode(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 822082ba4c70..b12cc0b30f53 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -28,7 +28,6 @@ import static android.graphics.GraphicsProtos.dumpPointProto; import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static android.os.PowerManager.DRAW_WAKE_LOCK; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import static android.view.InputWindowHandle.USE_SURFACE_TRUSTED_OVERLAY; import static android.view.SurfaceControl.Transaction; import static android.view.SurfaceControl.getGlobalTransaction; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; @@ -99,6 +98,7 @@ import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME; import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER; import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET; + import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; @@ -1112,9 +1112,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mInputWindowHandle.setName(getName()); mInputWindowHandle.setPackageName(mAttrs.packageName); mInputWindowHandle.setLayoutParamsType(mAttrs.type); - if (!USE_SURFACE_TRUSTED_OVERLAY) { - mInputWindowHandle.setTrustedOverlay(isWindowTrustedOverlay()); - } + mInputWindowHandle.setTrustedOverlay(shouldWindowHandleBeTrusted(s)); if (DEBUG) { Slog.v(TAG, "Window " + this + " client=" + c.asBinder() + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a); @@ -1195,12 +1193,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP : service.mAtmService.getProcessController(s.mPid, s.mUid); } - private boolean isWindowTrustedOverlay() { + boolean shouldWindowHandleBeTrusted(Session s) { return InputMonitor.isTrustedOverlay(mAttrs.type) || ((mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0 - && mSession.mCanAddInternalSystemWindow) + && s.mCanAddInternalSystemWindow) || ((mAttrs.privateFlags & PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY) != 0 - && mSession.mCanCreateSystemApplicationOverlay); + && s.mCanCreateSystemApplicationOverlay); } int getTouchOcclusionMode() { @@ -5194,9 +5192,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP updateFrameRateSelectionPriorityIfNeeded(); updateScaleIfNeeded(); mWinAnimator.prepareSurfaceLocked(getSyncTransaction()); - if (USE_SURFACE_TRUSTED_OVERLAY) { - getSyncTransaction().setTrustedOverlay(mSurfaceControl, isWindowTrustedOverlay()); - } } super.prepareSurfaces(); } @@ -5949,13 +5944,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } boolean isTrustedOverlay() { - if (USE_SURFACE_TRUSTED_OVERLAY) { - WindowState parentWindow = getParentWindow(); - return isWindowTrustedOverlay() || (parentWindow != null - && parentWindow.isWindowTrustedOverlay()); - } else { - return mInputWindowHandle.isTrustedOverlay(); - } + return mInputWindowHandle.isTrustedOverlay(); } public boolean receiveFocusFromTapOutside() { diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index d22e02e9dae6..4203e89ec618 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -50,6 +50,13 @@ maxOccurs="1"/> <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0" maxOccurs="1"/> + + <xs:element name="hdrBrightnessConfig" type="hdrBrightnessConfig" + minOccurs="0" maxOccurs="1"> + <xs:annotation name="nullable"/> + <xs:annotation name="final"/> + </xs:element> + <xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1"/> <xs:element type="autoBrightness" name="autoBrightness" minOccurs="0" maxOccurs="1"/> @@ -238,6 +245,31 @@ </xs:all> </xs:complexType> + <!-- brightness config for HDR content --> + <xs:complexType name="hdrBrightnessConfig"> + <!-- lux level from light sensor to screen brightness recommended max value map. --> + <xs:element name="brightnessMap" type="nonNegativeFloatToFloatMap"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + <!-- Debounce for brightness increase in millis --> + <xs:element name="brightnessIncreaseDebounceMillis" type="xs:nonNegativeInteger"> + <xs:annotation name="final"/> + </xs:element> + <!-- Debounce for brightness decrease in millis --> + <xs:element name="brightnessDecreaseDebounceMillis" type="xs:nonNegativeInteger"> + <xs:annotation name="final"/> + </xs:element> + <!-- Animation time for brightness increase in millis --> + <xs:element name="brightnessIncreaseDurationMillis" type="xs:nonNegativeInteger"> + <xs:annotation name="final"/> + </xs:element> + <!-- Animation time for brightness decrease in millis --> + <xs:element name="brightnessDecreaseDurationMillis" type="xs:nonNegativeInteger"> + <xs:annotation name="final"/> + </xs:element> + </xs:complexType> + <!-- Maps to PowerManager.THERMAL_STATUS_* values. --> <xs:simpleType name="thermalStatus"> <xs:restriction base="xs:string"> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 6364c1feb659..ebd9b1c23a50 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -101,6 +101,7 @@ package com.android.server.display.config { method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping(); method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds(); method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle(); + method @Nullable public final com.android.server.display.config.HdrBrightnessConfig getHdrBrightnessConfig(); method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode(); method public final com.android.server.display.config.SensorDetails getLightSensor(); method public com.android.server.display.config.LuxThrottling getLuxThrottling(); @@ -130,6 +131,7 @@ package com.android.server.display.config { method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping); method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds); method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds); + method public final void setHdrBrightnessConfig(@Nullable com.android.server.display.config.HdrBrightnessConfig); method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode); method public final void setLightSensor(com.android.server.display.config.SensorDetails); method public void setLuxThrottling(com.android.server.display.config.LuxThrottling); @@ -168,6 +170,20 @@ package com.android.server.display.config { method public final void setTimeWindowSecs_all(@NonNull java.math.BigInteger); } + public class HdrBrightnessConfig { + ctor public HdrBrightnessConfig(); + method public final java.math.BigInteger getBrightnessDecreaseDebounceMillis(); + method public final java.math.BigInteger getBrightnessDecreaseDurationMillis(); + method public final java.math.BigInteger getBrightnessIncreaseDebounceMillis(); + method public final java.math.BigInteger getBrightnessIncreaseDurationMillis(); + method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getBrightnessMap(); + method public final void setBrightnessDecreaseDebounceMillis(java.math.BigInteger); + method public final void setBrightnessDecreaseDurationMillis(java.math.BigInteger); + method public final void setBrightnessIncreaseDebounceMillis(java.math.BigInteger); + method public final void setBrightnessIncreaseDurationMillis(java.math.BigInteger); + method public final void setBrightnessMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap); + } + public class HighBrightnessMode { ctor public HighBrightnessMode(); method @NonNull public final boolean getAllowInLowPowerMode_all(); diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index d8c684fb8c1e..82d39d6c6982 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -35,11 +35,13 @@ import android.credentials.CreateCredentialException; import android.credentials.CreateCredentialRequest; import android.credentials.CredentialOption; import android.credentials.CredentialProviderInfo; +import android.credentials.GetCandidateCredentialsRequest; import android.credentials.GetCredentialException; import android.credentials.GetCredentialRequest; import android.credentials.IClearCredentialStateCallback; import android.credentials.ICreateCredentialCallback; import android.credentials.ICredentialManager; +import android.credentials.IGetCandidateCredentialsCallback; import android.credentials.IGetCredentialCallback; import android.credentials.IPrepareGetCredentialCallback; import android.credentials.ISetEnabledProvidersCallback; @@ -96,6 +98,9 @@ public final class CredentialManagerService private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER = "enable_credential_manager"; + private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API = + "enable_credential_description_api"; + private final Context mContext; /** Cache of system service list per user id. */ @@ -321,7 +326,14 @@ public final class CredentialManagerService } public static boolean isCredentialDescriptionApiEnabled() { - return true; + final long origId = Binder.clearCallingIdentity(); + try { + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, + false); + } finally { + Binder.restoreCallingIdentity(origId); + } } @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked @@ -461,6 +473,17 @@ public final class CredentialManagerService final class CredentialManagerServiceStub extends ICredentialManager.Stub { @Override + public ICancellationSignal getCandidateCredentials( + GetCandidateCredentialsRequest request, + IGetCandidateCredentialsCallback callback, + final String callingPackage) { + Slog.i(TAG, "starting getCandidateCredentials with callingPackage: " + + callingPackage); + // TODO(): Implement + return CancellationSignal.createTransport(); + } + + @Override public ICancellationSignal executeGetCredential( GetCredentialRequest request, IGetCredentialCallback callback, @@ -954,7 +977,7 @@ public final class CredentialManagerService Slog.i(TAG, "registerCredentialDescription with callingPackage: " + callingPackage); if (!isCredentialDescriptionApiEnabled()) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Feature not supported"); } enforceCallingPackage(callingPackage, Binder.getCallingUid()); @@ -974,7 +997,7 @@ public final class CredentialManagerService if (!isCredentialDescriptionApiEnabled()) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Feature not supported"); } enforceCallingPackage(callingPackage, Binder.getCallingUid()); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index 21b12914bc4c..d0ead141b58c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -1472,11 +1472,10 @@ final class DevicePolicyEngine { synchronized (mLock) { clear(); new DevicePoliciesReaderWriter().readFromFileLocked(); - reapplyAllPoliciesLocked(); } } - private <V> void reapplyAllPoliciesLocked() { + <V> void reapplyAllPoliciesLocked() { for (PolicyKey policy : mGlobalPolicies.keySet()) { PolicyState<?> policyState = mGlobalPolicies.get(policy); // Policy definition and value will always be of the same type diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 50dc061c1ab2..84d1a452fa7e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3347,6 +3347,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mOwners.systemReady(); applyManagedSubscriptionsPolicyIfRequired(); break; + case SystemService.PHASE_SYSTEM_SERVICES_READY: + synchronized (getLockObject()) { + mDevicePolicyEngine.reapplyAllPoliciesLocked(); + } + break; case SystemService.PHASE_ACTIVITY_MANAGER_READY: synchronized (getLockObject()) { migrateToProfileOnOrganizationOwnedDeviceIfCompLocked(); @@ -15836,6 +15841,83 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } /** + * @param restriction The restriction enforced by admin. It could be any user restriction or + * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA}, + * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE} and {@link + * DevicePolicyManager#POLICY_SUSPEND_PACKAGES}. + */ + private Set<android.app.admin.EnforcingAdmin> getEnforcingAdminsForRestrictionInternal( + int userId, @NonNull String restriction) { + Objects.requireNonNull(restriction); + Set<android.app.admin.EnforcingAdmin> admins = new HashSet<>(); + // For POLICY_SUSPEND_PACKAGES return PO or DO to keep the behavior same as + // before the bug fix for b/192245204. + if (DevicePolicyManager.POLICY_SUSPEND_PACKAGES.equals( + restriction)) { + ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId); + if (profileOwner != null) { + EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin( + profileOwner, userId); + admins.add(admin.getParcelableAdmin()); + return admins; + } + final Pair<Integer, ComponentName> deviceOwner = + mOwners.getDeviceOwnerUserIdAndComponent(); + if (deviceOwner != null && deviceOwner.first == userId) { + EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin( + deviceOwner.second, deviceOwner.first); + admins.add(admin.getParcelableAdmin()); + return admins; + } + } else { + long ident = mInjector.binderClearCallingIdentity(); + try { + PolicyDefinition<Boolean> policyDefinition = getPolicyDefinitionForRestriction( + restriction); + Boolean value = mDevicePolicyEngine.getResolvedPolicy(policyDefinition, userId); + if (value != null && value) { + Map<EnforcingAdmin, PolicyValue<Boolean>> globalPolicies = + mDevicePolicyEngine.getGlobalPoliciesSetByAdmins(policyDefinition); + for (EnforcingAdmin admin : globalPolicies.keySet()) { + if (globalPolicies.get(admin) != null + && Boolean.TRUE.equals(globalPolicies.get(admin).getValue())) { + admins.add(admin.getParcelableAdmin()); + } + } + + Map<EnforcingAdmin, PolicyValue<Boolean>> localPolicies = + mDevicePolicyEngine.getLocalPoliciesSetByAdmins( + policyDefinition, userId); + for (EnforcingAdmin admin : localPolicies.keySet()) { + if (localPolicies.get(admin) != null + && Boolean.TRUE.equals(localPolicies.get(admin).getValue())) { + admins.add(admin.getParcelableAdmin()); + } + } + return admins; + } + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } + } + return admins; + } + + private static PolicyDefinition<Boolean> getPolicyDefinitionForRestriction( + @NonNull String restriction) { + Objects.requireNonNull(restriction); + if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) { + return PolicyDefinition.getPolicyDefinitionForUserRestriction( + UserManager.DISALLOW_CAMERA); + } else if (DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) { + return PolicyDefinition.SCREEN_CAPTURE_DISABLED; + } else { + return PolicyDefinition.getPolicyDefinitionForUserRestriction(restriction); + } + } + + + /** * Excludes restrictions imposed by UserManager. */ private List<UserManager.EnforcingUser> getDevicePolicySources( @@ -15873,6 +15955,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return getEnforcingAdminAndUserDetailsInternal(userId, restriction); } + @Override + public List<android.app.admin.EnforcingAdmin> getEnforcingAdminsForRestriction( + int userId, String restriction) { + Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity())); + return new ArrayList<>(getEnforcingAdminsForRestrictionInternal(userId, restriction)); + } + /** * @param restriction The restriction enforced by admin. It could be any user restriction or * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java index 5243d14af1ab..006642235615 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java @@ -228,7 +228,8 @@ final class EnforcingAdmin { return new android.app.admin.EnforcingAdmin( mPackageName, authority, - UserHandle.of(mUserId)); + UserHandle.of(mUserId), + mComponentName); } /** diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index dc2fbfc6414d..2889c749f679 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -24,9 +24,7 @@ import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_MORE_DETAILS; import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND; import static android.content.pm.parsing.FrameworkParsingPackageUtils.parsePublicKey; import static android.content.res.Resources.ID_NULL; - import static com.android.server.pm.PackageManagerService.WRITE_USER_PACKAGE_RESTRICTIONS; - import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; @@ -997,7 +995,7 @@ public class PackageManagerSettingsTests { null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, UUID.randomUUID()); - origPkgSetting01.setUserState(0, 100, 1, true, false, false, false, 0, null, false, + origPkgSetting01.setUserState(0, 100, 100, 1, true, false, false, false, 0, null, false, false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}), new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning", "splashScreenTheme", 1000L, PackageManager.USER_MIN_ASPECT_RATIO_UNSET, null); diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt index 3ef3a89da94d..3ab25479686d 100644 --- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt @@ -16,12 +16,14 @@ package com.android.server.permission.test +import android.Manifest import android.content.pm.PackageManager import android.content.pm.PermissionGroupInfo import android.content.pm.PermissionInfo +import android.content.pm.SigningDetails +import android.os.Build import android.os.Bundle import android.util.ArrayMap -import android.util.ArraySet import android.util.SparseArray import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.modules.utils.testing.ExtendedMockitoRule @@ -33,12 +35,15 @@ import com.android.server.permission.access.immutable.* // ktlint-disable no-wil import com.android.server.permission.access.permission.AppIdPermissionPolicy import com.android.server.permission.access.permission.Permission import com.android.server.permission.access.permission.PermissionFlags +import com.android.server.permission.access.util.hasBits import com.android.server.pm.parsing.PackageInfoUtils +import com.android.server.pm.permission.PermissionAllowlist import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState import com.android.server.pm.pkg.PackageUserState import com.android.server.pm.pkg.component.ParsedPermission import com.android.server.pm.pkg.component.ParsedPermissionGroup +import com.android.server.testutils.any import com.android.server.testutils.mock import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertWithMessage @@ -46,6 +51,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyLong /** * Mocking unit test for AppIdPermissionPolicy. @@ -55,11 +61,16 @@ class AppIdPermissionPolicyTest { private lateinit var oldState: MutableAccessState private lateinit var newState: MutableAccessState - private lateinit var androidPackage0: AndroidPackage - private lateinit var androidPackage1: AndroidPackage - - private lateinit var packageState0: PackageState - private lateinit var packageState1: PackageState + private val defaultPermissionGroup = mockParsedPermissionGroup( + PERMISSION_GROUP_NAME_0, + PACKAGE_NAME_0 + ) + private val defaultPermissionTree = mockParsedPermission( + PERMISSION_TREE_NAME, + PACKAGE_NAME_0, + isTree = true + ) + private val defaultPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0) private val appIdPermissionPolicy = AppIdPermissionPolicy() @@ -70,160 +81,124 @@ class AppIdPermissionPolicyTest { .build() @Before - fun init() { + fun setUp() { oldState = MutableAccessState() createUserState(USER_ID_0) - createUserState(USER_ID_1) oldState.mutateExternalState().setPackageStates(ArrayMap()) - - androidPackage0 = mockAndroidPackage( - PACKAGE_NAME_0, - PERMISSION_GROUP_NAME_0, - PERMISSION_NAME_0 - ) - androidPackage1 = mockAndroidPackage( - PACKAGE_NAME_1, - PERMISSION_GROUP_NAME_1, - PERMISSION_NAME_1 - ) - - packageState0 = mockPackageState(APP_ID_0, androidPackage0) - packageState1 = mockPackageState(APP_ID_1, androidPackage1) + oldState.mutateExternalState().setDisabledSystemPackageStates(ArrayMap()) + mockPackageInfoUtilsGeneratePermissionInfo() + mockPackageInfoUtilsGeneratePermissionGroupInfo() } - private fun mockAndroidPackage( - packageName: String, - permissionGroupName: String, - permissionName: String, - ): AndroidPackage { - val parsedPermissionGroup = mock<ParsedPermissionGroup> { - whenever(name).thenReturn(permissionGroupName) - whenever(metaData).thenReturn(Bundle()) - } + private fun createUserState(userId: Int) { + oldState.mutateExternalState().mutateUserIds().add(userId) + oldState.mutateUserStatesNoWrite().put(userId, MutableUserState()) + } - @Suppress("DEPRECATION") - val permissionGroupInfo = PermissionGroupInfo().apply { - name = permissionGroupName - this.packageName = packageName - } + private fun mockPackageInfoUtilsGeneratePermissionInfo() { wheneverStatic { - PackageInfoUtils.generatePermissionGroupInfo( - parsedPermissionGroup, - PackageManager.GET_META_DATA.toLong() - ) - }.thenReturn(permissionGroupInfo) - - val parsedPermission = mock<ParsedPermission> { - whenever(name).thenReturn(permissionName) - whenever(isTree).thenReturn(false) - whenever(metaData).thenReturn(Bundle()) + PackageInfoUtils.generatePermissionInfo(any(ParsedPermission::class.java), anyLong()) + }.thenAnswer { invocation -> + val parsedPermission = invocation.getArgument<ParsedPermission>(0) + val generateFlags = invocation.getArgument<Long>(1) + PermissionInfo(parsedPermission.backgroundPermission).apply { + name = parsedPermission.name + packageName = parsedPermission.packageName + metaData = if (generateFlags.toInt().hasBits(PackageManager.GET_META_DATA)) { + parsedPermission.metaData + } else { + null + } + @Suppress("DEPRECATION") + protectionLevel = parsedPermission.protectionLevel + group = parsedPermission.group + flags = parsedPermission.flags + } } + } - @Suppress("DEPRECATION") - val permissionInfo = PermissionInfo().apply { - name = permissionName - this.packageName = packageName - } + private fun mockPackageInfoUtilsGeneratePermissionGroupInfo() { wheneverStatic { - PackageInfoUtils.generatePermissionInfo( - parsedPermission, - PackageManager.GET_META_DATA.toLong() + PackageInfoUtils.generatePermissionGroupInfo( + any(ParsedPermissionGroup::class.java), + anyLong() ) - }.thenReturn(permissionInfo) - - val requestedPermissions = ArraySet<String>() - return mock { - whenever(this.packageName).thenReturn(packageName) - whenever(this.requestedPermissions).thenReturn(requestedPermissions) - whenever(permissionGroups).thenReturn(listOf(parsedPermissionGroup)) - whenever(permissions).thenReturn(listOf(parsedPermission)) - whenever(signingDetails).thenReturn(mock {}) - } - } - - private fun mockPackageState( - appId: Int, - androidPackage: AndroidPackage, - ): PackageState { - val packageName = androidPackage.packageName - oldState.mutateExternalState().mutateAppIdPackageNames().mutateOrPut(appId) { - MutableIndexedListSet() - }.add(packageName) - - val userStates = SparseArray<PackageUserState>().apply { - put(USER_ID_0, mock { whenever(isInstantApp).thenReturn(false) }) - } - val mockPackageState: PackageState = mock { - whenever(this.packageName).thenReturn(packageName) - whenever(this.appId).thenReturn(appId) - whenever(this.androidPackage).thenReturn(androidPackage) - whenever(isSystem).thenReturn(false) - whenever(this.userStates).thenReturn(userStates) - } - oldState.mutateExternalState().setPackageStates( - oldState.mutateExternalState().packageStates.toMutableMap().apply { - put(packageName, mockPackageState) + }.thenAnswer { invocation -> + val parsedPermissionGroup = invocation.getArgument<ParsedPermissionGroup>(0) + val generateFlags = invocation.getArgument<Long>(1) + @Suppress("DEPRECATION") + PermissionGroupInfo().apply { + name = parsedPermissionGroup.name + packageName = parsedPermissionGroup.packageName + metaData = if (generateFlags.toInt().hasBits(PackageManager.GET_META_DATA)) { + parsedPermissionGroup.metaData + } else { + null + } + flags = parsedPermissionGroup.flags } - ) - return mockPackageState - } - - private fun createUserState(userId: Int) { - oldState.mutateUserStatesNoWrite().put(userId, MutableUserState()) + } } @Test fun testResetRuntimePermissions_runtimeGranted_getsRevoked() { val oldFlags = PermissionFlags.RUNTIME_GRANTED val expectedNewFlags = 0 - testResetRuntimePermissions(oldFlags, expectedNewFlags) {} + testResetRuntimePermissions(oldFlags, expectedNewFlags) } @Test fun testResetRuntimePermissions_roleGranted_getsGranted() { val oldFlags = PermissionFlags.ROLE val expectedNewFlags = PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED - testResetRuntimePermissions(oldFlags, expectedNewFlags) {} + testResetRuntimePermissions(oldFlags, expectedNewFlags) } @Test fun testResetRuntimePermissions_nullAndroidPackage_remainsUnchanged() { val oldFlags = PermissionFlags.RUNTIME_GRANTED val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED - testResetRuntimePermissions(oldFlags, expectedNewFlags) { - whenever(packageState0.androidPackage).thenReturn(null) - } + testResetRuntimePermissions(oldFlags, expectedNewFlags, isAndroidPackageMissing = true) } - private inline fun testResetRuntimePermissions( + private fun testResetRuntimePermissions( oldFlags: Int, expectedNewFlags: Int, - additionalSetup: () -> Unit + isAndroidPackageMissing: Boolean = false ) { - createSystemStatePermission( - APP_ID_0, - PACKAGE_NAME_0, + val parsedPermission = mockParsedPermission( PERMISSION_NAME_0, - PermissionInfo.PROTECTION_DANGEROUS + PACKAGE_NAME_0, + protectionLevel = PermissionInfo.PROTECTION_DANGEROUS, ) - androidPackage0.requestedPermissions.add(PERMISSION_NAME_0) - oldState.mutateUserState(USER_ID_0)!!.mutateAppIdPermissionFlags().mutateOrPut(APP_ID_0) { - MutableIndexedMap() - }.put(PERMISSION_NAME_0, oldFlags) - - additionalSetup() + val permissionOwnerPackageState = mockPackageState( + APP_ID_0, + mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission)) + ) + val requestingPackageState = if (isAndroidPackageMissing) { + mockPackageState(APP_ID_1, PACKAGE_NAME_1) + } else { + mockPackageState( + APP_ID_1, + mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0)) + ) + } + setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags) + addPackageState(permissionOwnerPackageState) + addPackageState(requestingPackageState) + addPermission(parsedPermission) mutateState { with(appIdPermissionPolicy) { - resetRuntimePermissions(PACKAGE_NAME_0, USER_ID_0) + resetRuntimePermissions(PACKAGE_NAME_1, USER_ID_0) } } - val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) assertWithMessage( "After resetting runtime permissions, permission flags did not match" + - " expected values: expectedNewFlags is $expectedNewFlags," + - " actualFlags is $actualFlags, while the oldFlags is $oldFlags" + " expected values: expectedNewFlags is $expectedNewFlags," + + " actualFlags is $actualFlags, while the oldFlags is $oldFlags" ) .that(actualFlags) .isEqualTo(expectedNewFlags) @@ -231,285 +206,1499 @@ class AppIdPermissionPolicyTest { @Test fun testOnPackageAdded_permissionsOfMissingSystemApp_getsAdopted() { - testOnPackageAdded { - adoptPermissionTestSetup() - whenever(packageState1.androidPackage).thenReturn(null) - } + testAdoptPermissions(hasMissingPackage = true, isSystem = true) - val permission0 = newState.systemState.permissions[PERMISSION_NAME_0] assertWithMessage( "After onPackageAdded() is called for a null adopt permission package," + - " the permission package name: ${permission0!!.packageName} did not match" + - " the expected package name: $PACKAGE_NAME_0" + " the permission package name: ${getPermission(PERMISSION_NAME_0)?.packageName}" + + " did not match the expected package name: $PACKAGE_NAME_0" ) - .that(permission0.packageName) + .that(getPermission(PERMISSION_NAME_0)?.packageName) .isEqualTo(PACKAGE_NAME_0) } @Test fun testOnPackageAdded_permissionsOfExistingSystemApp_notAdopted() { - testOnPackageAdded { - adoptPermissionTestSetup() - } + testAdoptPermissions(isSystem = true) - val permission0 = newState.systemState.permissions[PERMISSION_NAME_0] assertWithMessage( "After onPackageAdded() is called for a non-null adopt permission" + - " package, the permission package name: ${permission0!!.packageName} should" + - " not match the package name: $PACKAGE_NAME_0" + " package, the permission package name:" + + " ${getPermission(PERMISSION_NAME_0)?.packageName} should not match the" + + " package name: $PACKAGE_NAME_0" ) - .that(permission0.packageName) + .that(getPermission(PERMISSION_NAME_0)?.packageName) .isNotEqualTo(PACKAGE_NAME_0) } @Test fun testOnPackageAdded_permissionsOfNonSystemApp_notAdopted() { - testOnPackageAdded { - adoptPermissionTestSetup() - whenever(packageState1.isSystem).thenReturn(false) - } + testAdoptPermissions(hasMissingPackage = true) - val permission0 = newState.systemState.permissions[PERMISSION_NAME_0] assertWithMessage( "After onPackageAdded() is called for a non-system adopt permission" + - " package, the permission package name: ${permission0!!.packageName} should" + - " not match the package name: $PACKAGE_NAME_0" + " package, the permission package name:" + + " ${getPermission(PERMISSION_NAME_0)?.packageName} should not match the" + + " package name: $PACKAGE_NAME_0" ) - .that(permission0.packageName) + .that(getPermission(PERMISSION_NAME_0)?.packageName) .isNotEqualTo(PACKAGE_NAME_0) } - private fun adoptPermissionTestSetup() { - createSystemStatePermission( - APP_ID_1, - PACKAGE_NAME_1, - PERMISSION_NAME_0, - PermissionInfo.PROTECTION_SIGNATURE - ) - whenever(androidPackage0.adoptPermissions).thenReturn(listOf(PACKAGE_NAME_1)) - whenever(packageState1.isSystem).thenReturn(true) + private fun testAdoptPermissions( + hasMissingPackage: Boolean = false, + isSystem: Boolean = false + ) { + val parsedPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_1) + val packageToAdoptPermission = if (hasMissingPackage) { + mockPackageState(APP_ID_1, PACKAGE_NAME_1, isSystem = isSystem) + } else { + mockPackageState( + APP_ID_1, + mockAndroidPackage( + PACKAGE_NAME_1, + permissions = listOf(parsedPermission) + ), + isSystem = isSystem + ) + } + addPackageState(packageToAdoptPermission) + addPermission(parsedPermission) + + mutateState { + val installedPackage = mockPackageState( + APP_ID_0, + mockAndroidPackage( + PACKAGE_NAME_0, + permissions = listOf(defaultPermission), + adoptPermissions = listOf(PACKAGE_NAME_1) + ) + ) + addPackageState(installedPackage, newState) + with(appIdPermissionPolicy) { + onPackageAdded(installedPackage) + } + } } @Test fun testOnPackageAdded_newPermissionGroup_getsDeclared() { - testOnPackageAdded {} + mutateState { + val packageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage()) + addPackageState(packageState, newState) + with(appIdPermissionPolicy) { + onPackageAdded(packageState) + } + } assertWithMessage( "After onPackageAdded() is called when there is no existing" + - " permission groups, the new permission group $PERMISSION_GROUP_NAME_0 is not added" + " permission groups, the new permission group $PERMISSION_GROUP_NAME_0 is not added" ) - .that(newState.systemState.permissionGroups[PERMISSION_GROUP_NAME_0]?.name) + .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.name) .isEqualTo(PERMISSION_GROUP_NAME_0) } @Test fun testOnPackageAdded_systemAppTakingOverPermissionGroupDefinition_getsTakenOver() { - testOnPackageAdded { - whenever(packageState0.isSystem).thenReturn(true) - createSystemStatePermissionGroup(PACKAGE_NAME_1, PERMISSION_GROUP_NAME_0) - } + testTakingOverPermissionAndPermissionGroupDefinitions(newPermissionOwnerIsSystem = true) assertWithMessage( "After onPackageAdded() is called when $PERMISSION_GROUP_NAME_0 already" + - " exists in the system, the system app $PACKAGE_NAME_0 didn't takeover the ownership" + - " of this permission group" + " exists in the system, the system app $PACKAGE_NAME_0 didn't takeover the" + + " ownership of this permission group" ) - .that(newState.systemState.permissionGroups[PERMISSION_GROUP_NAME_0]?.packageName) + .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName) .isEqualTo(PACKAGE_NAME_0) } @Test fun testOnPackageAdded_instantApps_remainsUnchanged() { - testOnPackageAdded { - (packageState0.userStates as SparseArray<PackageUserState>).apply { - put(0, mock { whenever(isInstantApp).thenReturn(true) }) - } - } + testTakingOverPermissionAndPermissionGroupDefinitions( + newPermissionOwnerIsInstant = true, + permissionGroupAlreadyExists = false + ) assertWithMessage( "After onPackageAdded() is called for an instant app," + - " the new permission group $PERMISSION_GROUP_NAME_0 should not be added" + " the new permission group $PERMISSION_GROUP_NAME_0 should not be added" ) - .that(newState.systemState.permissionGroups[PERMISSION_GROUP_NAME_0]) + .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)) .isNull() } @Test fun testOnPackageAdded_nonSystemAppTakingOverPermissionGroupDefinition_remainsUnchanged() { - testOnPackageAdded { - createSystemStatePermissionGroup(PACKAGE_NAME_1, PERMISSION_GROUP_NAME_0) - } + testTakingOverPermissionAndPermissionGroupDefinitions() assertWithMessage( "After onPackageAdded() is called when $PERMISSION_GROUP_NAME_0 already" + - " exists in the system, non-system app $PACKAGE_NAME_0 shouldn't takeover ownership" + - " of this permission group" + " exists in the system, non-system app $PACKAGE_NAME_0 shouldn't takeover" + + " ownership of this permission group" ) - .that(newState.systemState.permissionGroups[PERMISSION_GROUP_NAME_0]?.packageName) + .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName) .isEqualTo(PACKAGE_NAME_1) } @Test fun testOnPackageAdded_takingOverPermissionGroupDeclaredBySystemApp_remainsUnchanged() { - testOnPackageAdded { - whenever(packageState1.isSystem).thenReturn(true) - createSystemStatePermissionGroup(PACKAGE_NAME_1, PERMISSION_GROUP_NAME_0) - } + testTakingOverPermissionAndPermissionGroupDefinitions(oldPermissionOwnerIsSystem = true) assertWithMessage( "After onPackageAdded() is called when $PERMISSION_GROUP_NAME_0 already" + - " exists in the system and is owned by a system app, app $PACKAGE_NAME_0 shouldn't" + - " takeover ownership of this permission group" + " exists in the system and is owned by a system app, app $PACKAGE_NAME_0" + + " shouldn't takeover ownership of this permission group" ) - .that(newState.systemState.permissionGroups[PERMISSION_GROUP_NAME_0]?.packageName) + .that(getPermissionGroup(PERMISSION_GROUP_NAME_0)?.packageName) .isEqualTo(PACKAGE_NAME_1) } @Test fun testOnPackageAdded_newPermission_getsDeclared() { - testOnPackageAdded {} + mutateState { + val packageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage()) + addPackageState(packageState, newState) + with(appIdPermissionPolicy) { + onPackageAdded(packageState) + } + } assertWithMessage( "After onPackageAdded() is called when there is no existing" + - " permissions, the new permission $PERMISSION_NAME_0 is not added" + " permissions, the new permission $PERMISSION_NAME_0 is not added" ) - .that(newState.systemState.permissions[PERMISSION_NAME_0]?.name) + .that(getPermission(PERMISSION_NAME_0)?.name) .isEqualTo(PERMISSION_NAME_0) } @Test fun testOnPackageAdded_configPermission_getsTakenOver() { - testOnPackageAdded { - whenever(packageState0.isSystem).thenReturn(true) - createSystemStatePermission( - APP_ID_0, - PACKAGE_NAME_1, - PERMISSION_NAME_0, - PermissionInfo.PROTECTION_DANGEROUS, - Permission.TYPE_CONFIG, - false - ) - } + testTakingOverPermissionAndPermissionGroupDefinitions( + oldPermissionOwnerIsSystem = true, + newPermissionOwnerIsSystem = true, + type = Permission.TYPE_CONFIG, + isReconciled = false + ) assertWithMessage( "After onPackageAdded() is called for a config permission with" + - " no owner, the ownership is not taken over by a system app $PACKAGE_NAME_0" + " no owner, the ownership is not taken over by a system app $PACKAGE_NAME_0" ) - .that(newState.systemState.permissions[PERMISSION_NAME_0]?.packageName) + .that(getPermission(PERMISSION_NAME_0)?.packageName) .isEqualTo(PACKAGE_NAME_0) } @Test fun testOnPackageAdded_systemAppTakingOverPermissionDefinition_getsTakenOver() { - testOnPackageAdded { - whenever(packageState0.isSystem).thenReturn(true) - createSystemStatePermission( - APP_ID_1, - PACKAGE_NAME_1, - PERMISSION_NAME_0, - PermissionInfo.PROTECTION_DANGEROUS - ) - } + testTakingOverPermissionAndPermissionGroupDefinitions(newPermissionOwnerIsSystem = true) assertWithMessage( "After onPackageAdded() is called when $PERMISSION_NAME_0 already" + - " exists in the system, the system app $PACKAGE_NAME_0 didn't takeover the ownership" + - " of this permission" + " exists in the system, the system app $PACKAGE_NAME_0 didn't takeover ownership" + + " of this permission" ) - .that(newState.systemState.permissions[PERMISSION_NAME_0]?.packageName) + .that(getPermission(PERMISSION_NAME_0)?.packageName) .isEqualTo(PACKAGE_NAME_0) } @Test fun testOnPackageAdded_nonSystemAppTakingOverPermissionDefinition_remainsUnchanged() { - testOnPackageAdded { - createSystemStatePermission( - APP_ID_1, - PACKAGE_NAME_1, - PERMISSION_NAME_0, - PermissionInfo.PROTECTION_DANGEROUS - ) - } + testTakingOverPermissionAndPermissionGroupDefinitions() assertWithMessage( "After onPackageAdded() is called when $PERMISSION_NAME_0 already" + - " exists in the system, the non-system app $PACKAGE_NAME_0 shouldn't takeover" + - " ownership of this permission" + " exists in the system, the non-system app $PACKAGE_NAME_0 shouldn't takeover" + + " ownership of this permission" ) - .that(newState.systemState.permissions[PERMISSION_NAME_0]?.packageName) + .that(getPermission(PERMISSION_NAME_0)?.packageName) .isEqualTo(PACKAGE_NAME_1) } @Test fun testOnPackageAdded_takingOverPermissionDeclaredBySystemApp_remainsUnchanged() { - testOnPackageAdded { - whenever(packageState1.isSystem).thenReturn(true) - createSystemStatePermission( - APP_ID_1, - PACKAGE_NAME_1, + testTakingOverPermissionAndPermissionGroupDefinitions(oldPermissionOwnerIsSystem = true) + + assertWithMessage( + "After onPackageAdded() is called when $PERMISSION_NAME_0 already" + + " exists in system and is owned by a system app, the $PACKAGE_NAME_0 shouldn't" + + " takeover ownership of this permission" + ) + .that(getPermission(PERMISSION_NAME_0)?.packageName) + .isEqualTo(PACKAGE_NAME_1) + } + + private fun testTakingOverPermissionAndPermissionGroupDefinitions( + oldPermissionOwnerIsSystem: Boolean = false, + newPermissionOwnerIsSystem: Boolean = false, + newPermissionOwnerIsInstant: Boolean = false, + permissionGroupAlreadyExists: Boolean = true, + permissionAlreadyExists: Boolean = true, + type: Int = Permission.TYPE_MANIFEST, + isReconciled: Boolean = true, + ) { + val oldPermissionOwnerPackageState = mockPackageState( + APP_ID_1, + PACKAGE_NAME_1, + isSystem = oldPermissionOwnerIsSystem + ) + addPackageState(oldPermissionOwnerPackageState) + if (permissionGroupAlreadyExists) { + addPermissionGroup(mockParsedPermissionGroup(PERMISSION_GROUP_NAME_0, PACKAGE_NAME_1)) + } + if (permissionAlreadyExists) { + addPermission( + mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_1), + type = type, + isReconciled = isReconciled + ) + } + + mutateState { + val newPermissionOwnerPackageState = mockPackageState( + APP_ID_0, + mockSimpleAndroidPackage(), + isSystem = newPermissionOwnerIsSystem, + isInstantApp = newPermissionOwnerIsInstant + ) + addPackageState(newPermissionOwnerPackageState, newState) + with(appIdPermissionPolicy) { + onPackageAdded(newPermissionOwnerPackageState) + } + } + } + + @Test + fun testOnPackageAdded_permissionGroupChanged_getsRevoked() { + testPermissionChanged( + oldPermissionGroup = PERMISSION_GROUP_NAME_1, + newPermissionGroup = PERMISSION_GROUP_NAME_0 + ) + + val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = 0 + assertWithMessage( + "After onPackageAdded() is called for a package that has a permission group change" + + " for a permission it defines, the actual permission flags $actualFlags" + + " should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_protectionLevelChanged_getsRevoked() { + testPermissionChanged(newProtectionLevel = PermissionInfo.PROTECTION_INTERNAL) + + val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = 0 + assertWithMessage( + "After onPackageAdded() is called for a package that has a protection level change" + + " for a permission it defines, the actual permission flags $actualFlags" + + " should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + private fun testPermissionChanged( + oldPermissionGroup: String? = null, + newPermissionGroup: String? = null, + newProtectionLevel: Int = PermissionInfo.PROTECTION_DANGEROUS + ) { + val oldPermission = mockParsedPermission( + PERMISSION_NAME_0, + PACKAGE_NAME_0, + group = oldPermissionGroup, + protectionLevel = PermissionInfo.PROTECTION_DANGEROUS + ) + val oldPackageState = mockPackageState( + APP_ID_0, + mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(oldPermission)) + ) + addPackageState(oldPackageState) + addPermission(oldPermission) + setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED) + + mutateState { + val newPermission = mockParsedPermission( PERMISSION_NAME_0, - PermissionInfo.PROTECTION_DANGEROUS + PACKAGE_NAME_0, + group = newPermissionGroup, + protectionLevel = newProtectionLevel + ) + val newPackageState = mockPackageState( + APP_ID_0, + mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(newPermission)) ) + addPackageState(newPackageState, newState) + with(appIdPermissionPolicy) { + onPackageAdded(newPackageState) + } } + } + + @Test + fun testOnPackageAdded_permissionTreeNoLongerDeclared_getsDefinitionRemoved() { + testPermissionDeclaration {} assertWithMessage( - "After onPackageAdded() is called when $PERMISSION_NAME_0 already" + - " exists in system and is owned by a system app, the app $PACKAGE_NAME_0 shouldn't" + - " takeover ownership of this permission" + "After onPackageAdded() is called for a package that no longer defines a permission" + + " tree, the permission tree: $PERMISSION_NAME_0 in system state should be removed" ) - .that(newState.systemState.permissions[PERMISSION_NAME_0]?.packageName) - .isEqualTo(PACKAGE_NAME_1) + .that(getPermissionTree(PERMISSION_NAME_0)) + .isNull() + } + + @Test + fun testOnPackageAdded_permissionTreeByDisabledSystemPackage_remainsUnchanged() { + testPermissionDeclaration { + val disabledSystemPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage()) + addDisabledSystemPackageState(disabledSystemPackageState) + } + + assertWithMessage( + "After onPackageAdded() is called for a package that no longer defines" + + " a permission tree while this permission tree is still defined by" + + " a disabled system package, the permission tree: $PERMISSION_NAME_0 in" + + " system state should not be removed" + ) + .that(getPermissionTree(PERMISSION_TREE_NAME)) + .isNotNull() + } + + @Test + fun testOnPackageAdded_permissionNoLongerDeclared_getsDefinitionRemoved() { + testPermissionDeclaration {} + + assertWithMessage( + "After onPackageAdded() is called for a package that no longer defines a permission," + + " the permission: $PERMISSION_NAME_0 in system state should be removed" + ) + .that(getPermission(PERMISSION_NAME_0)) + .isNull() + } + + @Test + fun testOnPackageAdded_permissionByDisabledSystemPackage_remainsUnchanged() { + testPermissionDeclaration { + val disabledSystemPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage()) + addDisabledSystemPackageState(disabledSystemPackageState) + } + + assertWithMessage( + "After onPackageAdded() is called for a disabled system package and it's updated apk" + + " no longer defines a permission, the permission: $PERMISSION_NAME_0 in" + + " system state should not be removed" + ) + .that(getPermission(PERMISSION_NAME_0)) + .isNotNull() + } + + private fun testPermissionDeclaration(additionalSetup: () -> Unit) { + val oldPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage()) + addPackageState(oldPackageState) + addPermission(defaultPermissionTree) + addPermission(defaultPermission) + + additionalSetup() + + mutateState { + val newPackageState = mockPackageState(APP_ID_0, mockAndroidPackage(PACKAGE_NAME_0)) + addPackageState(newPackageState, newState) + with(appIdPermissionPolicy) { + onPackageAdded(newPackageState) + } + } + } + + @Test + fun testOnPackageAdded_permissionsNoLongerRequested_getsFlagsRevoked() { + val parsedPermission = mockParsedPermission( + PERMISSION_NAME_0, + PACKAGE_NAME_0, + protectionLevel = PermissionInfo.PROTECTION_DANGEROUS + ) + val oldPackageState = mockPackageState( + APP_ID_0, + mockAndroidPackage( + PACKAGE_NAME_0, + permissions = listOf(parsedPermission), + requestedPermissions = setOf(PERMISSION_NAME_0) + ) + ) + addPackageState(oldPackageState) + addPermission(parsedPermission) + setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.RUNTIME_GRANTED) + + mutateState { + val newPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage()) + addPackageState(newPackageState, newState) + with(appIdPermissionPolicy) { + onPackageAdded(newPackageState) + } + } + + val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = 0 + assertWithMessage( + "After onPackageAdded() is called for a package that no longer requests a permission" + + " the actual permission flags $actualFlags should match the" + + " expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_storageAndMediaPermissionsDowngradingPastQ_getsRuntimeRevoked() { + testRevokePermissionsOnPackageUpdate( + PermissionFlags.RUNTIME_GRANTED, + newTargetSdkVersion = Build.VERSION_CODES.P + ) + + val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE) + val expectedNewFlags = 0 + assertWithMessage( + "After onPackageAdded() is called for a package that's downgrading past Q" + + " the actual permission flags $actualFlags should match the" + + " expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_storageAndMediaPermissionsNotDowngradingPastQ_remainsUnchanged() { + val oldFlags = PermissionFlags.RUNTIME_GRANTED + testRevokePermissionsOnPackageUpdate( + oldFlags, + oldTargetSdkVersion = Build.VERSION_CODES.P, + newTargetSdkVersion = Build.VERSION_CODES.P + ) + + val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE) + val expectedNewFlags = oldFlags + assertWithMessage( + "After onPackageAdded() is called for a package that's not downgrading past Q" + + " the actual permission flags $actualFlags should match the" + + " expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) } - private inline fun testOnPackageAdded(mockBehaviorOverride: () -> Unit) { - mockBehaviorOverride() + @Test + fun testOnPackageAdded_policyFixedDowngradingPastQ_remainsUnchanged() { + val oldFlags = PermissionFlags.RUNTIME_GRANTED and PermissionFlags.POLICY_FIXED + testRevokePermissionsOnPackageUpdate(oldFlags, newTargetSdkVersion = Build.VERSION_CODES.P) + + val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE) + val expectedNewFlags = oldFlags + assertWithMessage( + "After onPackageAdded() is called for a package that's downgrading past Q" + + " the actual permission flags with PermissionFlags.POLICY_FIXED $actualFlags" + + " should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_newlyRequestingLegacyExternalStorage_getsRuntimeRevoked() { + testRevokePermissionsOnPackageUpdate( + PermissionFlags.RUNTIME_GRANTED, + oldTargetSdkVersion = Build.VERSION_CODES.P, + newTargetSdkVersion = Build.VERSION_CODES.P, + oldIsRequestLegacyExternalStorage = false + ) + + val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE) + val expectedNewFlags = 0 + assertWithMessage( + "After onPackageAdded() is called for a package with" + + " newlyRequestingLegacyExternalStorage, the actual permission flags $actualFlags" + + " should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_missingOldPackage_remainsUnchanged() { + val oldFlags = PermissionFlags.RUNTIME_GRANTED + testRevokePermissionsOnPackageUpdate( + oldFlags, + newTargetSdkVersion = Build.VERSION_CODES.P, + isOldPackageMissing = true + ) + + val actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE) + val expectedNewFlags = oldFlags + assertWithMessage( + "After onPackageAdded() is called for a package that's downgrading past Q" + + " and doesn't have the oldPackage, the actual permission flags $actualFlags" + + " should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + private fun testRevokePermissionsOnPackageUpdate( + oldFlags: Int, + oldTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + newTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + oldIsRequestLegacyExternalStorage: Boolean = true, + newIsRequestLegacyExternalStorage: Boolean = true, + isOldPackageMissing: Boolean = false + ) { + val parsedPermission = mockParsedPermission( + PERMISSION_READ_EXTERNAL_STORAGE, + PACKAGE_NAME_0, + protectionLevel = PermissionInfo.PROTECTION_DANGEROUS + ) + val oldPackageState = if (isOldPackageMissing) { + mockPackageState(APP_ID_0, PACKAGE_NAME_0) + } else { + mockPackageState( + APP_ID_0, + mockAndroidPackage( + PACKAGE_NAME_0, + targetSdkVersion = oldTargetSdkVersion, + isRequestLegacyExternalStorage = oldIsRequestLegacyExternalStorage, + requestedPermissions = setOf(PERMISSION_READ_EXTERNAL_STORAGE), + permissions = listOf(parsedPermission) + ) + ) + } + addPackageState(oldPackageState) + addPermission(parsedPermission) + setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_READ_EXTERNAL_STORAGE, oldFlags) mutateState { + val newPackageState = mockPackageState( + APP_ID_0, + mockAndroidPackage( + PACKAGE_NAME_0, + targetSdkVersion = newTargetSdkVersion, + isRequestLegacyExternalStorage = newIsRequestLegacyExternalStorage, + requestedPermissions = setOf(PERMISSION_READ_EXTERNAL_STORAGE), + permissions = listOf(parsedPermission) + ) + ) + addPackageState(newPackageState, newState) with(appIdPermissionPolicy) { - onPackageAdded(packageState0) + onPackageAdded(newPackageState) } } } + @Test + fun testOnPackageAdded_normalPermissionAlreadyGranted_remainsUnchanged() { + val oldFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.INSTALL_REVOKED + testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_NORMAL) {} + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = oldFlags + assertWithMessage( + "After onPackageAdded() is called for a package that requests a normal permission" + + " with an existing INSTALL_GRANTED flag, the actual permission flags $actualFlags" + + " should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_normalPermissionNotInstallRevoked_getsGranted() { + val oldFlags = 0 + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_NORMAL, + isNewInstall = true + ) {} + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.INSTALL_GRANTED + assertWithMessage( + "After onPackageAdded() is called for a package that requests a normal permission" + + " with no existing flags, the actual permission flags $actualFlags" + + " should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_normalPermissionRequestedByInstalledPackage_getsGranted() { + val oldFlags = PermissionFlags.INSTALL_REVOKED + testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_NORMAL) {} + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.INSTALL_GRANTED + assertWithMessage( + "After onPackageAdded() is called for a package that requests a normal permission" + + " with the INSTALL_REVOKED flag, the actual permission flags $actualFlags" + + " should match the expected flags $expectedNewFlags since it's a new install" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + /** + * We setup a permission protection level change from SIGNATURE to NORMAL in order to make + * the permission a "changed permission" in order to test evaluatePermissionState() called by + * evaluatePermissionStateForAllPackages(). This makes the requestingPackageState not the + * installedPackageState so that we can test whether requesting by system package will give us + * the expected permission flags. + * + * Besides, this also helps us test evaluatePermissionStateForAllPackages(). Since both + * evaluatePermissionStateForAllPackages() and evaluateAllPermissionStatesForPackage() call + * evaluatePermissionState() in their implementations, we use these tests as the only tests + * that test evaluatePermissionStateForAllPackages() + */ + @Test + fun testOnPackageAdded_normalPermissionRequestedBySystemPackage_getsGranted() { + testEvaluateNormalPermissionStateWithPermissionChanges(requestingPackageIsSystem = true) + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.INSTALL_GRANTED + assertWithMessage( + "After onPackageAdded() is called for a system package that requests a normal" + + " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" + + " should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_normalCompatibilityPermission_getsGranted() { + testEvaluateNormalPermissionStateWithPermissionChanges( + permissionName = PERMISSION_POST_NOTIFICATIONS, + requestingPackageTargetSdkVersion = Build.VERSION_CODES.S + ) + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_POST_NOTIFICATIONS) + val expectedNewFlags = PermissionFlags.INSTALL_GRANTED + assertWithMessage( + "After onPackageAdded() is called for a package that requests a normal compatibility" + + " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" + + " should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_normalPermissionPreviouslyRevoked_getsInstallRevoked() { + testEvaluateNormalPermissionStateWithPermissionChanges() + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.INSTALL_REVOKED + assertWithMessage( + "After onPackageAdded() is called for a package that requests a normal" + + " permission with INSTALL_REVOKED flag, the actual permission flags $actualFlags" + + " should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + private fun testEvaluateNormalPermissionStateWithPermissionChanges( + permissionName: String = PERMISSION_NAME_0, + requestingPackageTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + requestingPackageIsSystem: Boolean = false + ) { + val oldParsedPermission = mockParsedPermission( + permissionName, + PACKAGE_NAME_0, + protectionLevel = PermissionInfo.PROTECTION_SIGNATURE + ) + val oldPermissionOwnerPackageState = mockPackageState( + APP_ID_0, + mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(oldParsedPermission)) + ) + val requestingPackageState = mockPackageState( + APP_ID_1, + mockAndroidPackage( + PACKAGE_NAME_1, + requestedPermissions = setOf(permissionName), + targetSdkVersion = requestingPackageTargetSdkVersion + ), + isSystem = requestingPackageIsSystem, + ) + addPackageState(oldPermissionOwnerPackageState) + addPackageState(requestingPackageState) + addPermission(oldParsedPermission) + val oldFlags = PermissionFlags.INSTALL_REVOKED + setPermissionFlags(APP_ID_1, USER_ID_0, permissionName, oldFlags) + + mutateState { + val newPermissionOwnerPackageState = mockPackageState( + APP_ID_0, + mockAndroidPackage( + PACKAGE_NAME_0, + permissions = listOf(mockParsedPermission(permissionName, PACKAGE_NAME_0)) + ) + ) + addPackageState(newPermissionOwnerPackageState, newState) + with(appIdPermissionPolicy) { + onPackageAdded(newPermissionOwnerPackageState) + } + } + } + + @Test + fun testOnPackageAdded_normalAppOpPermission_getsRoleAndUserSetFlagsPreserved() { + val oldFlags = PermissionFlags.ROLE or PermissionFlags.USER_SET + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_NORMAL or PermissionInfo.PROTECTION_FLAG_APPOP + ) {} + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.INSTALL_GRANTED or oldFlags + assertWithMessage( + "After onPackageAdded() is called for a package that requests a normal app op" + + " permission with existing ROLE and USER_SET flags, the actual permission flags" + + " $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_internalPermissionWasGrantedWithMissingPackage_getsProtectionGranted() { + val oldFlags = PermissionFlags.PROTECTION_GRANTED + testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_INTERNAL) { + val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE) + addPackageState(packageStateWithMissingPackage) + } + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = oldFlags + assertWithMessage( + "After onPackageAdded() is called for a package that requests an internal permission" + + " with missing android package and $oldFlags flag, the actual permission flags" + + " $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_internalAppOpPermission_getsRoleAndUserSetFlagsPreserved() { + val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.ROLE or + PermissionFlags.USER_SET + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_APPOP + ) { + val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE) + addPackageState(packageStateWithMissingPackage) + } + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = oldFlags + assertWithMessage( + "After onPackageAdded() is called for a package that requests an internal permission" + + " with missing android package and $oldFlags flag and the permission isAppOp," + + " the actual permission flags $actualFlags should match the expected" + + " flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_internalDevelopmentPermission_getsRuntimeGrantedPreserved() { + val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_DEVELOPMENT + ) { + val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE) + addPackageState(packageStateWithMissingPackage) + } + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = oldFlags + assertWithMessage( + "After onPackageAdded() is called for a package that requests an internal permission" + + " with missing android package and $oldFlags flag and permission isDevelopment," + + " the actual permission flags $actualFlags should match the expected" + + " flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_internalRolePermission_getsRoleAndRuntimeGrantedPreserved() { + val oldFlags = PermissionFlags.PROTECTION_GRANTED or PermissionFlags.ROLE or + PermissionFlags.RUNTIME_GRANTED + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_INTERNAL or PermissionInfo.PROTECTION_FLAG_ROLE + ) { + val packageStateWithMissingPackage = mockPackageState(APP_ID_1, MISSING_ANDROID_PACKAGE) + addPackageState(packageStateWithMissingPackage) + } + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = oldFlags + assertWithMessage( + "After onPackageAdded() is called for a package that requests an internal permission" + + " with missing android package and $oldFlags flag and the permission isRole," + + " the actual permission flags $actualFlags should match the expected" + + " flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_signaturePrivilegedPermissionNotAllowlisted_isNotGranted() { + val oldFlags = 0 + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_SIGNATURE or PermissionInfo.PROTECTION_FLAG_PRIVILEGED, + isInstalledPackageSystem = true, + isInstalledPackagePrivileged = true, + isInstalledPackageProduct = true, + // To mock the return value of shouldGrantPrivilegedOrOemPermission() + isInstalledPackageVendor = true, + isNewInstall = true + ) { + val platformPackage = mockPackageState( + PLATFORM_APP_ID, + mockAndroidPackage(PLATFORM_PACKAGE_NAME) + ) + setupAllowlist(PACKAGE_NAME_1, false) + addPackageState(platformPackage) + } + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = oldFlags + assertWithMessage( + "After onPackageAdded() is called for a package that requests a signature privileged" + + " permission that's not allowlisted, the actual permission" + + " flags $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_nonPrivilegedPermissionShouldGrantBySignature_getsProtectionGranted() { + val oldFlags = 0 + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_SIGNATURE, + isInstalledPackageSystem = true, + isInstalledPackagePrivileged = true, + isInstalledPackageProduct = true, + isInstalledPackageSignatureMatching = true, + isInstalledPackageVendor = true, + isNewInstall = true + ) { + val platformPackage = mockPackageState( + PLATFORM_APP_ID, + mockAndroidPackage(PLATFORM_PACKAGE_NAME, isSignatureMatching = true) + ) + setupAllowlist(PACKAGE_NAME_1, false) + addPackageState(platformPackage) + } + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.PROTECTION_GRANTED + assertWithMessage( + "After onPackageAdded() is called for a package that requests a signature" + + " non-privileged permission, the actual permission" + + " flags $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_privilegedAllowlistPermissionShouldGrantByProtectionFlags_getsGranted() { + val oldFlags = 0 + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_SIGNATURE or PermissionInfo.PROTECTION_FLAG_PRIVILEGED, + isInstalledPackageSystem = true, + isInstalledPackagePrivileged = true, + isInstalledPackageProduct = true, + isNewInstall = true + ) { + val platformPackage = mockPackageState( + PLATFORM_APP_ID, + mockAndroidPackage(PLATFORM_PACKAGE_NAME) + ) + setupAllowlist(PACKAGE_NAME_1, true) + addPackageState(platformPackage) + } + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.PROTECTION_GRANTED + assertWithMessage( + "After onPackageAdded() is called for a package that requests a signature privileged" + + " permission that's allowlisted and should grant by protection flags, the actual" + + " permission flags $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + private fun setupAllowlist( + packageName: String, + allowlistState: Boolean, + state: MutableAccessState = oldState + ) { + state.mutateExternalState().setPrivilegedPermissionAllowlistPackages( + MutableIndexedListSet<String>().apply { add(packageName) } + ) + val mockAllowlist = mock<PermissionAllowlist> { + whenever( + getProductPrivilegedAppAllowlistState(packageName, PERMISSION_NAME_0) + ).thenReturn(allowlistState) + } + state.mutateExternalState().setPermissionAllowlist(mockAllowlist) + } + + @Test + fun testOnPackageAdded_nonRuntimeFlagsOnRuntimePermissions_getsCleared() { + val oldFlags = PermissionFlags.INSTALL_GRANTED or PermissionFlags.PREGRANT or + PermissionFlags.RUNTIME_GRANTED + testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {} + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.PREGRANT or PermissionFlags.RUNTIME_GRANTED + assertWithMessage( + "After onPackageAdded() is called for a package that requests a runtime permission" + + " with existing $oldFlags flags, the actual permission flags $actualFlags should" + + " match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_newPermissionsForPreM_requiresUserReview() { + val oldFlags = 0 + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_DANGEROUS, + installedPackageTargetSdkVersion = Build.VERSION_CODES.LOLLIPOP, + isNewInstall = true + ) {} + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT + assertWithMessage( + "After onPackageAdded() is called for a package that requests a runtime permission" + + " with no existing flags in pre M, actual permission flags $actualFlags should" + + " match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_legacyOrImplicitGrantedPermissionPreviouslyRevoked_getsAppOpRevoked() { + val oldFlags = PermissionFlags.USER_FIXED + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_DANGEROUS, + installedPackageTargetSdkVersion = Build.VERSION_CODES.LOLLIPOP + ) { + setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags) + } + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.USER_FIXED or + PermissionFlags.APP_OP_REVOKED + assertWithMessage( + "After onPackageAdded() is called for a package that requests a runtime permission" + + " that should be LEGACY_GRANTED or IMPLICIT_GRANTED that was previously revoked," + + " the actual permission flags $actualFlags should" + + " match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_legacyGrantedPermissionsForPostM_userReviewRequirementRemoved() { + val oldFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT + testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {} + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = 0 + assertWithMessage( + "After onPackageAdded() is called for a package that requests a runtime permission" + + " that used to require user review, the user review requirement should be removed" + + " if it's upgraded to post M. The actual permission flags $actualFlags should" + + " match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_legacyGrantedPermissionsAlreadyReviewedForPostM_getsGranted() { + val oldFlags = PermissionFlags.LEGACY_GRANTED + testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {} + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED + assertWithMessage( + "After onPackageAdded() is called for a package that requests a runtime permission" + + " that was already reviewed by the user, the permission should be RUNTIME_GRANTED" + + " if it's upgraded to post M. The actual permission flags $actualFlags should" + + " match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_leanbackNotificationPermissionsForPostM_getsImplicitGranted() { + val oldFlags = 0 + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_DANGEROUS, + permissionName = PERMISSION_POST_NOTIFICATIONS, + isNewInstall = true + ) { + oldState.mutateExternalState().setLeanback(true) + } + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_POST_NOTIFICATIONS) + val expectedNewFlags = PermissionFlags.IMPLICIT_GRANTED + assertWithMessage( + "After onPackageAdded() is called for a package that requests a runtime notification" + + " permission when isLeanback, the actual permission flags $actualFlags should" + + " match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_implicitSourceFromNonRuntime_getsImplicitGranted() { + val oldFlags = 0 + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_DANGEROUS, + implicitPermissions = setOf(PERMISSION_NAME_0), + isNewInstall = true + ) { + oldState.mutateExternalState().setImplicitToSourcePermissions( + MutableIndexedMap<String, IndexedListSet<String>>().apply { + put(PERMISSION_NAME_0, MutableIndexedListSet<String>().apply { + add(PERMISSION_NAME_1) + }) + } + ) + addPermission(mockParsedPermission(PERMISSION_NAME_1, PACKAGE_NAME_0)) + } + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT + assertWithMessage( + "After onPackageAdded() is called for a package that requests a runtime implicit" + + " permission that's source from a non-runtime permission, the actual permission" + + " flags $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + /** + * For a legacy granted or implicit permission during the app upgrade, when the permission + * should no longer be legacy or implicit granted, we want to remove the APP_OP_REVOKED flag + * so that the app can request the permission. + */ + @Test + fun testOnPackageAdded_noLongerLegacyOrImplicitGranted_canBeRequested() { + val oldFlags = PermissionFlags.LEGACY_GRANTED or PermissionFlags.APP_OP_REVOKED or + PermissionFlags.RUNTIME_GRANTED + testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {} + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = 0 + assertWithMessage( + "After onPackageAdded() is called for a package that requests a runtime permission" + + " that is no longer LEGACY_GRANTED or IMPLICIT_GRANTED, the actual permission" + + " flags $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_noLongerImplicitPermissions_getsRuntimeAndImplicitFlagsRemoved() { + val oldFlags = PermissionFlags.IMPLICIT or PermissionFlags.RUNTIME_GRANTED or + PermissionFlags.USER_SET or PermissionFlags.USER_FIXED + testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {} + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = 0 + assertWithMessage( + "After onPackageAdded() is called for a package that requests a runtime permission" + + " that is no longer implicit and we shouldn't retain as nearby device" + + " permissions, the actual permission flags $actualFlags should match the expected" + + " flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_noLongerImplicitNearbyPermissionsWasGranted_getsRuntimeGranted() { + val oldFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_DANGEROUS, + permissionName = PERMISSION_BLUETOOTH_CONNECT, + requestedPermissions = setOf( + PERMISSION_BLUETOOTH_CONNECT, + PERMISSION_ACCESS_BACKGROUND_LOCATION + ) + ) { + setPermissionFlags( + APP_ID_1, + USER_ID_0, + PERMISSION_ACCESS_BACKGROUND_LOCATION, + PermissionFlags.RUNTIME_GRANTED + ) + } + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_BLUETOOTH_CONNECT) + val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED + assertWithMessage( + "After onPackageAdded() is called for a package that requests a runtime nearby device" + + " permission that was granted by implicit, the actual permission flags" + + " $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_noLongerImplicitSystemOrPolicyFixedWasGranted_getsRuntimeGranted() { + val oldFlags = PermissionFlags.IMPLICIT_GRANTED or PermissionFlags.IMPLICIT or + PermissionFlags.SYSTEM_FIXED + testEvaluatePermissionState(oldFlags, PermissionInfo.PROTECTION_DANGEROUS) {} + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.RUNTIME_GRANTED or PermissionFlags.SYSTEM_FIXED + assertWithMessage( + "After onPackageAdded() is called for a package that requests a runtime permission" + + " that was granted and is no longer implicit and is SYSTEM_FIXED or POLICY_FIXED," + + " the actual permission flags $actualFlags should match the expected" + + " flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_restrictedPermissionsNotExempt_getsRestrictionFlags() { + val oldFlags = PermissionFlags.RESTRICTION_REVOKED + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_DANGEROUS, + permissionInfoFlags = PermissionInfo.FLAG_HARD_RESTRICTED + ) {} + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = oldFlags + assertWithMessage( + "After onPackageAdded() is called for a package that requests a runtime hard" + + " restricted permission that is not exempted, the actual permission flags" + + " $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageAdded_restrictedPermissionsIsExempted_clearsRestrictionFlags() { + val oldFlags = 0 + testEvaluatePermissionState( + oldFlags, + PermissionInfo.PROTECTION_DANGEROUS, + permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED + ) {} + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.UPGRADE_EXEMPT + assertWithMessage( + "After onPackageAdded() is called for a package that requests a runtime soft" + + " restricted permission that is exempted, the actual permission flags" + + " $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + /** + * Setup simple package states for testing evaluatePermissionState(). + * permissionOwnerPackageState is definer of permissionName with APP_ID_0. + * installedPackageState is the installed package that requests permissionName with APP_ID_1. + * + * @param oldFlags the existing permission flags for APP_ID_1, USER_ID_0, permissionName + * @param protectionLevel the protectionLevel for the permission + * @param permissionName the name of the permission (1) being defined (2) of the oldFlags, and + * (3) requested by installedPackageState + * @param requestedPermissions the permissions requested by installedPackageState + * @param implicitPermissions the implicit permissions of installedPackageState + * @param permissionInfoFlags the flags for the permission itself + * @param isInstalledPackageSystem whether installedPackageState is a system package + * + * @return installedPackageState + */ + fun testEvaluatePermissionState( + oldFlags: Int, + protectionLevel: Int, + permissionName: String = PERMISSION_NAME_0, + requestedPermissions: Set<String> = setOf(permissionName), + implicitPermissions: Set<String> = emptySet(), + permissionInfoFlags: Int = 0, + isInstalledPackageSystem: Boolean = false, + isInstalledPackagePrivileged: Boolean = false, + isInstalledPackageProduct: Boolean = false, + isInstalledPackageSignatureMatching: Boolean = false, + isInstalledPackageVendor: Boolean = false, + installedPackageTargetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + isNewInstall: Boolean = false, + additionalSetup: () -> Unit + ) { + val parsedPermission = mockParsedPermission( + permissionName, + PACKAGE_NAME_0, + protectionLevel = protectionLevel, + flags = permissionInfoFlags + ) + val permissionOwnerPackageState = mockPackageState( + APP_ID_0, + mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission)) + ) + val installedPackageState = mockPackageState( + APP_ID_1, + mockAndroidPackage( + PACKAGE_NAME_1, + requestedPermissions = requestedPermissions, + implicitPermissions = implicitPermissions, + targetSdkVersion = installedPackageTargetSdkVersion, + isSignatureMatching = isInstalledPackageSignatureMatching + ), + isSystem = isInstalledPackageSystem, + isPrivileged = isInstalledPackagePrivileged, + isProduct = isInstalledPackageProduct, + isVendor = isInstalledPackageVendor + ) + addPackageState(permissionOwnerPackageState) + if (!isNewInstall) { + addPackageState(installedPackageState) + setPermissionFlags(APP_ID_1, USER_ID_0, permissionName, oldFlags) + } + addPermission(parsedPermission) + + additionalSetup() + + mutateState { + if (isNewInstall) { + addPackageState(installedPackageState, newState) + setPermissionFlags(APP_ID_1, USER_ID_0, permissionName, oldFlags, newState) + } + with(appIdPermissionPolicy) { + onPackageAdded(installedPackageState) + } + } + } + + /** + * Mock an AndroidPackage with PACKAGE_NAME_0, PERMISSION_NAME_0 and PERMISSION_GROUP_NAME_0 + */ + private fun mockSimpleAndroidPackage(): AndroidPackage = + mockAndroidPackage( + PACKAGE_NAME_0, + permissionGroups = listOf(defaultPermissionGroup), + permissions = listOf(defaultPermissionTree, defaultPermission) + ) + private inline fun mutateState(action: MutateStateScope.() -> Unit) { newState = oldState.toMutable() MutateStateScope(oldState, newState).action() } - private fun createSystemStatePermission( + private fun mockPackageState( appId: Int, packageName: String, + isSystem: Boolean = false, + ): PackageState = + mock { + whenever(this.appId).thenReturn(appId) + whenever(this.packageName).thenReturn(packageName) + whenever(androidPackage).thenReturn(null) + whenever(this.isSystem).thenReturn(isSystem) + } + + private fun mockPackageState( + appId: Int, + androidPackage: AndroidPackage, + isSystem: Boolean = false, + isPrivileged: Boolean = false, + isProduct: Boolean = false, + isInstantApp: Boolean = false, + isVendor: Boolean = false + ): PackageState = + mock { + whenever(this.appId).thenReturn(appId) + whenever(this.androidPackage).thenReturn(androidPackage) + val packageName = androidPackage.packageName + whenever(this.packageName).thenReturn(packageName) + whenever(this.isSystem).thenReturn(isSystem) + whenever(this.isPrivileged).thenReturn(isPrivileged) + whenever(this.isProduct).thenReturn(isProduct) + whenever(this.isVendor).thenReturn(isVendor) + val userStates = SparseArray<PackageUserState>().apply { + put(USER_ID_0, mock { whenever(this.isInstantApp).thenReturn(isInstantApp) }) + } + whenever(this.userStates).thenReturn(userStates) + } + + private fun mockAndroidPackage( + packageName: String, + targetSdkVersion: Int = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + isRequestLegacyExternalStorage: Boolean = false, + adoptPermissions: List<String> = emptyList(), + implicitPermissions: Set<String> = emptySet(), + requestedPermissions: Set<String> = emptySet(), + permissionGroups: List<ParsedPermissionGroup> = emptyList(), + permissions: List<ParsedPermission> = emptyList(), + isSignatureMatching: Boolean = false + ): AndroidPackage = + mock { + whenever(this.packageName).thenReturn(packageName) + whenever(this.targetSdkVersion).thenReturn(targetSdkVersion) + whenever(this.isRequestLegacyExternalStorage).thenReturn(isRequestLegacyExternalStorage) + whenever(this.adoptPermissions).thenReturn(adoptPermissions) + whenever(this.implicitPermissions).thenReturn(implicitPermissions) + whenever(this.requestedPermissions).thenReturn(requestedPermissions) + whenever(this.permissionGroups).thenReturn(permissionGroups) + whenever(this.permissions).thenReturn(permissions) + val signingDetails = mock<SigningDetails> { + whenever( + hasCommonSignerWithCapability(any(), any()) + ).thenReturn(isSignatureMatching) + whenever(hasAncestorOrSelf(any())).thenReturn(isSignatureMatching) + whenever( + checkCapability(any<SigningDetails>(), any()) + ).thenReturn(isSignatureMatching) + } + whenever(this.signingDetails).thenReturn(signingDetails) + } + + private fun mockParsedPermission( permissionName: String, - protectionLevel: Int, + packageName: String, + backgroundPermission: String? = null, + group: String? = null, + protectionLevel: Int = PermissionInfo.PROTECTION_NORMAL, + flags: Int = 0, + isTree: Boolean = false + ): ParsedPermission = + mock { + whenever(name).thenReturn(permissionName) + whenever(this.packageName).thenReturn(packageName) + whenever(metaData).thenReturn(Bundle()) + whenever(this.backgroundPermission).thenReturn(backgroundPermission) + whenever(this.group).thenReturn(group) + whenever(this.protectionLevel).thenReturn(protectionLevel) + whenever(this.flags).thenReturn(flags) + whenever(this.isTree).thenReturn(isTree) + } + + private fun mockParsedPermissionGroup( + permissionGroupName: String, + packageName: String, + ): ParsedPermissionGroup = + mock { + whenever(name).thenReturn(permissionGroupName) + whenever(this.packageName).thenReturn(packageName) + whenever(metaData).thenReturn(Bundle()) + } + + private fun addPackageState(packageState: PackageState, state: MutableAccessState = oldState) { + state.mutateExternalState().apply { + setPackageStates( + packageStates.toMutableMap().apply { + put(packageState.packageName, packageState) + } + ) + mutateAppIdPackageNames().mutateOrPut(packageState.appId) { MutableIndexedListSet() } + .add(packageState.packageName) + } + } + + private fun addDisabledSystemPackageState( + packageState: PackageState, + state: MutableAccessState = oldState + ) = state.mutateExternalState().apply { + (disabledSystemPackageStates as ArrayMap)[packageState.packageName] = packageState + } + + private fun addPermission( + parsedPermission: ParsedPermission, type: Int = Permission.TYPE_MANIFEST, isReconciled: Boolean = true, - isTree: Boolean = false + state: MutableAccessState = oldState ) { - @Suppress("DEPRECATION") - val permissionInfo = PermissionInfo().apply { - name = permissionName - this.packageName = packageName - this.protectionLevel = protectionLevel - } + val permissionInfo = PackageInfoUtils.generatePermissionInfo( + parsedPermission, + PackageManager.GET_META_DATA.toLong() + )!! + val appId = state.externalState.packageStates[permissionInfo.packageName]!!.appId val permission = Permission(permissionInfo, isReconciled, type, appId) - if (isTree) { - oldState.mutateSystemState().mutatePermissionTrees().put(permissionName, permission) + if (parsedPermission.isTree) { + state.mutateSystemState().mutatePermissionTrees()[permission.name] = permission } else { - oldState.mutateSystemState().mutatePermissions().put(permissionName, permission) + state.mutateSystemState().mutatePermissions()[permission.name] = permission } } - private fun createSystemStatePermissionGroup(packageName: String, permissionGroupName: String) { - @Suppress("DEPRECATION") - val permissionGroupInfo = PermissionGroupInfo().apply { - name = permissionGroupName - this.packageName = packageName - } - oldState.mutateSystemState().mutatePermissionGroups()[permissionGroupName] = - permissionGroupInfo + private fun addPermissionGroup( + parsedPermissionGroup: ParsedPermissionGroup, + state: MutableAccessState = oldState + ) { + state.mutateSystemState().mutatePermissionGroups()[parsedPermissionGroup.name] = + PackageInfoUtils.generatePermissionGroupInfo( + parsedPermissionGroup, + PackageManager.GET_META_DATA.toLong() + )!! } - fun getPermissionFlags( + private fun getPermission( + permissionName: String, + state: MutableAccessState = newState + ): Permission? = state.systemState.permissions[permissionName] + + private fun getPermissionTree( + permissionTreeName: String, + state: MutableAccessState = newState + ): Permission? = state.systemState.permissionTrees[permissionTreeName] + + private fun getPermissionGroup( + permissionGroupName: String, + state: MutableAccessState = newState + ): PermissionGroupInfo? = state.systemState.permissionGroups[permissionGroupName] + + private fun getPermissionFlags( appId: Int, userId: Int, permissionName: String, @@ -517,20 +1706,43 @@ class AppIdPermissionPolicyTest { ): Int = state.userStates[userId]?.appIdPermissionFlags?.get(appId).getWithDefault(permissionName, 0) + private fun setPermissionFlags( + appId: Int, + userId: Int, + permissionName: String, + flags: Int, + state: MutableAccessState = oldState + ) = + state.mutateUserState(userId)!!.mutateAppIdPermissionFlags().mutateOrPut(appId) { + MutableIndexedMap() + }.put(permissionName, flags) + companion object { private const val PACKAGE_NAME_0 = "packageName0" private const val PACKAGE_NAME_1 = "packageName1" + private const val MISSING_ANDROID_PACKAGE = "missingAndroidPackage" + private const val PLATFORM_PACKAGE_NAME = "android" private const val APP_ID_0 = 0 private const val APP_ID_1 = 1 - - private const val PERMISSION_NAME_0 = "permissionName0" - private const val PERMISSION_NAME_1 = "permissionName1" + private const val PLATFORM_APP_ID = 2 private const val PERMISSION_GROUP_NAME_0 = "permissionGroupName0" private const val PERMISSION_GROUP_NAME_1 = "permissionGroupName1" + private const val PERMISSION_TREE_NAME = "permissionTree" + + private const val PERMISSION_NAME_0 = "permissionName0" + private const val PERMISSION_NAME_1 = "permissionName1" + private const val PERMISSION_READ_EXTERNAL_STORAGE = + Manifest.permission.READ_EXTERNAL_STORAGE + private const val PERMISSION_POST_NOTIFICATIONS = + Manifest.permission.POST_NOTIFICATIONS + private const val PERMISSION_BLUETOOTH_CONNECT = + Manifest.permission.BLUETOOTH_CONNECT + private const val PERMISSION_ACCESS_BACKGROUND_LOCATION = + Manifest.permission.ACCESS_BACKGROUND_LOCATION + private const val USER_ID_0 = 0 - private const val USER_ID_1 = 1 } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index 82d00a6fcbca..7374901763db 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -44,6 +44,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.server.display.config.HdrBrightnessData; import com.android.server.display.config.ThermalStatus; import org.junit.Before; @@ -477,6 +478,23 @@ public final class DisplayDeviceConfigTest { mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA); } + @Test + public void testHdrBrightnessDataFromDisplayConfig() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(); + + HdrBrightnessData data = mDisplayDeviceConfig.getHdrBrightnessData(); + + assertNotNull(data); + assertEquals(2, data.mMaxBrightnessLimits.size()); + assertEquals(13000, data.mBrightnessDecreaseDebounceMillis); + assertEquals(10000, data.mBrightnessDecreaseDurationMillis); + assertEquals(1000, data.mBrightnessIncreaseDebounceMillis); + assertEquals(11000, data.mBrightnessIncreaseDurationMillis); + + assertEquals(0.3f, data.mMaxBrightnessLimits.get(500f), SMALL_DELTA); + assertEquals(0.6f, data.mMaxBrightnessLimits.get(1200f), SMALL_DELTA); + } + private void verifyConfigValuesFromConfigResource() { assertNull(mDisplayDeviceConfig.getName()); assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new @@ -694,6 +712,25 @@ public final class DisplayDeviceConfigTest { + "</proxSensor>\n"; } + private String getHdrBrightnessConfig() { + return "<hdrBrightnessConfig>\n" + + " <brightnessMap>\n" + + " <point>\n" + + " <first>500</first>\n" + + " <second>0.3</second>\n" + + " </point>\n" + + " <point>\n" + + " <first>1200</first>\n" + + " <second>0.6</second>\n" + + " </point>\n" + + " </brightnessMap>\n" + + " <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>\n" + + " <brightnessIncreaseDurationMillis>11000</brightnessIncreaseDurationMillis>\n" + + " <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>\n" + + " <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>\n" + + "</hdrBrightnessConfig>"; + } + private String getContent() { return getContent(getValidLuxThrottling(), getValidProxSensor()); } @@ -784,6 +821,7 @@ public final class DisplayDeviceConfigTest { + "</point>\n" + "</sdrHdrRatioMap>\n" + "</highBrightnessMode>\n" + + getHdrBrightnessConfig() + brightnessCapConfig + "<lightSensor>\n" + "<type>test_light_sensor</type>\n" diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java index e7dc48e529eb..9722426667db 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java @@ -43,6 +43,7 @@ import android.content.res.Resources; import android.hardware.Sensor; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.os.Handler; @@ -67,7 +68,9 @@ import com.android.server.LocalServices; import com.android.server.am.BatteryStatsService; import com.android.server.display.RampAnimator.DualRampAnimator; import com.android.server.display.brightness.BrightnessEvent; +import com.android.server.display.brightness.clamper.HdrClamper; import com.android.server.display.color.ColorDisplayService; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.layout.Layout; import com.android.server.display.whitebalance.DisplayWhiteBalanceController; import com.android.server.policy.WindowManagerPolicy; @@ -1175,6 +1178,26 @@ public final class DisplayPowerController2Test { verify(mHolder.displayPowerState, times(1)).stop(); } + @Test + public void testRampRateForHdrContent() { + float clampedBrightness = 0.6f; + float transitionRate = 35.5f; + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + when(mHolder.hbmController.getHighBrightnessMode()).thenReturn( + BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR); + when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(PowerManager.BRIGHTNESS_MAX); + when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(clampedBrightness); + when(mHolder.hdrClamper.getTransitionRate()).thenReturn(transitionRate); + + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + verify(mHolder.animator, atLeastOnce()).animateTo(eq(clampedBrightness), anyFloat(), + eq(transitionRate)); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ @@ -1270,12 +1293,15 @@ public final class DisplayPowerController2Test { final ScreenOffBrightnessSensorController screenOffBrightnessSensorController = mock(ScreenOffBrightnessSensorController.class); final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class); + final HdrClamper hdrClamper = mock(HdrClamper.class); + final DisplayManagerFlags flags = mock(DisplayManagerFlags.class); when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX); TestInjector injector = spy(new TestInjector(displayPowerState, animator, automaticBrightnessController, wakelockController, brightnessMappingStrategy, - hysteresisLevels, screenOffBrightnessSensorController, hbmController)); + hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper, + flags)); final LogicalDisplay display = mock(LogicalDisplay.class); final DisplayDevice device = mock(DisplayDevice.class); @@ -1289,11 +1315,11 @@ public final class DisplayPowerController2Test { mContext, injector, mDisplayPowerCallbacksMock, mHandler, mSensorManagerMock, mDisplayBlankerMock, display, mBrightnessTrackerMock, brightnessSetting, () -> {}, - hbmMetadata, /* bootCompleted= */ false); + hbmMetadata, /* bootCompleted= */ false, flags); return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting, animator, automaticBrightnessController, wakelockController, - screenOffBrightnessSensorController, hbmController, hbmMetadata, + screenOffBrightnessSensorController, hbmController, hdrClamper, hbmMetadata, brightnessMappingStrategy, injector); } @@ -1311,6 +1337,8 @@ public final class DisplayPowerController2Test { public final WakelockController wakelockController; public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController; public final HighBrightnessModeController hbmController; + + public final HdrClamper hdrClamper; public final HighBrightnessModeMetadata hbmMetadata; public final BrightnessMappingStrategy brightnessMappingStrategy; public final DisplayPowerController2.Injector injector; @@ -1322,6 +1350,7 @@ public final class DisplayPowerController2Test { WakelockController wakelockController, ScreenOffBrightnessSensorController screenOffBrightnessSensorController, HighBrightnessModeController hbmController, + HdrClamper hdrClamper, HighBrightnessModeMetadata hbmMetadata, BrightnessMappingStrategy brightnessMappingStrategy, DisplayPowerController2.Injector injector) { @@ -1334,6 +1363,7 @@ public final class DisplayPowerController2Test { this.wakelockController = wakelockController; this.screenOffBrightnessSensorController = screenOffBrightnessSensorController; this.hbmController = hbmController; + this.hdrClamper = hdrClamper; this.hbmMetadata = hbmMetadata; this.brightnessMappingStrategy = brightnessMappingStrategy; this.injector = injector; @@ -1350,13 +1380,19 @@ public final class DisplayPowerController2Test { private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController; private final HighBrightnessModeController mHighBrightnessModeController; + private final HdrClamper mHdrClamper; + + private final DisplayManagerFlags mFlags; + TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator, AutomaticBrightnessController automaticBrightnessController, WakelockController wakelockController, BrightnessMappingStrategy brightnessMappingStrategy, HysteresisLevels hysteresisLevels, ScreenOffBrightnessSensorController screenOffBrightnessSensorController, - HighBrightnessModeController highBrightnessModeController) { + HighBrightnessModeController highBrightnessModeController, + HdrClamper hdrClamper, + DisplayManagerFlags flags) { mDisplayPowerState = dps; mAnimator = animator; mAutomaticBrightnessController = automaticBrightnessController; @@ -1365,6 +1401,8 @@ public final class DisplayPowerController2Test { mHysteresisLevels = hysteresisLevels; mScreenOffBrightnessSensorController = screenOffBrightnessSensorController; mHighBrightnessModeController = highBrightnessModeController; + mHdrClamper = hdrClamper; + mFlags = flags; } @Override @@ -1471,6 +1509,15 @@ public final class DisplayPowerController2Test { } @Override + BrightnessRangeController getBrightnessRangeController( + HighBrightnessModeController hbmController, Runnable modeChangeCallback, + DisplayDeviceConfig displayDeviceConfig, Handler handler, + DisplayManagerFlags flags) { + return new BrightnessRangeController(hbmController, modeChangeCallback, + displayDeviceConfig, mHdrClamper, mFlags); + } + + @Override DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler, SensorManager sensorManager, Resources resources) { return mDisplayWhiteBalanceControllerMock; diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 2640390ceecf..4a6665acf92b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -69,6 +69,7 @@ import com.android.server.am.BatteryStatsService; import com.android.server.display.RampAnimator.DualRampAnimator; import com.android.server.display.brightness.BrightnessEvent; import com.android.server.display.color.ColorDisplayService; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.layout.Layout; import com.android.server.display.whitebalance.DisplayWhiteBalanceController; import com.android.server.policy.WindowManagerPolicy; @@ -758,14 +759,16 @@ public final class DisplayPowerControllerTest { @Test public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); DisplayPowerRequest dpr = new DisplayPowerRequest(); dpr.policy = DisplayPowerRequest.POLICY_DOZE; - mContext.getOrCreateTestableResources().addOverride( - com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true); mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); advanceTime(1); // Run updatePowerState @@ -1280,6 +1283,7 @@ public final class DisplayPowerControllerTest { final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class); final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class); final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class); + final DisplayManagerFlags flags = mock(DisplayManagerFlags.class); setUpDisplay(displayId, uniqueId, display, device, config, isEnabled); @@ -1287,7 +1291,7 @@ public final class DisplayPowerControllerTest { mContext, injector, mDisplayPowerCallbacksMock, mHandler, mSensorManagerMock, mDisplayBlankerMock, display, mBrightnessTrackerMock, brightnessSetting, () -> {}, - hbmMetadata, /* bootCompleted= */ false); + hbmMetadata, /* bootCompleted= */ false, flags); return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting, animator, automaticBrightnessController, screenOffBrightnessSensorController, diff --git a/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java b/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java new file mode 100644 index 000000000000..2820da7c49c1 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import static org.junit.Assert.assertEquals; + +import android.util.FloatProperty; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; + +@SmallTest +public class RampAnimatorTest { + + private RampAnimator<TestObject> mRampAnimator; + + private final TestObject mTestObject = new TestObject(); + + private final FloatProperty<TestObject> mTestProperty = new FloatProperty<>("mValue") { + @Override + public void setValue(TestObject object, float value) { + object.mValue = value; + } + + @Override + public Float get(TestObject object) { + return object.mValue; + } + }; + + @Before + public void setUp() { + mRampAnimator = new RampAnimator<>(mTestObject, mTestProperty); + } + + @Test + public void testInitialValueUsedInLastAnimationStep() { + mRampAnimator.setAnimationTarget(0.67f, 0.1f); + + assertEquals(0.67f, mTestObject.mValue, 0); + } + + private static class TestObject { + private float mValue; + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java new file mode 100644 index 000000000000..0ebe46ac0c88 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.clamper; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import android.os.PowerManager; + +import androidx.test.filters.SmallTest; + +import com.android.server.testutils.OffsettableClock; +import com.android.server.testutils.TestHandler; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@SmallTest +public class HdrClamperTest { + + public static final float FLOAT_TOLERANCE = 0.0001f; + + @Rule + public MockitoRule mRule = MockitoJUnit.rule(); + + @Mock + private BrightnessClamperController.ClamperChangeListener mMockListener; + + OffsettableClock mClock = new OffsettableClock.Stopped(); + + private final TestHandler mTestHandler = new TestHandler(null, mClock); + + + private HdrClamper mHdrClamper; + + + @Before + public void setUp() { + mHdrClamper = new HdrClamper(mMockListener, mTestHandler); + configureClamper(); + } + + @Test + public void testClamper_AmbientLuxChangesAboveLimit() { + mHdrClamper.onAmbientLuxChange(500); + + assertFalse(mTestHandler.hasMessagesOrCallbacks()); + assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + } + + @Test + public void testClamper_AmbientLuxChangesBelowLimit() { + mHdrClamper.onAmbientLuxChange(499); + + assertTrue(mTestHandler.hasMessagesOrCallbacks()); + TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek(); + assertEquals(2000, msgInfo.sendTime); + assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + + mClock.fastForward(2000); + mTestHandler.timeAdvance(); + assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + } + + @Test + public void testClamper_AmbientLuxChangesBelowLimit_ThenFastAboveLimit() { + mHdrClamper.onAmbientLuxChange(499); + mHdrClamper.onAmbientLuxChange(500); + + assertFalse(mTestHandler.hasMessagesOrCallbacks()); + assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + } + + @Test + public void testClamper_AmbientLuxChangesBelowLimit_ThenSlowlyAboveLimit() { + mHdrClamper.onAmbientLuxChange(499); + mClock.fastForward(2000); + mTestHandler.timeAdvance(); + + mHdrClamper.onAmbientLuxChange(500); + + assertTrue(mTestHandler.hasMessagesOrCallbacks()); + TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek(); + assertEquals(3000, msgInfo.sendTime); // 2000 + 1000 + + mClock.fastForward(1000); + mTestHandler.timeAdvance(); + assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + } + + private void configureClamper() { + mHdrClamper.getConfiguration().mMaxBrightnessLimits.put(500f, 0.6f); + mHdrClamper.getConfiguration().mIncreaseConfig.mDebounceTimeMillis = 1000; + mHdrClamper.getConfiguration().mIncreaseConfig.mTransitionTimeMillis = 1500; + mHdrClamper.getConfiguration().mDecreaseConfig.mDebounceTimeMillis = 2000; + mHdrClamper.getConfiguration().mDecreaseConfig.mTransitionTimeMillis = 2500; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index d988063bdfb0..614fd11e0517 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -18,7 +18,9 @@ package com.android.server.pm; import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import static android.content.pm.PackageManager.DELETE_KEEP_DATA; + import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -157,11 +159,12 @@ public class PackageArchiverTest { when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.getResourcesForApplication(eq(PACKAGE))).thenReturn( mock(Resources.class)); - when(mIcon.compress(eq(Bitmap.CompressFormat.PNG), eq(100), any())).thenReturn(true); mArchiveManager = spy(new PackageArchiver(mContext, pm)); doReturn(ICON_PATH).when(mArchiveManager).storeIcon(eq(PACKAGE), - any(LauncherActivityInfo.class), eq(mUserId)); + any(LauncherActivityInfo.class), eq(mUserId), anyInt()); + doReturn(mIcon).when(mArchiveManager).decodeIcon( + any(ArchiveState.ArchiveActivityInfo.class)); } @Test @@ -249,7 +252,7 @@ public class PackageArchiverTest { public void archiveApp_storeIconFails() throws IntentSender.SendIntentException, IOException { IOException e = new IOException("IO"); doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE), - any(LauncherActivityInfo.class), eq(mUserId)); + any(LauncherActivityInfo.class), eq(mUserId), anyInt()); mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT); rule.mocks().getHandler().flush(); @@ -372,6 +375,38 @@ public class PackageArchiverTest { assertThat(intent.getPackage()).isEqualTo(INSTALLER_PACKAGE); } + @Test + public void getArchivedAppIcon_packageNotInstalled() { + when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn( + null); + + Exception e = assertThrows( + ParcelableException.class, + () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + String.format("Package %s not found.", PACKAGE)); + } + + @Test + public void getArchivedAppIcon_notArchived() { + Exception e = assertThrows( + ParcelableException.class, + () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)); + assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); + assertThat(e.getCause()).hasMessageThat().isEqualTo( + String.format("Package %s is not currently archived.", PACKAGE)); + } + + @Test + public void getArchivedAppIcon_success() { + mUserState.setArchiveState(createArchiveState()).setInstalled(false); + + assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isEqualTo( + mIcon); + } + + private static ArchiveState createArchiveState() { List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>(); for (LauncherActivityInfo mainActivity : createLauncherActivities()) { diff --git a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java new file mode 100644 index 000000000000..9fdbdda38c75 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.platform.test.annotations.Presubmit; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; + +import android.util.Log; + +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Build/Install/Run: + * atest FrameworksServicesTests:AnrTimerTest + */ +@SmallTest +@Presubmit +public class AnrTimerTest { + + /** + * A handler that allows control over when to dispatch messages and callbacks. Because most + * Handler methods are final, the only thing this handler can intercept is sending messages. + * This handler allows unit tests to be written without a need to sleep (which leads to flaky + * tests). + * + * This code was cloned from {@link com.android.systemui.utils.os.FakeHandler}. + */ + static class TestHandler extends Handler { + + private boolean mImmediate = true; + private ArrayList<Message> mQueuedMessages = new ArrayList<>(); + + ArrayList<Long> mDelays = new ArrayList<>(); + + TestHandler(Looper looper, Callback callback, boolean immediate) { + super(looper, callback); + mImmediate = immediate; + } + + TestHandler(Looper looper, Callback callback) { + this(looper, callback, true); + } + + /** + * Override sendMessageAtTime. In immediate mode, the message is immediately dispatched. + * In non-immediate mode, the message is enqueued to the real handler. In both cases, the + * original delay is computed by comparing the target dispatch time with 'now'. This + * computation is prone to errors if the code experiences delays. The computed time is + * captured in the mDelays list. + */ + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + long delay = uptimeMillis - SystemClock.uptimeMillis(); + mDelays.add(delay); + if (mImmediate) { + mQueuedMessages.add(msg); + dispatchQueuedMessages(); + } else { + super.sendMessageAtTime(msg, uptimeMillis); + } + return true; + } + + void setImmediate(boolean immediate) { + mImmediate = immediate; + } + + /** Dispatch any messages that have been queued on the calling thread. */ + void dispatchQueuedMessages() { + ArrayList<Message> messages = new ArrayList<>(mQueuedMessages); + mQueuedMessages.clear(); + for (Message msg : messages) { + dispatchMessage(msg); + } + } + + /** + * Compare the captured delays with the input array. The comparison is fuzzy because the + * captured delay (see sendMessageAtTime) is affected by process delays. + */ + void verifyDelays(long[] r) { + final long FUZZ = 10; + assertEquals(r.length, mDelays.size()); + for (int i = 0; i < mDelays.size(); i++) { + long t = r[i]; + long v = mDelays.get(i); + assertTrue(v >= t - FUZZ && v <= t + FUZZ); + } + } + } + + private Handler mHandler; + private CountDownLatch mLatch = null; + private ArrayList<Message> mMessages; + + // The commonly used message timeout key. + private static final int MSG_TIMEOUT = 1; + + @Before + public void setUp() { + mHandler = new Handler(Looper.getMainLooper(), this::expirationHandler); + mMessages = new ArrayList<>(); + mLatch = new CountDownLatch(1); + AnrTimer.resetTimerListForHermeticTest(); + } + + @After + public void tearDown() { + mHandler = null; + mMessages = null; + } + + // When a timer expires, set the expiration time in the message and add it to the queue. + private boolean expirationHandler(Message msg) { + mMessages.add(Message.obtain(msg)); + mLatch.countDown(); + return false; + } + + // The test argument includes a pid and uid, and a tag. The tag is used to distinguish + // different message instances. + private static class TestArg { + final int pid; + final int uid; + final int tag; + + TestArg(int pid, int uid, int tag) { + this.pid = pid; + this.uid = uid; + this.tag = tag; + } + @Override + public String toString() { + return String.format("pid=%d uid=%d tag=%d", pid, uid, tag); + } + } + + /** + * An instrumented AnrTimer. + */ + private class TestAnrTimer extends AnrTimer { + // A local copy of 'what'. The field in AnrTimer is private. + final int mWhat; + + TestAnrTimer(Handler h, int key, String tag) { + super(h, key, tag); + mWhat = key; + } + + TestAnrTimer() { + this(mHandler, MSG_TIMEOUT, caller()); + } + + TestAnrTimer(Handler h, int key, String tag, boolean extend, TestInjector injector) { + super(h, key, tag, extend, injector); + mWhat = key; + } + + TestAnrTimer(boolean extend, TestInjector injector) { + this(mHandler, MSG_TIMEOUT, caller(), extend, injector); + } + + // Return the name of method that called the constructor, assuming that this function is + // called from inside the constructor. The calling method is used to name the AnrTimer + // instance so that logs are easier to understand. + private static String caller() { + final int n = 4; + StackTraceElement[] stack = Thread.currentThread().getStackTrace(); + if (stack.length < n+1) return "test"; + return stack[n].getMethodName(); + } + + boolean start(TestArg arg, long millis) { + return start(arg, arg.pid, arg.uid, millis); + } + + int what() { + return mWhat; + } + } + + private static class TestTracker extends AnrTimer.CpuTracker { + long index = 0; + final int skip; + TestTracker(int skip) { + this.skip = skip; + } + long delay(int pid) { + return index++ * skip; + } + } + + private class TestInjector extends AnrTimer.Injector { + final boolean mImmediate; + final AnrTimer.CpuTracker mTracker; + TestHandler mTestHandler; + + TestInjector(int skip, boolean immediate) { + mTracker = new TestTracker(skip); + mImmediate = immediate; + } + + TestInjector(int skip) { + this(skip, true); + } + + @Override + Handler getHandler(Handler.Callback callback) { + if (mTestHandler == null) { + mTestHandler = new TestHandler(mHandler.getLooper(), callback, mImmediate); + } + return mTestHandler; + } + + /** Fetch the allocated handle. This does not check for nulls. */ + TestHandler getHandler() { + return mTestHandler; + } + + AnrTimer.CpuTracker getTracker() { + return mTracker; + } + } + + // Tests + // 1. Start a timer and wait for expiration. + // 2. Start a timer and cancel it. Verify no expiration. + // 3. Start a timer. Shortly thereafter, restart it. Verify only one expiration. + // 4. Start a couple of timers. Verify max active timers. Discard one and verify the active + // count drops by 1. Accept one and verify the active count drops by 1. + + + @Test + public void testSimpleTimeout() throws Exception { + // Create an immediate TestHandler. + TestInjector injector = new TestInjector(0); + TestAnrTimer timer = new TestAnrTimer(false, injector); + TestArg t = new TestArg(1, 1, 3); + assertTrue(timer.start(t, 10)); + // Delivery is immediate but occurs on a different thread. + assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS)); + assertEquals(1, mMessages.size()); + Message m = mMessages.get(0); + assertEquals(timer.what(), m.what); + assertEquals(t, m.obj); + + // Verify that the timer is still present. + assertEquals(1, AnrTimer.sizeOfTimerList()); + assertTrue(timer.accept(t)); + assertEquals(0, AnrTimer.sizeOfTimerList()); + + // Verify that the timer no longer exists. + assertFalse(timer.accept(t)); + } + + @Test + public void testCancel() throws Exception { + // Create an non-immediate TestHandler. + TestInjector injector = new TestInjector(0, false); + TestAnrTimer timer = new TestAnrTimer(false, injector); + + Handler handler = injector.getHandler(); + assertNotNull(handler); + assertTrue(handler instanceof TestHandler); + + // The tests that follow check for a 'what' of 0 (zero), which is the message key used + // by AnrTimer internally. + TestArg t = new TestArg(1, 1, 3); + assertFalse(handler.hasMessages(0)); + assertTrue(timer.start(t, 100)); + assertTrue(handler.hasMessages(0)); + assertTrue(timer.cancel(t)); + assertFalse(handler.hasMessages(0)); + + // Verify that no expiration messages were delivered. + assertEquals(0, mMessages.size()); + assertEquals(0, AnrTimer.sizeOfTimerList()); + } + + @Test + public void testRestart() throws Exception { + // Create an non-immediate TestHandler. + TestInjector injector = new TestInjector(0, false); + TestAnrTimer timer = new TestAnrTimer(false, injector); + + TestArg t = new TestArg(1, 1, 3); + assertTrue(timer.start(t, 2500)); + assertTrue(timer.start(t, 1000)); + + // Verify that the test handler saw two timeouts. + injector.getHandler().verifyDelays(new long[] { 2500, 1000 }); + + // Verify that there is a single timer. Then cancel it. + assertEquals(1, AnrTimer.sizeOfTimerList()); + assertTrue(timer.cancel(t)); + assertEquals(0, AnrTimer.sizeOfTimerList()); + } + + @Test + public void testExtendNormal() throws Exception { + // Create an immediate TestHandler. + TestInjector injector = new TestInjector(5); + TestAnrTimer timer = new TestAnrTimer(true, injector); + TestArg t = new TestArg(1, 1, 3); + assertTrue(timer.start(t, 10)); + + assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS)); + assertEquals(1, mMessages.size()); + Message m = mMessages.get(0); + assertEquals(timer.what(), m.what); + assertEquals(t, m.obj); + + // Verify that the test handler saw two timeouts: one of 10ms and one of 5ms. + injector.getHandler().verifyDelays(new long[] { 10, 5 }); + + // Verify that the timer is still present. Then remove it and verify that the list is + // empty. + assertEquals(1, AnrTimer.sizeOfTimerList()); + assertTrue(timer.accept(t)); + assertEquals(0, AnrTimer.sizeOfTimerList()); + } + + @Test + public void testExtendOversize() throws Exception { + // Create an immediate TestHandler. + TestInjector injector = new TestInjector(25); + TestAnrTimer timer = new TestAnrTimer(true, injector); + TestArg t = new TestArg(1, 1, 3); + assertTrue(timer.start(t, 10)); + + assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS)); + assertEquals(1, mMessages.size()); + Message m = mMessages.get(0); + assertEquals(timer.what(), m.what); + assertEquals(t, m.obj); + + // Verify that the test handler saw two timeouts: one of 10ms and one of 10ms. + injector.getHandler().verifyDelays(new long[] { 10, 10 }); + + // Verify that the timer is still present. Then remove it and verify that the list is + // empty. + assertEquals(1, AnrTimer.sizeOfTimerList()); + assertTrue(timer.accept(t)); + assertEquals(0, AnrTimer.sizeOfTimerList()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index 769be177ce03..0f3daec263e0 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -182,7 +182,8 @@ public class AuthSessionTest { eq(TEST_PACKAGE), eq(TEST_REQUEST_ID), eq(sensor.getCookie()), - anyBoolean() /* allowBackgroundAuthentication */); + anyBoolean() /* allowBackgroundAuthentication */, + anyBoolean() /* isForLegacyFingerprintManager */); } final int cookie1 = session.mPreAuthInfo.eligibleSensors.get(0).getCookie(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index e79ac0986dc8..0230d77e8e14 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -598,7 +598,8 @@ public class BiometricServiceTest { anyString() /* opPackageName */, eq(TEST_REQUEST_ID), cookieCaptor.capture() /* cookie */, - anyBoolean() /* allowBackgroundAuthentication */); + anyBoolean() /* allowBackgroundAuthentication */, + anyBoolean() /* isForLegacyFingerprintManager */); // onReadyForAuthentication, mAuthSession state OK mBiometricService.mImpl.onReadyForAuthentication(TEST_REQUEST_ID, cookieCaptor.getValue()); diff --git a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java new file mode 100644 index 000000000000..75d71daa208d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER; + +import android.content.Context; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.google.common.truth.Truth; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class BluetoothRouteControllerTest { + + private final BluetoothRouteController.BluetoothRoutesUpdatedListener + mBluetoothRoutesUpdatedListener = routes -> { + // Empty on purpose. + }; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + private Context mContext; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + } + + @Test + @RequiresFlagsDisabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) + public void createInstance_audioPoliciesFlagIsDisabled_createsLegacyController() { + BluetoothRouteController deviceRouteController = + BluetoothRouteController.createInstance(mContext, mBluetoothRoutesUpdatedListener); + + Truth.assertThat(deviceRouteController).isInstanceOf(LegacyBluetoothRouteController.class); + } + + @Test + @RequiresFlagsEnabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) + public void createInstance_audioPoliciesFlagIsEnabled_createsAudioPoliciesController() { + BluetoothRouteController deviceRouteController = + BluetoothRouteController.createInstance(mContext, mBluetoothRoutesUpdatedListener); + + Truth.assertThat(deviceRouteController) + .isInstanceOf(AudioPoliciesBluetoothRouteController.class); + } +} diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java new file mode 100644 index 000000000000..ec4b8a804533 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER; + +import android.content.Context; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.google.common.truth.Truth; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class DeviceRouteControllerTest { + + private final DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener = + deviceRoute -> { + // Empty on purpose. + }; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + private Context mContext; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + } + + @Test + @RequiresFlagsDisabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) + public void createInstance_audioPoliciesFlagIsDisabled_createsLegacyController() { + DeviceRouteController deviceRouteController = + DeviceRouteController.createInstance(mContext, mOnDeviceRouteChangedListener); + + Truth.assertThat(deviceRouteController).isInstanceOf(LegacyDeviceRouteController.class); + } + + @Test + @RequiresFlagsEnabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER) + public void createInstance_audioPoliciesFlagIsEnabled_createsAudioPoliciesController() { + DeviceRouteController deviceRouteController = + DeviceRouteController.createInstance(mContext, mOnDeviceRouteChangedListener); + + Truth.assertThat(deviceRouteController) + .isInstanceOf(AudioPoliciesDeviceRouteController.class); + } +} diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index ddd1221e4c91..d85768dd7588 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -79,6 +79,9 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + /** * Tests for the {@link MediaProjectionManagerService} class. * @@ -202,6 +205,29 @@ public class MediaProjectionManagerServiceTest { } @Test + public void testCreateProjection_priorProjectionGrant() throws + NameNotFoundException, InterruptedException { + // Create a first projection. + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + FakeIMediaProjectionCallback callback1 = new FakeIMediaProjectionCallback(); + projection.start(callback1); + + // Create a second projection. + MediaProjectionManagerService.MediaProjection secondProjection = + startProjectionPreconditions(); + FakeIMediaProjectionCallback callback2 = new FakeIMediaProjectionCallback(); + secondProjection.start(callback2); + + // Check that the first projection get stopped, but not the second projection. + final int timeout = 5; + boolean stoppedCallback1 = callback1.mLatch.await(timeout, TimeUnit.SECONDS); + boolean stoppedCallback2 = callback2.mLatch.await(timeout, TimeUnit.SECONDS); + + assertThat(stoppedCallback1).isTrue(); + assertThat(stoppedCallback2).isFalse(); + } + + @Test public void testCreateProjection_attemptReuse_noPriorProjectionGrant() throws NameNotFoundException { // Create a first projection. @@ -785,8 +811,10 @@ public class MediaProjectionManagerServiceTest { } private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub { + CountDownLatch mLatch = new CountDownLatch(1); @Override public void onStop() throws RemoteException { + mLatch.countDown(); } @Override diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index cb0a61533dd5..4576e9be07ad 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -22,6 +22,8 @@ import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_ import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.SHOW_IMMEDIATELY; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP; +import static android.app.Notification.EXTRA_PICTURE; +import static android.app.Notification.EXTRA_PICTURE_ICON; import static android.app.Notification.FLAG_AUTO_CANCEL; import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.FLAG_CAN_COLORIZE; @@ -30,7 +32,6 @@ import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; import static android.app.Notification.FLAG_USER_INITIATED_JOB; -import static android.app.Notification.GROUP_KEY_SILENT; import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE; import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; @@ -89,6 +90,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER; +import static com.android.server.notification.NotificationManagerService.BITMAP_EXPIRATION_TIME_MS; import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED; import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED; @@ -11285,114 +11287,142 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue(); } - @Test - public void testIsBigPictureWithBitmapOrIcon_notBigPicture_false() { - Notification n = new Notification.Builder(mContext).build(); + private NotificationRecord createBigPictureRecord(boolean isBigPictureStyle, boolean hasImage, + boolean isImageBitmap, boolean isExpired) { + Notification.Builder builder = new Notification.Builder(mContext); + Notification.BigPictureStyle style = new Notification.BigPictureStyle(); - assertThat(mService.isBigPictureWithBitmapOrIcon(n)).isFalse(); - } + if (isBigPictureStyle && hasImage) { + if (isImageBitmap) { + style = style.bigPicture(Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888)); + } else { + style = style.bigPicture(Icon.createWithResource(mContext, R.drawable.btn_plus)); + } + } + if (isBigPictureStyle) { + builder.setStyle(style); + } - @Test - public void testIsBigPictureWithBitmapOrIcon_bigPictureWithBitmap_true() { - Bitmap bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888); + Notification notification = builder.setChannelId(TEST_CHANNEL_ID).build(); - Notification n = new Notification.Builder(mContext) - .setStyle(new Notification.BigPictureStyle().bigPicture(bitmap)) - .build(); + long timePostedMs = System.currentTimeMillis(); + if (isExpired) { + timePostedMs -= BITMAP_EXPIRATION_TIME_MS; + } + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + notification, UserHandle.getUserHandleForUid(mUid), null, timePostedMs); - assertThat(mService.isBigPictureWithBitmapOrIcon(n)).isTrue(); + return new NotificationRecord(mContext, sbn, mTestNotificationChannel); } - @Test - public void testIsBigPictureWithBitmapOrIcon_bigPictureWithIcon_true() { - Icon icon = Icon.createWithResource(mContext, R.drawable.btn_plus); - - Notification n = new Notification.Builder(mContext) - .setStyle(new Notification.BigPictureStyle().bigPicture(icon)) - .build(); - - assertThat(mService.isBigPictureWithBitmapOrIcon(n)).isTrue(); + private void addRecordAndRemoveBitmaps(NotificationRecord record) { + mService.addNotification(record); + mInternalService.removeBitmaps(); + waitForIdle(); } @Test - public void testIsBitmapExpired_notExpired_false() { - final boolean result = mService.isBitmapExpired( - /* timePosted= */ 0, - /* timeNow= */ 1, - /* timeToLive= */ 2); - assertThat(result).isFalse(); + public void testRemoveBitmaps_notBigPicture_noRepost() { + addRecordAndRemoveBitmaps( + createBigPictureRecord( + /* isBigPictureStyle= */ false, + /* hasImage= */ false, + /* isImageBitmap= */ false, + /* isExpired= */ false)); + verify(mWorkerHandler, never()) + .post(any(NotificationManagerService.EnqueueNotificationRunnable.class)); } @Test - public void testIsBitmapExpired_expired_true() { - final boolean result = mService.isBitmapExpired( - /* timePosted= */ 0, - /* timeNow= */ 2, - /* timeToLive= */ 1); - assertThat(result).isTrue(); + public void testRemoveBitmaps_bigPictureNoImage_noRepost() { + addRecordAndRemoveBitmaps( + createBigPictureRecord( + /* isBigPictureStyle= */ true, + /* hasImage= */ false, + /* isImageBitmap= */ false, + /* isExpired= */ false)); + verify(mWorkerHandler, never()) + .post(any(NotificationManagerService.EnqueueNotificationRunnable.class)); } @Test - public void testRemoveBitmapAndRepost_removeBitmapFromExtras() { - // Create big picture NotificationRecord with bitmap - Bitmap bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888); - - Notification n = new Notification.Builder(mContext, "test") - .setStyle(new android.app.Notification.BigPictureStyle().bigPicture(bitmap)) - .build(); - - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, - n, UserHandle.getUserHandleForUid(mUid), null, 0); - - NotificationRecord bigPictureRecord = - new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - mService.removeBitmapAndRepost(bigPictureRecord); - - Bitmap bitmapExtra = bigPictureRecord.getNotification().extras.getParcelable( - Notification.EXTRA_PICTURE, Bitmap.class); - assertThat(bitmapExtra).isNull(); + public void testRemoveBitmaps_notExpired_noRepost() { + addRecordAndRemoveBitmaps( + createBigPictureRecord( + /* isBigPictureStyle= */ true, + /* hasImage= */ true, + /* isImageBitmap= */ true, + /* isExpired= */ false)); + verify(mWorkerHandler, never()) + .post(any(NotificationManagerService.EnqueueNotificationRunnable.class)); } @Test - public void testRemoveBitmapAndRepost_removeIconFromExtras() { - // Create big picture NotificationRecord with Icon - Icon icon = Icon.createWithResource(mContext, R.drawable.btn_plus); - - Notification n = new Notification.Builder(mContext, "test") - .setStyle(new android.app.Notification.BigPictureStyle().bigPicture(icon)) - .build(); - - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, - n, UserHandle.getUserHandleForUid(mUid), null, 0); - - NotificationRecord bigPictureRecord = - new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - mService.removeBitmapAndRepost(bigPictureRecord); - - Icon iconExtra = bigPictureRecord.getNotification().extras.getParcelable( - Notification.EXTRA_PICTURE_ICON, Icon.class); - assertThat(iconExtra).isNull(); + public void testRemoveBitmaps_bitmapExpired_repost() { + addRecordAndRemoveBitmaps( + createBigPictureRecord( + /* isBigPictureStyle= */ true, + /* hasImage= */ true, + /* isImageBitmap= */ true, + /* isExpired= */ true)); + verify(mWorkerHandler, times(1)) + .post(any(NotificationManagerService.EnqueueNotificationRunnable.class)); } @Test - public void testRemoveBitmapAndRepost_flagOnlyAlertOnce() { - Bitmap bitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888); - - Notification n = new Notification.Builder(mContext, "test") - .setStyle(new android.app.Notification.BigPictureStyle().bigPicture(bitmap)) - .build(); + public void testRemoveBitmaps_bitmapExpired_bitmapGone() { + NotificationRecord record = createBigPictureRecord( + /* isBigPictureStyle= */ true, + /* hasImage= */ true, + /* isImageBitmap= */ true, + /* isExpired= */ true); + addRecordAndRemoveBitmaps(record); + assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE)).isFalse(); + } - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, - n, UserHandle.getUserHandleForUid(mUid), null, 0); + @Test + public void testRemoveBitmaps_bitmapExpired_silent() { + NotificationRecord record = createBigPictureRecord( + /* isBigPictureStyle= */ true, + /* hasImage= */ true, + /* isImageBitmap= */ true, + /* isExpired= */ true); + addRecordAndRemoveBitmaps(record); + assertThat(record.getNotification().flags & FLAG_ONLY_ALERT_ONCE).isNotEqualTo(0); + } - NotificationRecord bigPictureRecord = - new NotificationRecord(mContext, sbn, mTestNotificationChannel); + @Test + public void testRemoveBitmaps_iconExpired_repost() { + addRecordAndRemoveBitmaps( + createBigPictureRecord( + /* isBigPictureStyle= */ true, + /* hasImage= */ true, + /* isImageBitmap= */ false, + /* isExpired= */ true)); + verify(mWorkerHandler, times(1)) + .post(any(NotificationManagerService.EnqueueNotificationRunnable.class)); + } - mService.removeBitmapAndRepost(bigPictureRecord); + @Test + public void testRemoveBitmaps_iconExpired_iconGone() { + NotificationRecord record = createBigPictureRecord( + /* isBigPictureStyle= */ true, + /* hasImage= */ true, + /* isImageBitmap= */ false, + /* isExpired= */ true); + addRecordAndRemoveBitmaps(record); + assertThat(record.getNotification().extras.containsKey(EXTRA_PICTURE_ICON)).isFalse(); + } - assertThat(n.flags & FLAG_ONLY_ALERT_ONCE).isNotEqualTo(0); + @Test + public void testRemoveBitmaps_iconExpired_silent() { + NotificationRecord record = createBigPictureRecord( + /* isBigPictureStyle= */ true, + /* hasImage= */ true, + /* isImageBitmap= */ false, + /* isExpired= */ true); + addRecordAndRemoveBitmaps(record); + assertThat(record.getNotification().flags & FLAG_ONLY_ALERT_ONCE).isNotEqualTo(0); } @Test diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index d6a38773bf6b..42e3383987d6 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -47,8 +47,6 @@ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"/> - <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/> - <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) --> <application android:debuggable="true" @@ -106,11 +104,6 @@ android:showWhenLocked="true" android:turnScreenOn="true" /> - <activity android:name="android.app.Activity" - android:exported="true" - android:showWhenLocked="true" - android:turnScreenOn="true" /> - <activity android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyActivity" android:exported="true"> diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java index 479194673e80..0a7bb00ce1c2 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java @@ -297,6 +297,7 @@ public class ShortcutLoggingTests extends ShortcutKeyTestBase { mPhoneWindowManager.overrideUserSetupComplete(); mPhoneWindowManager.setupAssistForLaunch(); mPhoneWindowManager.overrideTogglePanel(); + mPhoneWindowManager.overrideInjectKeyEvent(); } @Test diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index e301da7c58fc..6d46d9ccf02e 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -464,6 +464,10 @@ class TestPhoneWindowManager { doReturn(device).when(mInputManager).getInputDevice(anyInt()); } + void overrideInjectKeyEvent() { + doReturn(true).when(mInputManager).injectInputEvent(any(KeyEvent.class), anyInt()); + } + void overrideSearchKeyBehavior(int behavior) { mPhoneWindowManager.mSearchKeyBehavior = behavior; } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java index 0989db4c25ac..568471d67c31 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; @@ -26,7 +25,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; -import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SANDBOX_ORDER_ID; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -36,7 +34,6 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import android.annotation.NonNull; import android.annotation.Nullable; @@ -44,13 +41,11 @@ import android.app.ActivityManagerInternal; import android.app.ActivityOptions; import android.app.KeyguardManager; import android.app.admin.DevicePolicyManagerInternal; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; -import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; @@ -76,7 +71,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; /** @@ -509,88 +503,4 @@ public class ActivityStartInterceptorTest { verify(callback, times(1)).onActivityLaunched(any(), any(), any()); } - - @Test - public void testSandboxServiceInterceptionHappensToIntentWithSandboxActivityAction() { - ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null); - mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback); - - PackageManager packageManagerMock = mock(PackageManager.class); - String sandboxPackageNameMock = "com.sandbox.mock"; - when(mContext.getPackageManager()).thenReturn(packageManagerMock); - when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock); - - Intent intent = new Intent().setAction(ACTION_START_SANDBOXED_ACTIVITY); - mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null); - - verify(spyCallback, times(1)).onInterceptActivityLaunch( - any(ActivityInterceptorCallback.ActivityInterceptorInfo.class)); - } - - @Test - public void testSandboxServiceInterceptionHappensToIntentWithSandboxPackage() { - ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null); - mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback); - - PackageManager packageManagerMock = mock(PackageManager.class); - String sandboxPackageNameMock = "com.sandbox.mock"; - when(mContext.getPackageManager()).thenReturn(packageManagerMock); - when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock); - - Intent intent = new Intent().setPackage(sandboxPackageNameMock); - mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null); - - verify(spyCallback, times(1)).onInterceptActivityLaunch( - any(ActivityInterceptorCallback.ActivityInterceptorInfo.class)); - } - - @Test - public void testSandboxServiceInterceptionHappensToIntentWithComponentNameWithSandboxPackage() { - ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null); - mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback); - - PackageManager packageManagerMock = mock(PackageManager.class); - String sandboxPackageNameMock = "com.sandbox.mock"; - when(mContext.getPackageManager()).thenReturn(packageManagerMock); - when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock); - - Intent intent = new Intent().setComponent(new ComponentName(sandboxPackageNameMock, "")); - mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null); - - verify(spyCallback, times(1)).onInterceptActivityLaunch( - any(ActivityInterceptorCallback.ActivityInterceptorInfo.class)); - } - - @Test - public void testSandboxServiceInterceptionNotCalledWhenIntentNotRelatedToSandbox() { - ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null); - mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback); - - PackageManager packageManagerMock = mock(PackageManager.class); - String sandboxPackageNameMock = "com.sandbox.mock"; - when(mContext.getPackageManager()).thenReturn(packageManagerMock); - when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock); - - // Intent: null - mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null); - - // Action: null, Package: null, ComponentName: null - Intent intent = new Intent(); - mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null); - - // Wrong Action - intent = new Intent().setAction(Intent.ACTION_VIEW); - mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null); - - // Wrong Package - intent = new Intent().setPackage("Random"); - mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null); - - // Wrong ComponentName's package - intent = new Intent().setComponent(new ComponentName("Random", "")); - mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null); - - verify(spyCallback, never()).onInterceptActivityLaunch( - any(ActivityInterceptorCallback.ActivityInterceptorInfo.class)); - } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index 5341588c3992..72c3ebece242 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -237,26 +237,27 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { displayInfo.copyFrom(mDisplayInfo); displayInfo.type = Display.TYPE_VIRTUAL; DisplayContent virtualDisplay = createNewDisplay(displayInfo); + final KeyguardController keyguardController = mSupervisor.getKeyguardController(); // Make sure we're starting out with 2 unlocked displays assertEquals(2, mRootWindowContainer.getChildCount()); mRootWindowContainer.forAllDisplays(displayContent -> { assertFalse(displayContent.isKeyguardLocked()); - assertFalse(displayContent.isAodShowing()); + assertFalse(keyguardController.isAodShowing(displayContent.mDisplayId)); }); // Check that setLockScreenShown locks both displays mAtm.setLockScreenShown(true, true); mRootWindowContainer.forAllDisplays(displayContent -> { assertTrue(displayContent.isKeyguardLocked()); - assertTrue(displayContent.isAodShowing()); + assertTrue(keyguardController.isAodShowing(displayContent.mDisplayId)); }); // Check setLockScreenShown unlocking both displays mAtm.setLockScreenShown(false, false); mRootWindowContainer.forAllDisplays(displayContent -> { assertFalse(displayContent.isKeyguardLocked()); - assertFalse(displayContent.isAodShowing()); + assertFalse(keyguardController.isAodShowing(displayContent.mDisplayId)); }); } @@ -270,25 +271,26 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; displayInfo.flags = Display.FLAG_OWN_DISPLAY_GROUP | Display.FLAG_ALWAYS_UNLOCKED; DisplayContent newDisplay = createNewDisplay(displayInfo); + final KeyguardController keyguardController = mSupervisor.getKeyguardController(); // Make sure we're starting out with 2 unlocked displays assertEquals(2, mRootWindowContainer.getChildCount()); mRootWindowContainer.forAllDisplays(displayContent -> { assertFalse(displayContent.isKeyguardLocked()); - assertFalse(displayContent.isAodShowing()); + assertFalse(keyguardController.isAodShowing(displayContent.mDisplayId)); }); // setLockScreenShown should only lock the default display, not the virtual one mAtm.setLockScreenShown(true, true); assertTrue(mDefaultDisplay.isKeyguardLocked()); - assertTrue(mDefaultDisplay.isAodShowing()); + assertTrue(keyguardController.isAodShowing(mDefaultDisplay.mDisplayId)); DisplayContent virtualDisplay = mRootWindowContainer.getDisplayContent( newDisplay.getDisplayId()); assertNotEquals(Display.DEFAULT_DISPLAY, virtualDisplay.getDisplayId()); assertFalse(virtualDisplay.isKeyguardLocked()); - assertFalse(virtualDisplay.isAodShowing()); + assertFalse(keyguardController.isAodShowing(virtualDisplay.mDisplayId)); } /* diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java index ecd84e174615..3d3531e92abe 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java @@ -304,6 +304,30 @@ public class ContentRecorderTests extends WindowTestsBase { anyFloat(), anyFloat()); } + /** + * Test that resizing the output surface results in resizing the mirrored content to fit. + */ + @Test + public void testOnConfigurationChanged_resizeSurface() { + mContentRecorder.setContentRecordingSession(mDisplaySession); + mContentRecorder.updateRecording(); + + // Resize the output surface. + final Point newSurfaceSize = new Point(Math.round(sSurfaceSize.x / 2f), + Math.round(sSurfaceSize.y * 2)); + doReturn(newSurfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize( + anyInt()); + mContentRecorder.onConfigurationChanged( + mVirtualDisplayContent.getConfiguration().orientation); + + // No resize is issued, only the initial transformations when we started recording. + verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(), + anyFloat()); + verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(), + anyFloat(), anyFloat()); + + } + @Test public void testOnTaskOrientationConfigurationChanged_resizesSurface() { mContentRecorder.setContentRecordingSession(mTaskSession); diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java index be3f01ec7dbf..80e169d8d579 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java @@ -17,15 +17,14 @@ package com.android.server.wm; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; -import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; + import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; -import static junit.framework.Assert.assertEquals; + import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -38,8 +37,6 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; -import com.android.window.flags.FakeFeatureFlagsImpl; - import org.junit.Before; import org.junit.Test; @@ -60,16 +57,12 @@ public class LetterboxConfigurationTest { private LetterboxConfiguration mLetterboxConfiguration; private LetterboxConfigurationPersister mLetterboxConfigurationPersister; - private MutableFakeFeatureFlagsImpl mMutableFakeFeatureFlags; - - @Before public void setUp() throws Exception { mContext = getInstrumentation().getTargetContext(); - mMutableFakeFeatureFlags = new MutableFakeFeatureFlagsImpl(); mLetterboxConfigurationPersister = mock(LetterboxConfigurationPersister.class); mLetterboxConfiguration = new LetterboxConfiguration(mContext, - mLetterboxConfigurationPersister, mMutableFakeFeatureFlags); + mLetterboxConfigurationPersister); } @Test @@ -99,22 +92,6 @@ public class LetterboxConfigurationTest { } @Test - public void test_whenFlagEnabled_wallpaperIsDefaultBackground() { - mMutableFakeFeatureFlags.setLetterboxBackgroundWallpaperFlag(true); - assertEquals(LETTERBOX_BACKGROUND_WALLPAPER, - mLetterboxConfiguration.getLetterboxBackgroundType()); - assertEquals(1, mMutableFakeFeatureFlags.getInvocationCount()); - } - - @Test - public void test_whenFlagDisabled_solidColorIsDefaultBackground() { - mMutableFakeFeatureFlags.setLetterboxBackgroundWallpaperFlag(false); - assertEquals(LETTERBOX_BACKGROUND_SOLID_COLOR, - mLetterboxConfiguration.getLetterboxBackgroundType()); - assertEquals(1, mMutableFakeFeatureFlags.getInvocationCount()); - } - - @Test public void test_whenMovedHorizontally_updatePositionAccordingly() { // Starting from center assertForHorizontalMove( @@ -311,23 +288,4 @@ public class LetterboxConfigurationTest { false /* forTabletopMode */, LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP); } - - private static class MutableFakeFeatureFlagsImpl extends FakeFeatureFlagsImpl { - private boolean mLetterboxBackgroundWallpaperFlag; - private int mInvocationCount; - - public void setLetterboxBackgroundWallpaperFlag(boolean letterboxBackgroundWallpaperFlag) { - mLetterboxBackgroundWallpaperFlag = letterboxBackgroundWallpaperFlag; - } - - @Override - public boolean letterboxBackgroundWallpaperFlag() { - mInvocationCount++; - return mLetterboxBackgroundWallpaperFlag; - } - - int getInvocationCount() { - return mInvocationCount; - } - } } diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 2ad9fa0e5b13..0566f460c655 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -862,6 +862,39 @@ public class LetterboxUiControllerTest extends WindowTestsBase { } @Test + public void testShouldEnableUserAspectRatioSettings_falseProperty_returnsFalse() + throws Exception { + prepareActivityThatShouldApplyUserMinAspectRatioOverride(); + mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false); + + mController = new LetterboxUiController(mWm, mActivity); + + assertFalse(mController.shouldEnableUserAspectRatioSettings()); + } + + @Test + public void testShouldEnableUserAspectRatioSettings_trueProperty_returnsTrue() + throws Exception { + prepareActivityThatShouldApplyUserMinAspectRatioOverride(); + mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true); + + mController = new LetterboxUiController(mWm, mActivity); + + assertTrue(mController.shouldEnableUserAspectRatioSettings()); + } + + @Test + public void testShouldEnableUserAspectRatioSettings_noIgnoreOrientaion_returnsFalse() + throws Exception { + prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false); + mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true); + + mController = new LetterboxUiController(mWm, mActivity); + + assertFalse(mController.shouldEnableUserAspectRatioSettings()); + } + + @Test public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse() throws Exception { prepareActivityThatShouldApplyUserMinAspectRatioOverride(); @@ -898,13 +931,26 @@ public class LetterboxUiControllerTest extends WindowTestsBase { assertTrue(mController.shouldApplyUserMinAspectRatioOverride()); } - private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() { + @Test + public void testShouldApplyUserMinAspectRatioOverride_noIgnoreOrientationreturnsFalse() { + prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false); + + assertFalse(mController.shouldApplyUserMinAspectRatioOverride()); + } + + private void prepareActivityForShouldApplyUserMinAspectRatioOverride( + boolean orientationRequest) { spyOn(mController); - doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled(); + doReturn(orientationRequest).when( + mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled(); mDisplayContent.setIgnoreOrientationRequest(true); doReturn(USER_MIN_ASPECT_RATIO_3_2).when(mController).getUserMinAspectRatioOverrideCode(); } + private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() { + prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ true); + } + private void prepareActivityThatShouldApplyUserFullscreenOverride() { spyOn(mController); doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 1dd71e00841f..fb27d6368a7e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -571,26 +571,28 @@ public class TaskTests extends WindowTestsBase { final Task task = rootTask.getBottomMostTask(); final ActivityRecord root = task.getTopNonFinishingActivity(); spyOn(mWm.mLetterboxConfiguration); - - // When device config flag is disabled the button is not enabled - doReturn(false).when(mWm.mLetterboxConfiguration) - .isUserAppAspectRatioSettingsEnabled(); - doReturn(false).when(mWm.mLetterboxConfiguration) - .isTranslucentLetterboxingEnabled(); - assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton); - - // The flag is enabled - doReturn(true).when(mWm.mLetterboxConfiguration) - .isUserAppAspectRatioSettingsEnabled(); spyOn(root); - doReturn(task).when(root).getOrganizedTask(); - // When the flag is enabled and the top activity is not in size compat mode. + spyOn(root.mLetterboxUiController); + + doReturn(true).when(root.mLetterboxUiController) + .shouldEnableUserAspectRatioSettings(); doReturn(false).when(root).inSizeCompatMode(); + doReturn(task).when(root).getOrganizedTask(); + + // The button should be eligible to be displayed assertTrue(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton); + // When shouldApplyUserMinAspectRatioOverride is disable the button is not enabled + doReturn(false).when(root.mLetterboxUiController) + .shouldEnableUserAspectRatioSettings(); + assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton); + doReturn(true).when(root.mLetterboxUiController) + .shouldEnableUserAspectRatioSettings(); + // When in size compat mode the button is not enabled doReturn(true).when(root).inSizeCompatMode(); assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton); + doReturn(false).when(root).inSizeCompatMode(); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java deleted file mode 100644 index e8a847c5d8a4..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java +++ /dev/null @@ -1,149 +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.server.wm; - -import static android.view.InputWindowHandle.USE_SURFACE_TRUSTED_OVERLAY; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeFalse; -import static org.junit.Assume.assumeTrue; - -import android.app.Activity; -import android.app.Instrumentation; -import android.os.IBinder; -import android.platform.test.annotations.Presubmit; -import android.server.wm.BuildUtils; -import android.server.wm.CtsWindowInfoUtils; -import android.view.View; -import android.view.ViewTreeObserver; -import android.view.WindowManager; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.rule.ActivityTestRule; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -@Presubmit -public class TrustedOverlayTests { - private static final String TAG = "TrustedOverlayTests"; - private static final long TIMEOUT_S = 5L * BuildUtils.HW_TIMEOUT_MULTIPLIER; - - @Rule - public TestName mName = new TestName(); - - private final ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>( - Activity.class); - - private Instrumentation mInstrumentation; - private Activity mActivity; - - @Before - public void setup() { - mInstrumentation = InstrumentationRegistry.getInstrumentation(); - mActivity = mActivityRule.launchActivity(null); - } - - @Test - public void setTrustedOverlayInputWindow() throws InterruptedException { - assumeFalse(USE_SURFACE_TRUSTED_OVERLAY); - testTrustedOverlayChildHelper(false); - } - - @Test - public void setTrustedOverlayChildLayer() throws InterruptedException { - assumeTrue(USE_SURFACE_TRUSTED_OVERLAY); - testTrustedOverlayChildHelper(true); - } - - private void testTrustedOverlayChildHelper(boolean expectTrusted) throws InterruptedException { - IBinder[] tokens = new IBinder[2]; - CountDownLatch hostTokenReady = new CountDownLatch(1); - mInstrumentation.runOnMainSync(() -> { - mActivity.getWindow().addPrivateFlags(PRIVATE_FLAG_TRUSTED_OVERLAY); - View rootView = mActivity.getWindow().getDecorView(); - if (rootView.isAttachedToWindow()) { - tokens[0] = rootView.getWindowToken(); - hostTokenReady.countDown(); - } else { - rootView.getViewTreeObserver().addOnWindowAttachListener( - new ViewTreeObserver.OnWindowAttachListener() { - @Override - public void onWindowAttached() { - tokens[0] = rootView.getWindowToken(); - hostTokenReady.countDown(); - } - - @Override - public void onWindowDetached() { - } - }); - } - }); - - assertTrue("Failed to wait for host to get added", - hostTokenReady.await(TIMEOUT_S, TimeUnit.SECONDS)); - - mInstrumentation.runOnMainSync(() -> { - WindowManager wm = mActivity.getSystemService(WindowManager.class); - - View childView = new View(mActivity) { - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - tokens[1] = getWindowToken(); - } - }; - WindowManager.LayoutParams params = new WindowManager.LayoutParams(); - params.token = tokens[0]; - params.type = TYPE_APPLICATION_PANEL; - wm.addView(childView, params); - }); - - boolean[] foundTrusted = new boolean[2]; - - CtsWindowInfoUtils.waitForWindowInfos( - windowInfos -> { - for (var windowInfo : windowInfos) { - if (windowInfo.windowToken == tokens[0] - && windowInfo.isTrustedOverlay) { - foundTrusted[0] = true; - } else if (windowInfo.windowToken == tokens[1] - && windowInfo.isTrustedOverlay) { - foundTrusted[1] = true; - } - } - return foundTrusted[0] && foundTrusted[1]; - }, TIMEOUT_S, TimeUnit.SECONDS); - - if (!foundTrusted[0] || !foundTrusted[1]) { - CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, mName.getMethodName()); - } - - assertEquals("Failed to find parent window or was not marked trusted", expectTrusted, - foundTrusted[0]); - assertEquals("Failed to find child window or was not marked trusted", expectTrusted, - foundTrusted[1]); - } -} diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java index f173d661c0a4..c5dd447b5b0c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java @@ -18,16 +18,16 @@ package com.android.server.wm; import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule; import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.app.Activity; import android.platform.test.annotations.Presubmit; -import android.util.Log; +import android.server.wm.CtsWindowInfoUtils; import android.view.SurfaceControl; import android.view.SurfaceControl.TrustedPresentationThresholds; +import androidx.annotation.GuardedBy; import androidx.test.ext.junit.rules.ActivityScenarioRule; import com.android.server.wm.utils.CommonUtils; @@ -36,9 +36,8 @@ import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestName; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.function.Consumer; /** @@ -53,6 +52,15 @@ public class TrustedPresentationCallbackTest { private static final float FRACTION_VISIBLE = 0.1f; + private final Object mResultsLock = new Object(); + @GuardedBy("mResultsLock") + private boolean mResult; + @GuardedBy("mResultsLock") + private boolean mReceivedResults; + + @Rule + public TestName mName = new TestName(); + @Rule public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule( TestActivity.class); @@ -71,36 +79,32 @@ public class TrustedPresentationCallbackTest { @Test public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException { - boolean[] results = new boolean[1]; - CountDownLatch receivedResults = new CountDownLatch(1); TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds( 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS); SurfaceControl.Transaction t = new SurfaceControl.Transaction(); mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds, Runnable::run, inTrustedPresentationState -> { - Log.d(TAG, "onTrustedPresentationChanged " + inTrustedPresentationState); - results[0] = inTrustedPresentationState; - receivedResults.countDown(); + synchronized (mResultsLock) { + mResult = inTrustedPresentationState; + mReceivedResults = true; + mResultsLock.notify(); + } }); t.apply(); - - assertTrue("Timed out waiting for results", - receivedResults.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); - assertTrue(results[0]); + synchronized (mResultsLock) { + assertResults(); + } } @Test public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException { - final Object resultsLock = new Object(); - boolean[] results = new boolean[1]; - boolean[] receivedResults = new boolean[1]; TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds( 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS); Consumer<Boolean> trustedPresentationCallback = inTrustedPresentationState -> { - synchronized (resultsLock) { - results[0] = inTrustedPresentationState; - receivedResults[0] = true; - resultsLock.notify(); + synchronized (mResultsLock) { + mResult = inTrustedPresentationState; + mReceivedResults = true; + mResultsLock.notify(); } }; SurfaceControl.Transaction t = new SurfaceControl.Transaction(); @@ -108,32 +112,41 @@ public class TrustedPresentationCallbackTest { Runnable::run, trustedPresentationCallback); t.apply(); - synchronized (resultsLock) { - if (!receivedResults[0]) { - resultsLock.wait(WAIT_TIME_MS); + synchronized (mResultsLock) { + if (!mReceivedResults) { + mResultsLock.wait(WAIT_TIME_MS); } - // Make sure we received the results and not just timed out - assertTrue("Timed out waiting for results", receivedResults[0]); - assertTrue(results[0]); - + assertResults(); // reset the state - receivedResults[0] = false; + mReceivedResults = false; } mActivity.getWindow().getRootSurfaceControl().removeTrustedPresentationCallback(t, trustedPresentationCallback); t.apply(); - synchronized (resultsLock) { - if (!receivedResults[0]) { - resultsLock.wait(WAIT_TIME_MS); + synchronized (mResultsLock) { + if (!mReceivedResults) { + mResultsLock.wait(WAIT_TIME_MS); } // Ensure we waited the full time and never received a notify on the result from the // callback. - assertFalse("Should never have received a callback", receivedResults[0]); + assertFalse("Should never have received a callback", mReceivedResults); // results shouldn't have changed. - assertTrue(results[0]); + assertTrue(mResult); + } + } + + @GuardedBy("mResultsLock") + private void assertResults() throws InterruptedException { + mResultsLock.wait(WAIT_TIME_MS); + + if (!mReceivedResults) { + CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName()); } + // Make sure we received the results and not just timed out + assertTrue("Timed out waiting for results", mReceivedResults); + assertTrue(mResult); } public static class TestActivity extends Activity { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 3d78a1dd8943..0a70a5f9b947 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -27,9 +27,9 @@ import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPH import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH; -import android.app.AppOpsManager; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.AppOpsManager; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; import android.content.ComponentName; @@ -50,6 +50,7 @@ import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SharedMemory; +import android.os.SystemProperties; import android.provider.DeviceConfig; import android.service.voice.HotwordDetectionService; import android.service.voice.HotwordDetectionServiceFailure; @@ -114,6 +115,9 @@ final class HotwordDetectionConnection { private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour private static final int MAX_ISOLATED_PROCESS_NUMBER = 10; + private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED = + SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false); + /** * Indicates the {@link HotwordDetectionService} is created. */ @@ -680,7 +684,8 @@ final class HotwordDetectionConnection { mIntent = intent; mDetectionServiceType = detectionServiceType; int flags = bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0; - if (mVisualQueryDetectionComponentName != null + if (SYSPROP_VISUAL_QUERY_SERVICE_ENABLED + && mVisualQueryDetectionComponentName != null && mHotwordDetectionComponentName != null) { flags |= Context.BIND_SHARED_ISOLATED_PROCESS; } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 471acc118572..6ba77da1d972 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -57,6 +57,7 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SharedMemory; +import android.os.SystemProperties; import android.os.UserHandle; import android.service.voice.HotwordDetector; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; @@ -96,6 +97,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne /** The delay time for retrying to request DirectActions. */ private static final long REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS = 200; + private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED = + SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false); final boolean mValid; @@ -715,7 +718,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } else { verifyDetectorForVisualQueryDetectionLocked(sharedMemory); } - if (!verifyProcessSharingLocked()) { + if (SYSPROP_VISUAL_QUERY_SERVICE_ENABLED && !verifyProcessSharingLocked()) { Slog.w(TAG, "Sandboxed detection service not in shared isolated process"); throw new IllegalStateException("VisualQueryDetectionService or HotworDetectionService " + "not in a shared isolated process. Please make sure to set " @@ -914,6 +917,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne if (hotwordInfo == null || visualQueryInfo == null) { return true; } + // Enforce shared isolated option is used when VisualQueryDetectionservice is enabled return (hotwordInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0 && (visualQueryInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0; } diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index a72f7806d3ea..1f32c978fad1 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -412,6 +412,15 @@ public class TelecomManager { "android.telecom.extra.CALL_CREATED_TIME_MILLIS"; /** + * Optional extra for incoming containing a long which specifies the time the + * call was answered by user. This value is in milliseconds. + * @hide + */ + public static final String EXTRA_CALL_ANSWERED_TIME_MILLIS = + "android.telecom.extra.CALL_ANSWERED_TIME_MILLIS"; + + + /** * Optional extra for incoming and outgoing calls containing a long which specifies the Epoch * time the call was created. * @hide diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 66f3bedf697d..7a3fc3c29d2b 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -4607,12 +4607,12 @@ public class CarrierConfigManager { * * <p>Example: * - * <pre><code> + * <pre>{@code * <string-array name="carrier_service_name_array" num="2"> * <item value="Police"/> * <item value="Ambulance"/> * </string-array> - * </code></pre> + * }</pre> */ public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array"; @@ -4626,18 +4626,18 @@ public class CarrierConfigManager { * * <ul> * <li>The number of items in both the arrays are equal - * <li>The item added in this key follows a specific format. Either it should be all numbers, - * or "+" followed by all numbers. + * <li>The item should contain dialable characters only which includes 0-9, -, *, #, (, ), + * SPACE. * </ul> * * <p>Example: * - * <pre><code> + * <pre>{@code * <string-array name="carrier_service_number_array" num="2"> - * <item value="123"/> - * <item value="+343"/> + * <item value="*123"/> + * <item value="+ (111) 111-111"/> * </string-array> - * </code></pre> + * }</pre> */ public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array"; diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 5f6c14a36d46..6130af5abdfa 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -18,6 +18,7 @@ package android.telephony.satellite; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -39,6 +40,7 @@ import android.telephony.TelephonyFrameworkInitializer; import com.android.internal.telephony.IIntegerConsumer; import com.android.internal.telephony.ITelephony; import com.android.internal.telephony.IVoidConsumer; +import com.android.internal.telephony.flags.Flags; import com.android.telephony.Rlog; import java.lang.annotation.Retention; @@ -769,6 +771,16 @@ public final class SatelliteManager { */ public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; /** + * The satellite modem is powered on but the device is not registered to a satellite cell. + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; + /** + * The satellite modem is powered on and the device is registered to a satellite cell. + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; + /** * Satellite modem state is unknown. This generic modem state should be used only when the * modem state cannot be mapped to other specific modem states. */ @@ -782,6 +794,8 @@ public final class SatelliteManager { SATELLITE_MODEM_STATE_DATAGRAM_RETRYING, SATELLITE_MODEM_STATE_OFF, SATELLITE_MODEM_STATE_UNAVAILABLE, + SATELLITE_MODEM_STATE_NOT_CONNECTED, + SATELLITE_MODEM_STATE_CONNECTED, SATELLITE_MODEM_STATE_UNKNOWN }) @Retention(RetentionPolicy.SOURCE) diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl index e4f94134caa1..162fe2b40104 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl +++ b/telephony/java/android/telephony/satellite/stub/SatelliteModemState.aidl @@ -46,6 +46,14 @@ enum SatelliteModemState { */ SATELLITE_MODEM_STATE_UNAVAILABLE = 5, /** + * The satellite modem is powered on but the device is not registered to a satellite cell. + */ + SATELLITE_MODEM_STATE_NOT_CONNECTED = 6, + /** + * The satellite modem is powered on and the device is registered to a satellite cell. + */ + SATELLITE_MODEM_STATE_CONNECTED = 7, + /** * Satellite modem state is unknown. This generic modem state should be used only when the * modem state cannot be mapped to other specific modem states. */ diff --git a/tests/CtsSurfaceControlTestsStaging/TEST_MAPPING b/tests/CtsSurfaceControlTestsStaging/TEST_MAPPING index de0ad5915868..0b75eb35520e 100644 --- a/tests/CtsSurfaceControlTestsStaging/TEST_MAPPING +++ b/tests/CtsSurfaceControlTestsStaging/TEST_MAPPING @@ -1,5 +1,5 @@ { - "postsubmit": [ + "presubmit": [ { "name": "CtsSurfaceControlTestsStaging" } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt index c09041518296..ee22d0732eac 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt @@ -16,7 +16,6 @@ package com.android.server.wm.flicker.quickswitch -import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.datatypes.Rect import android.tools.common.traces.component.ComponentNameMatcher @@ -51,6 +50,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@FlakyTest(bugId = 298544839) class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTest(flicker) { private val testApp1 = SimpleAppHelper(instrumentation) private val testApp2 = NonResizeableAppHelper(instrumentation) @@ -92,7 +92,6 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes * Checks that the transition starts with [testApp1]'s windows filling/covering exactly the * entirety of the display. */ - @Presubmit @Test open fun startsWithApp1WindowsCoverFullScreen() { flicker.assertWmStart { @@ -112,7 +111,6 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes } /** Checks that the transition starts with [testApp1] being the top window. */ - @Presubmit @Test open fun startsWithApp1WindowBeingOnTop() { flicker.assertWmStart { this.isAppWindowOnTop(testApp1) } @@ -122,7 +120,6 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes * Checks that [testApp2] windows fill the entire screen (i.e. is "fullscreen") at the end of * the transition once we have fully quick switched from [testApp1] back to the [testApp2]. */ - @Presubmit @Test open fun endsWithApp2WindowsCoveringFullScreen() { flicker.assertWmEnd { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) } @@ -132,7 +129,6 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes * Checks that [testApp2] layers fill the entire screen (i.e. is "fullscreen") at the end of the * transition once we have fully quick switched from [testApp1] back to the [testApp2]. */ - @Presubmit @Test open fun endsWithApp2LayersCoveringFullScreen() { flicker.assertLayersEnd { @@ -145,7 +141,6 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes * Checks that [testApp2] is the top window at the end of the transition once we have fully * quick switched from [testApp1] back to the [testApp2]. */ - @Presubmit @Test open fun endsWithApp2BeingOnTop() { flicker.assertWmEnd { this.isAppWindowOnTop(testApp2) } @@ -155,7 +150,6 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes * Checks that [testApp2]'s window starts off invisible and becomes visible at some point before * the end of the transition and then stays visible until the end of the transition. */ - @Presubmit @Test open fun app2WindowBecomesAndStaysVisible() { flicker.assertWm { @@ -171,7 +165,6 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes * Checks that [testApp2]'s layer starts off invisible and becomes visible at some point before * the end of the transition and then stays visible until the end of the transition. */ - @Presubmit @Test open fun app2LayerBecomesAndStaysVisible() { flicker.assertLayers { this.isInvisible(testApp2).then().isVisible(testApp2) } @@ -181,7 +174,6 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes * Checks that [testApp1]'s window starts off visible and becomes invisible at some point before * the end of the transition and then stays invisible until the end of the transition. */ - @Presubmit @Test open fun app1WindowBecomesAndStaysInvisible() { flicker.assertWm { this.isAppWindowVisible(testApp1).then().isAppWindowInvisible(testApp1) } @@ -191,7 +183,6 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes * Checks that [testApp1]'s layer starts off visible and becomes invisible at some point before * the end of the transition and then stays invisible until the end of the transition. */ - @Presubmit @Test open fun app1LayerBecomesAndStaysInvisible() { flicker.assertLayers { this.isVisible(testApp1).then().isInvisible(testApp1) } @@ -202,7 +193,6 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes * Ensures that at any point, either [testApp2] or [testApp1]'s windows are at least partially * visible. */ - @Presubmit @Test open fun app2WindowIsVisibleOnceApp1WindowIsInvisible() { flicker.assertWm { @@ -221,7 +211,6 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes * Ensures that at any point, either [testApp2] or [testApp1]'s windows are at least partially * visible. */ - @Presubmit @Test open fun app2LayerIsVisibleOnceApp1LayerIsInvisible() { flicker.assertLayers { @@ -236,7 +225,6 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes } /** {@inheritDoc} */ - @Presubmit @Test override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() @@ -245,14 +233,34 @@ class QuickSwitchBetweenTwoAppsForwardTest(flicker: LegacyFlickerTest) : BaseTes @Test override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() - @FlakyTest(bugId = 246284708) @Test override fun visibleLayersShownMoreThanOneConsecutiveEntry() = super.visibleLayersShownMoreThanOneConsecutiveEntry() - @FlakyTest(bugId = 250518877) + @Test override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() + + @Test override fun entireScreenCovered() = super.entireScreenCovered() + + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + + @Test + override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd() + + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + @Test + override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd() + + @Test override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + + @Test override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible() + @Test - override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { private var startDisplayBounds = Rect.EMPTY diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index 96b685dc356a..365e00e2b652 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -26,6 +26,7 @@ android_test { "androidx.test.runner", "androidx.test.uiautomator_uiautomator", "servicestests-utils", + "flag-junit", "frameworks-base-testutils", "hamcrest-library", "kotlin-test", diff --git a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt new file mode 100644 index 000000000000..3a2a3be0690d --- /dev/null +++ b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input + +import android.content.ContextWrapper +import android.graphics.drawable.Drawable +import android.platform.test.annotations.Presubmit +import android.platform.test.flag.junit.SetFlagsRule +import androidx.test.platform.app.InstrumentationRegistry +import com.android.hardware.input.Flags +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.junit.MockitoJUnitRunner + +/** + * Tests for Keyboard layout preview + * + * Build/Install/Run: + * atest InputTests:KeyboardLayoutPreviewTests + */ +@Presubmit +@RunWith(MockitoJUnitRunner::class) +class KeyboardLayoutPreviewTests { + + companion object { + const val WIDTH = 100 + const val HEIGHT = 100 + } + + @get:Rule + val setFlagsRule = SetFlagsRule() + + private fun createDrawable(): Drawable? { + val context = ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext()) + val inputManager = context.getSystemService(InputManager::class.java)!! + return inputManager.getKeyboardLayoutPreview(null, WIDTH, HEIGHT) + } + + @Test + fun testKeyboardLayoutDrawable_hasCorrectDimensions() { + setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG) + val drawable = createDrawable()!! + assertEquals(WIDTH, drawable.intrinsicWidth) + assertEquals(HEIGHT, drawable.intrinsicHeight) + } + + @Test + fun testKeyboardLayoutDrawable_isNull_ifFlagOff() { + setFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG) + assertNull(createDrawable()) + } +}
\ No newline at end of file diff --git a/tests/InputScreenshotTest/Android.bp b/tests/InputScreenshotTest/Android.bp new file mode 100644 index 000000000000..eee486f99748 --- /dev/null +++ b/tests/InputScreenshotTest/Android.bp @@ -0,0 +1,60 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "InputScreenshotTests", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + platform_apis: true, + certificate: "platform", + static_libs: [ + "androidx.arch.core_core-testing", + "androidx.compose.ui_ui-test-junit4", + "androidx.compose.ui_ui-test-manifest", + "androidx.lifecycle_lifecycle-runtime-testing", + "androidx.compose.animation_animation", + "androidx.compose.material3_material3", + "androidx.compose.material_material-icons-extended", + "androidx.compose.runtime_runtime", + "androidx.compose.runtime_runtime-livedata", + "androidx.compose.ui_ui-tooling-preview", + "androidx.lifecycle_lifecycle-livedata-ktx", + "androidx.lifecycle_lifecycle-runtime-compose", + "androidx.navigation_navigation-compose", + "truth-prebuilt", + "androidx.compose.runtime_runtime", + "androidx.test.core", + "androidx.test.ext.junit", + "androidx.test.ext.truth", + "androidx.test.rules", + "androidx.test.runner", + "androidx.test.uiautomator_uiautomator", + "servicestests-utils", + "frameworks-base-testutils", + "platform-screenshot-diff-core", + "hamcrest-library", + "kotlin-test", + "flag-junit", + "platform-test-annotations", + "services.core.unboosted", + "testables", + "testng", + "truth-prebuilt", + ], + libs: [ + "android.test.mock", + "android.test.base", + ], + test_suites: ["device-tests"], + compile_multilib: "both", + use_embedded_native_libs: false, + asset_dirs: ["assets"], +} diff --git a/tests/InputScreenshotTest/AndroidManifest.xml b/tests/InputScreenshotTest/AndroidManifest.xml new file mode 100644 index 000000000000..9ffbb3a58ebb --- /dev/null +++ b/tests/InputScreenshotTest/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.input.screenshot"> + + <uses-sdk android:minSdkVersion="21"/> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:label="Screenshot tests for Input" + android:targetPackage="com.android.input.screenshot"> + </instrumentation> +</manifest> diff --git a/tests/InputScreenshotTest/AndroidTest.xml b/tests/InputScreenshotTest/AndroidTest.xml new file mode 100644 index 000000000000..cc25fa454f06 --- /dev/null +++ b/tests/InputScreenshotTest/AndroidTest.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> +<configuration description="Runs Input screendiff tests."> + <option name="test-suite-tag" value="apct-instrumentation" /> + <option name="test-suite-tag" value="apct" /> + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <option name="optimized-property-setting" value="true" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="InputScreenshotTests.apk" /> + </target_preparer> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="directory-keys" + value="/data/user/0/com.android.input.screenshot/files/input_screenshots" /> + <option name="collect-on-run-ended-only" value="true" /> + </metrics_collector> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.input.screenshot" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + </test> +</configuration> diff --git a/tests/InputScreenshotTest/OWNERS b/tests/InputScreenshotTest/OWNERS new file mode 100644 index 000000000000..3cffce960b1c --- /dev/null +++ b/tests/InputScreenshotTest/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 136048 +include /core/java/android/hardware/input/OWNERS diff --git a/tests/InputScreenshotTest/TEST_MAPPING b/tests/InputScreenshotTest/TEST_MAPPING new file mode 100644 index 000000000000..727e609d91ac --- /dev/null +++ b/tests/InputScreenshotTest/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "InputScreenshotTests" + } + ] +} diff --git a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png Binary files differnew file mode 100644 index 000000000000..70e4a7101c7f --- /dev/null +++ b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png Binary files differnew file mode 100644 index 000000000000..502c1b4499d4 --- /dev/null +++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png Binary files differnew file mode 100644 index 000000000000..591b2fa9608e --- /dev/null +++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png Binary files differnew file mode 100644 index 000000000000..0137a853e538 --- /dev/null +++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png diff --git a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png Binary files differnew file mode 100644 index 000000000000..37a91e1fce53 --- /dev/null +++ b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/Bitmap.kt b/tests/InputScreenshotTest/src/android/input/screenshot/Bitmap.kt new file mode 100644 index 000000000000..84c971c750fb --- /dev/null +++ b/tests/InputScreenshotTest/src/android/input/screenshot/Bitmap.kt @@ -0,0 +1,66 @@ +/* + * 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 android.graphics.Bitmap +import android.graphics.Canvas +import android.os.Build +import android.view.View +import platform.test.screenshot.matchers.MSSIMMatcher +import platform.test.screenshot.matchers.PixelPerfectMatcher + +/** Draw this [View] into a [Bitmap]. */ +// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their +// tests. +fun View.drawIntoBitmap(): Bitmap { + val bitmap = + Bitmap.createBitmap( + measuredWidth, + measuredHeight, + Bitmap.Config.ARGB_8888, + ) + val canvas = Canvas(bitmap) + draw(canvas) + return bitmap +} + +/** + * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for + * screenshot *unit* tests. + */ +val UnitTestBitmapMatcher = + if (Build.CPU_ABI == "x86_64") { + // Different CPU architectures can sometimes end up rendering differently, so we can't do + // pixel-perfect matching on different architectures using the same golden. Given that our + // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the + // x86_64 architecture and use the Structural Similarity Index on others. + // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can + // do pixel perfect matching both at presubmit time and at development time with actual + // devices. + PixelPerfectMatcher() + } else { + MSSIMMatcher() + } + +/** + * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for + * screenshot *unit* tests. + * + * We use the Structural Similarity Index for integration tests because they usually contain + * additional information and noise that shouldn't break the test. + */ +val IntegrationTestBitmapMatcher = MSSIMMatcher()
\ No newline at end of file diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/DefaultDeviceEmulationSpec.kt b/tests/InputScreenshotTest/src/android/input/screenshot/DefaultDeviceEmulationSpec.kt new file mode 100644 index 000000000000..edddc6b41cf7 --- /dev/null +++ b/tests/InputScreenshotTest/src/android/input/screenshot/DefaultDeviceEmulationSpec.kt @@ -0,0 +1,75 @@ +/* + * 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 platform.test.screenshot.DeviceEmulationSpec +import platform.test.screenshot.DisplaySpec + +/** + * The emulations specs for all 8 permutations of: + * - phone or tablet. + * - dark of light mode. + * - portrait or landscape. + */ +val DeviceEmulationSpec.Companion.PhoneAndTabletFull + get() = PhoneAndTabletFullSpec + +private val PhoneAndTabletFullSpec = + DeviceEmulationSpec.forDisplays(Displays.Phone, Displays.Tablet) + +/** + * The emulations specs of: + * - phone + light mode + portrait. + * - phone + light mode + landscape. + * - tablet + dark mode + portrait. + * + * This allows to test the most important permutations of a screen/layout with only 3 + * configurations. + */ +val DeviceEmulationSpec.Companion.PhoneAndTabletMinimal + get() = PhoneAndTabletMinimalSpec + +private val PhoneAndTabletMinimalSpec = + DeviceEmulationSpec.forDisplays(Displays.Phone, isDarkTheme = false) + + DeviceEmulationSpec.forDisplays(Displays.Tablet, isDarkTheme = true, isLandscape = false) + +/** + * This allows to test only single most important configuration. + */ +val DeviceEmulationSpec.Companion.PhoneMinimal + get() = PhoneMinimalSpec + +private val PhoneMinimalSpec = + DeviceEmulationSpec.forDisplays(Displays.Phone, isDarkTheme = false, isLandscape = false) + +object Displays { + val Phone = + DisplaySpec( + "phone", + width = 1440, + height = 3120, + densityDpi = 560, + ) + + val Tablet = + DisplaySpec( + "tablet", + width = 2560, + height = 1600, + densityDpi = 320, + ) +}
\ No newline at end of file diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt new file mode 100644 index 000000000000..8faf22440828 --- /dev/null +++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt @@ -0,0 +1,44 @@ +/* + * 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/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt new file mode 100644 index 000000000000..c2c3d5530a00 --- /dev/null +++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt @@ -0,0 +1,87 @@ +/* + * 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 android.content.Context +import android.graphics.Bitmap +import androidx.activity.ComponentActivity +import androidx.compose.foundation.Image +import androidx.compose.ui.platform.ViewRootForTest +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.graphics.asImageBitmap +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import platform.test.screenshot.DeviceEmulationRule +import platform.test.screenshot.DeviceEmulationSpec +import platform.test.screenshot.MaterialYouColorsRule +import platform.test.screenshot.ScreenshotTestRule +import platform.test.screenshot.getEmulatedDevicePathConfig + +/** A rule for Input screenshot diff tests. */ +class InputScreenshotTestRule( + emulationSpec: DeviceEmulationSpec, + assetsPathRelativeToBuildRoot: String +) : TestRule { + private val colorsRule = MaterialYouColorsRule() + private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) + private val screenshotRule = + ScreenshotTestRule( + InputGoldenImagePathManager( + getEmulatedDevicePathConfig(emulationSpec), + assetsPathRelativeToBuildRoot + ) + ) + private val composeRule = createAndroidComposeRule<ComponentActivity>() + private val delegateRule = + RuleChain.outerRule(colorsRule) + .around(deviceEmulationRule) + .around(screenshotRule) + .around(composeRule) + private val matcher = UnitTestBitmapMatcher + + override fun apply(base: Statement, description: Description): Statement { + return delegateRule.apply(base, description) + } + + /** + * Compare [content] with the golden image identified by [goldenIdentifier]. + */ + fun screenshotTest( + goldenIdentifier: String, + content: (Context) -> Bitmap, + ) { + // Make sure that the activity draws full screen and fits the whole display. + val activity = composeRule.activity + activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) } + + // Set the content using the AndroidComposeRule to make sure that the Activity is set up + // correctly. + composeRule.setContent { + Image( + bitmap = content(activity).asImageBitmap(), + contentDescription = null, + ) + } + composeRule.waitForIdle() + + val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view + screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher) + } +}
\ No newline at end of file diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt new file mode 100644 index 000000000000..e85578663764 --- /dev/null +++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt @@ -0,0 +1,70 @@ +/* + * 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 android.content.Context +import android.hardware.input.KeyboardLayout +import android.os.LocaleList +import android.platform.test.flag.junit.SetFlagsRule +import com.android.hardware.input.Flags +import java.util.Locale +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import platform.test.screenshot.DeviceEmulationSpec + +/** A screenshot test for Keyboard layout preview for Ansi physical layout. */ +@RunWith(Parameterized::class) +class KeyboardLayoutPreviewAnsiScreenshotTest(emulationSpec: DeviceEmulationSpec) { + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getTestSpecs() = DeviceEmulationSpec.PhoneMinimal + } + + val setFlagsRule = SetFlagsRule() + val screenshotRule = InputScreenshotTestRule( + emulationSpec, + "frameworks/base/tests/InputScreenshotTest/assets" + ) + + @get:Rule + val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule) + + @Test + fun test() { + setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG) + screenshotRule.screenshotTest("layout-preview-ansi") { + context: Context -> LayoutPreview.createLayoutPreview( + context, + KeyboardLayout( + "descriptor", + "layout", + /* collection= */null, + /* priority= */0, + LocaleList(Locale.US), + /* layoutType= */0, + /* vid= */0, + /* pid= */0 + ) + ) + } + } + +}
\ No newline at end of file diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt new file mode 100644 index 000000000000..8ae6dfd8b63b --- /dev/null +++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt @@ -0,0 +1,58 @@ +/* + * 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 android.content.Context +import android.hardware.input.KeyboardLayout +import android.os.LocaleList +import android.platform.test.flag.junit.SetFlagsRule +import com.android.hardware.input.Flags +import java.util.Locale +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import platform.test.screenshot.DeviceEmulationSpec + +/** A screenshot test for Keyboard layout preview for Iso physical layout. */ +@RunWith(Parameterized::class) +class KeyboardLayoutPreviewIsoScreenshotTest(emulationSpec: DeviceEmulationSpec) { + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal + } + + val setFlagsRule = SetFlagsRule() + val screenshotRule = InputScreenshotTestRule( + emulationSpec, + "frameworks/base/tests/InputScreenshotTest/assets" + ) + + @get:Rule + val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule) + + @Test + fun test() { + setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG) + screenshotRule.screenshotTest("layout-preview") { + context: Context -> LayoutPreview.createLayoutPreview(context, null) + } + } + +}
\ No newline at end of file diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt new file mode 100644 index 000000000000..5231c14bfc9a --- /dev/null +++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt @@ -0,0 +1,70 @@ +/* + * 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 android.content.Context +import android.hardware.input.KeyboardLayout +import android.os.LocaleList +import android.platform.test.flag.junit.SetFlagsRule +import com.android.hardware.input.Flags +import java.util.Locale +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import platform.test.screenshot.DeviceEmulationSpec + +/** A screenshot test for Keyboard layout preview for JIS physical layout. */ +@RunWith(Parameterized::class) +class KeyboardLayoutPreviewJisScreenshotTest(emulationSpec: DeviceEmulationSpec) { + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getTestSpecs() = DeviceEmulationSpec.PhoneMinimal + } + + val setFlagsRule = SetFlagsRule() + val screenshotRule = InputScreenshotTestRule( + emulationSpec, + "frameworks/base/tests/InputScreenshotTest/assets" + ) + + @get:Rule + val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule) + + @Test + fun test() { + setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG) + screenshotRule.screenshotTest("layout-preview-jis") { + context: Context -> LayoutPreview.createLayoutPreview( + context, + KeyboardLayout( + "descriptor", + "layout", + /* collection= */null, + /* priority= */0, + LocaleList(Locale.JAPAN), + /* layoutType= */0, + /* vid= */0, + /* pid= */0 + ) + ) + } + } + +}
\ No newline at end of file diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/LayoutPreview.kt b/tests/InputScreenshotTest/src/android/input/screenshot/LayoutPreview.kt new file mode 100644 index 000000000000..76ee3791011b --- /dev/null +++ b/tests/InputScreenshotTest/src/android/input/screenshot/LayoutPreview.kt @@ -0,0 +1,39 @@ +/* + * 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. + */ + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.hardware.input.InputManager +import android.hardware.input.KeyboardLayout +import android.util.TypedValue +import kotlin.math.roundToInt + +object LayoutPreview { + fun createLayoutPreview(context: Context, layout: KeyboardLayout?): Bitmap { + val im = context.getSystemService(InputManager::class.java)!! + val width = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + 600.0F, context.getResources().getDisplayMetrics()).roundToInt() + val height = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + 200.0F, context.getResources().getDisplayMetrics()).roundToInt() + val drawable = im.getKeyboardLayoutPreview(layout, width, height)!! + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()) + drawable.draw(canvas) + return bitmap + } +}
\ No newline at end of file diff --git a/tools/aapt2/OWNERS b/tools/aapt2/OWNERS index 4f655e54a7c2..55bab7d8d74f 100644 --- a/tools/aapt2/OWNERS +++ b/tools/aapt2/OWNERS @@ -1,4 +1,4 @@ set noparent -toddke@google.com zyy@google.com -patb@google.com
\ No newline at end of file +patb@google.com +markpun@google.com diff --git a/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/phone/dark_landscape_credential_view_pin_or_password_emergency_call_button.png b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/phone/dark_landscape_credential_view_pin_or_password_emergency_call_button.png Binary files differnew file mode 100644 index 000000000000..6bd8595bc097 --- /dev/null +++ b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/phone/dark_landscape_credential_view_pin_or_password_emergency_call_button.png diff --git a/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/phone/dark_portrait_credential_view_pin_or_password_emergency_call_button.png b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/phone/dark_portrait_credential_view_pin_or_password_emergency_call_button.png Binary files differnew file mode 100644 index 000000000000..d5d6fb6c6ea4 --- /dev/null +++ b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/phone/dark_portrait_credential_view_pin_or_password_emergency_call_button.png diff --git a/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/phone/light_landscape_credential_view_pin_or_password_emergency_call_button.png b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/phone/light_landscape_credential_view_pin_or_password_emergency_call_button.png Binary files differnew file mode 100644 index 000000000000..fe8dc6906108 --- /dev/null +++ b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/phone/light_landscape_credential_view_pin_or_password_emergency_call_button.png diff --git a/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/phone/light_portrait_credential_view_pin_or_password_emergency_call_button.png b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/phone/light_portrait_credential_view_pin_or_password_emergency_call_button.png Binary files differnew file mode 100644 index 000000000000..ccd8a33ab4fd --- /dev/null +++ b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/phone/light_portrait_credential_view_pin_or_password_emergency_call_button.png diff --git a/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/tablet/dark_landscape_credential_view_pin_or_password_emergency_call_button.png b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/tablet/dark_landscape_credential_view_pin_or_password_emergency_call_button.png Binary files differnew file mode 100644 index 000000000000..de84c4aa9dae --- /dev/null +++ b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/tablet/dark_landscape_credential_view_pin_or_password_emergency_call_button.png diff --git a/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/tablet/dark_portrait_credential_view_pin_or_password_emergency_call_button.png b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/tablet/dark_portrait_credential_view_pin_or_password_emergency_call_button.png Binary files differnew file mode 100644 index 000000000000..af9d7cf74ace --- /dev/null +++ b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/tablet/dark_portrait_credential_view_pin_or_password_emergency_call_button.png diff --git a/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/tablet/light_landscape_credential_view_pin_or_password_emergency_call_button.png b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/tablet/light_landscape_credential_view_pin_or_password_emergency_call_button.png Binary files differnew file mode 100644 index 000000000000..33daab9c401d --- /dev/null +++ b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/tablet/light_landscape_credential_view_pin_or_password_emergency_call_button.png diff --git a/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/tablet/light_portrait_credential_view_pin_or_password_emergency_call_button.png b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/tablet/light_portrait_credential_view_pin_or_password_emergency_call_button.png Binary files differnew file mode 100644 index 000000000000..14a799c41757 --- /dev/null +++ b/vendor/unbundled_google/packages/SystemUIGoogle/tests/screenshotBiometrics/assets/tablet/light_portrait_credential_view_pin_or_password_emergency_call_button.png diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java index d41c0197addc..bc41829f16d2 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java @@ -23,9 +23,11 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.res.Resources; import android.net.wifi.sharedconnectivity.service.ISharedConnectivityCallback; @@ -35,6 +37,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.IInterface; import android.os.RemoteException; +import android.os.UserManager; import android.text.TextUtils; import android.util.Log; @@ -67,7 +70,7 @@ import java.util.concurrent.Executor; @SystemApi public class SharedConnectivityManager { private static final String TAG = SharedConnectivityManager.class.getSimpleName(); - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final class SharedConnectivityCallbackProxy extends ISharedConnectivityCallback.Stub { @@ -172,6 +175,7 @@ public class SharedConnectivityManager { private final String mServicePackageName; private final String mIntentAction; private ServiceConnection mServiceConnection; + private UserManager mUserManager; /** * Creates a new instance of {@link SharedConnectivityManager}. @@ -217,12 +221,14 @@ public class SharedConnectivityManager { mContext = context; mServicePackageName = servicePackageName; mIntentAction = serviceIntentAction; + mUserManager = context.getSystemService(UserManager.class); } private void bind() { mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { + if (DEBUG) Log.i(TAG, "onServiceConnected"); mService = ISharedConnectivityService.Stub.asInterface(service); synchronized (mProxyDataLock) { if (!mCallbackProxyCache.isEmpty()) { @@ -253,9 +259,45 @@ public class SharedConnectivityManager { } }; - mContext.bindService( + boolean result = mContext.bindService( new Intent().setPackage(mServicePackageName).setAction(mIntentAction), mServiceConnection, Context.BIND_AUTO_CREATE); + if (!result) { + if (DEBUG) Log.i(TAG, "bindService failed"); + mServiceConnection = null; + if (mUserManager != null && !mUserManager.isUserUnlocked()) { // In direct boot mode + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); + mContext.registerReceiver(mBroadcastReceiver, intentFilter); + } else { + synchronized (mProxyDataLock) { + if (!mCallbackProxyCache.isEmpty()) { + mCallbackProxyCache.keySet().forEach( + callback -> callback.onRegisterCallbackFailed( + new IllegalStateException( + "Failed to bind after user unlock"))); + mCallbackProxyCache.clear(); + } + } + } + } + } + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + context.unregisterReceiver(mBroadcastReceiver); + bind(); + } + }; + + /** + * @hide + */ + @TestApi + @NonNull + public BroadcastReceiver getBroadcastReceiver() { + return mBroadcastReceiver; } private void registerCallbackInternal(SharedConnectivityClientCallback callback, @@ -357,6 +399,13 @@ public class SharedConnectivityManager { return false; } + // Try to unregister the broadcast receiver to guard against memory leaks. + try { + mContext.unregisterReceiver(mBroadcastReceiver); + } catch (IllegalArgumentException e) { + // This is fine, it means the receiver was never registered or was already unregistered. + } + if (mService == null) { boolean shouldUnbind; synchronized (mProxyDataLock) { |