diff options
299 files changed, 8610 insertions, 2586 deletions
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java index d4e32396187d..b49bbc5fca89 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -18,6 +18,7 @@ package com.android.server.appsearch; import static android.app.appsearch.AppSearchResult.throwableToFailedResult; import static android.os.Process.INVALID_UID; +import android.Manifest; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.app.appsearch.AppSearchBatchResult; @@ -331,6 +332,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(callback); long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime(); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK; @@ -343,7 +345,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); List<AppSearchSchema> schemas = new ArrayList<>(schemaBundles.size()); @@ -422,6 +424,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); Objects.requireNonNull(callback); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -430,7 +433,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -457,6 +460,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); Objects.requireNonNull(callback); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -465,7 +469,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -495,6 +499,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(callback); long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime(); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK; @@ -507,7 +512,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); AppSearchBatchResult.Builder<String, Void> resultBuilder = @@ -584,6 +589,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(callback); long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime(); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK; @@ -596,7 +602,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); AppSearchBatchResult.Builder<String, Bundle> resultBuilder = @@ -668,6 +674,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(callback); long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime(); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK; @@ -680,7 +687,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); instance = mAppSearchUserInstanceManager.getUserInstance(targetUser); @@ -737,6 +744,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(callback); long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime(); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK; @@ -749,7 +757,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); instance = mAppSearchUserInstanceManager.getUserInstance(targetUser); @@ -805,6 +813,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); Objects.requireNonNull(callback); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -813,7 +822,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -837,6 +846,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(packageName); Objects.requireNonNull(userHandle); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -845,7 +855,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -874,6 +884,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); Objects.requireNonNull(callback); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -882,7 +893,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -929,6 +940,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); Objects.requireNonNull(callback); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -937,7 +949,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -994,6 +1006,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); Objects.requireNonNull(callback); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -1002,7 +1015,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -1044,6 +1057,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(callback); long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime(); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK; @@ -1056,7 +1070,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); AppSearchBatchResult.Builder<String, Void> resultBuilder = @@ -1133,6 +1147,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(callback); long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime(); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK; @@ -1145,7 +1160,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); instance = mAppSearchUserInstanceManager.getUserInstance(targetUser); @@ -1200,6 +1215,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); Objects.requireNonNull(callback); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -1208,7 +1224,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -1233,6 +1249,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime(); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK; @@ -1245,7 +1262,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); instance = mAppSearchUserInstanceManager.getUserInstance(targetUser); @@ -1288,6 +1305,7 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(callback); long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime(); + int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { @@ -1301,7 +1319,7 @@ public class AppSearchManagerService extends SystemService { // Obtain the user where the client wants to run the operations in. This should // end up being the same as userHandle, assuming it is not a special user and // the client is allowed to run operations in that user. - UserHandle targetUser = handleIncomingUser(userHandle, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingPid, callingUid); verifyUserUnlocked(targetUser); Context targetUserContext = mContext.createContextAsUser(targetUser, @@ -1389,12 +1407,22 @@ public class AppSearchManagerService extends SystemService { /** * Helper for dealing with incoming user arguments to system service calls. * + * <p>Takes care of checking permissions and if the target is special user, this method will + * simply throw. + * * @param targetUserHandle The user which the caller is requesting to execute as. + * @param callingPid The actual pid of the caller as determined by Binder. * @param callingUid The actual uid of the caller as determined by Binder. + * * @return the user handle that the call should run as. Will always be a concrete user. + * + * @throws IllegalArgumentException if the target user is a special user. + * @throws SecurityException if caller trying to interact across user without + * {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} */ @NonNull - private UserHandle handleIncomingUser(@NonNull UserHandle targetUserHandle, int callingUid) { + private UserHandle handleIncomingUser(@NonNull UserHandle targetUserHandle, int callingPid, + int callingUid) { UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid); if (callingUserHandle.equals(targetUserHandle)) { return targetUserHandle; @@ -1406,9 +1434,16 @@ public class AppSearchManagerService extends SystemService { "Call does not support special user " + targetUserHandle); } + if (mContext.checkPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, + callingPid, + callingUid) == PackageManager.PERMISSION_GRANTED) { + return targetUserHandle; + } throw new SecurityException( - "Requested user, " + targetUserHandle + ", is not the same as the calling user, " - + callingUserHandle + "."); + "Permission denied while calling from uid " + callingUid + + " with " + targetUserHandle + "; Requires permission: " + + Manifest.permission.INTERACT_ACROSS_USERS_FULL); } /** diff --git a/core/api/current.txt b/core/api/current.txt index d684e4b82bbe..ec8dce0837cd 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -20454,7 +20454,7 @@ package android.media { method public String getProperty(String); method public int getRingerMode(); method @Deprecated public int getRouting(int); - method @Nullable public android.media.Spatializer getSpatializer(); + method @NonNull public android.media.Spatializer getSpatializer(); method public int getStreamMaxVolume(int); method public int getStreamMinVolume(int); method public int getStreamVolume(int); @@ -23854,9 +23854,13 @@ package android.media { public class Spatializer { method public void addOnSpatializerStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerStateChangedListener); method public boolean canBeSpatialized(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioFormat); + method public int getImmersiveAudioLevel(); method public boolean isAvailable(); method public boolean isEnabled(); method public void removeOnSpatializerStateChangedListener(@NonNull android.media.Spatializer.OnSpatializerStateChangedListener); + field public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1; // 0x1 + field public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0; // 0x0 + field public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1; // 0xffffffff } public static interface Spatializer.OnSpatializerStateChangedListener { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0d3dd7cb886e..9c01bcada1ee 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -5375,9 +5375,33 @@ package android.media { public class Spatializer { method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addOnHeadTrackingModeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadTrackingModeChangedListener); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnHeadToSoundstagePoseUpdatedListener(); method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<android.media.AudioDeviceAttributes> getCompatibleAudioDevices(); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getDesiredHeadTrackingMode(); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getHeadTrackingMode(); + method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<java.lang.Integer> getSupportedHeadTrackingModes(); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void recenterHeadTracker(); method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeOnHeadTrackingModeChangedListener(@NonNull android.media.Spatializer.OnHeadTrackingModeChangedListener); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setDesiredHeadTrackingMode(int); method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setEnabled(boolean); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setGlobalTransform(@NonNull float[]); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnHeadToSoundstagePoseUpdatedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadToSoundstagePoseUpdatedListener); + field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_DISABLED = -1; // 0xffffffff + field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_OTHER = 0; // 0x0 + field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2; // 0x2 + field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_RELATIVE_WORLD = 1; // 0x1 + field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_UNSUPPORTED = -2; // 0xfffffffe + } + + public static interface Spatializer.OnHeadToSoundstagePoseUpdatedListener { + method public void onHeadToSoundstagePoseUpdated(@NonNull android.media.Spatializer, @NonNull float[]); + } + + public static interface Spatializer.OnHeadTrackingModeChangedListener { + method public void onDesiredHeadTrackingModeChanged(@NonNull android.media.Spatializer, int); + method public void onHeadTrackingModeChanged(@NonNull android.media.Spatializer, int); } } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index f7164cf3761e..2ecf088fb5d0 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -425,6 +425,7 @@ package android.app.admin { public class DevicePolicyManager { method public int checkProvisioningPreCondition(@Nullable String, @NonNull String); + method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void clearOrganizationId(); method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord(); method @Nullable public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException; method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs(); @@ -3295,6 +3296,7 @@ package android.window { method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect); method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int); method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken); + method @NonNull public android.window.WindowContainerTransaction setAdjacentTaskFragments(@NonNull android.os.IBinder, @Nullable android.os.IBinder, @Nullable android.window.WindowContainerTransaction.TaskFragmentAdjacentParams); method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect); method @NonNull public android.window.WindowContainerTransaction setBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect); method @NonNull public android.window.WindowContainerTransaction setBoundsChangeTransaction(@NonNull android.window.WindowContainerToken, @NonNull android.view.SurfaceControl.Transaction); @@ -3310,6 +3312,15 @@ package android.window { field @NonNull public static final android.os.Parcelable.Creator<android.window.WindowContainerTransaction> CREATOR; } + public static class WindowContainerTransaction.TaskFragmentAdjacentParams { + ctor public WindowContainerTransaction.TaskFragmentAdjacentParams(); + ctor public WindowContainerTransaction.TaskFragmentAdjacentParams(@NonNull android.os.Bundle); + method public void setShouldDelayPrimaryLastActivityRemoval(boolean); + method public void setShouldDelaySecondaryLastActivityRemoval(boolean); + method public boolean shouldDelayPrimaryLastActivityRemoval(); + method public boolean shouldDelaySecondaryLastActivityRemoval(); + } + public abstract class WindowContainerTransactionCallback { ctor public WindowContainerTransactionCallback(); method public abstract void onTransactionReady(int, @NonNull android.view.SurfaceControl.Transaction); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 643020a7dbd2..12025f98ada8 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1934,10 +1934,14 @@ public class Activity extends ContextThemeWrapper } /** - * Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or - * {@link #onPause}, for your activity to start interacting with the user. This is an indicator - * that the activity became active and ready to receive input. It is on top of an activity stack - * and visible to user. + * Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or {@link #onPause}. This + * is usually a hint for your activity to start interacting with the user, which is a good + * indicator that the activity became active and ready to receive input. This sometimes could + * also be a transit state toward another resting state. For instance, an activity may be + * relaunched to {@link #onPause} due to configuration changes and the activity was visible, + * but wasn’t the top-most activity of an activity task. {@link #onResume} is guaranteed to be + * called before {@link #onPause} in this case which honors the activity lifecycle policy and + * the activity eventually rests in {@link #onPause}. * * <p>On platform versions prior to {@link android.os.Build.VERSION_CODES#Q} this is also a good * place to try to open exclusive-access devices or to get access to singleton resources. diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java index 1f323c378093..ae3a9e6668ab 100644 --- a/core/java/android/app/DisabledWallpaperManager.java +++ b/core/java/android/app/DisabledWallpaperManager.java @@ -74,6 +74,11 @@ final class DisabledWallpaperManager extends WallpaperManager { return false; } + private static int unsupportedInt() { + if (DEBUG) Log.w(TAG, "unsupported method called; returning -1", new Exception()); + return -1; + } + @Override public Drawable getDrawable() { return unsupported(); @@ -189,12 +194,12 @@ final class DisabledWallpaperManager extends WallpaperManager { @Override public int getWallpaperId(int which) { - return unsupported(); + return unsupportedInt(); } @Override public int getWallpaperIdForUser(int which, int userId) { - return unsupported(); + return unsupportedInt(); } @Override @@ -209,7 +214,8 @@ final class DisabledWallpaperManager extends WallpaperManager { @Override public int setResource(int resid, int which) throws IOException { - return unsupported(); + unsupported(); + return 0; } @Override @@ -220,19 +226,22 @@ final class DisabledWallpaperManager extends WallpaperManager { @Override public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup) throws IOException { - return unsupported(); + unsupported(); + return 0; } @Override public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup, int which) throws IOException { - return unsupported(); + unsupported(); + return 0; } @Override public int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup, int which, int userId) throws IOException { - return unsupported(); + unsupported(); + return 0; } @Override @@ -243,13 +252,15 @@ final class DisabledWallpaperManager extends WallpaperManager { @Override public int setStream(InputStream bitmapData, Rect visibleCropHint, boolean allowBackup) throws IOException { - return unsupported(); + unsupported(); + return 0; } @Override public int setStream(InputStream bitmapData, Rect visibleCropHint, boolean allowBackup, int which) throws IOException { - return unsupported(); + unsupported(); + return 0; } @Override @@ -259,12 +270,12 @@ final class DisabledWallpaperManager extends WallpaperManager { @Override public int getDesiredMinimumWidth() { - return unsupported(); + return unsupportedInt(); } @Override public int getDesiredMinimumHeight() { - return unsupported(); + return unsupportedInt(); } @Override diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 77bcef3ae009..be702c2a1756 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -46,7 +46,7 @@ import java.lang.annotation.RetentionPolicy; */ @SystemService(Context.STATUS_BAR_SERVICE) public class StatusBarManager { - + // LINT.IfChange /** @hide */ public static final int DISABLE_EXPAND = View.STATUS_BAR_DISABLE_EXPAND; /** @hide */ @@ -144,6 +144,7 @@ public class StatusBarManager { }) @Retention(RetentionPolicy.SOURCE) public @interface Disable2Flags {} + // LINT.ThenChange(frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt) /** * Default disable flags for setup diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 123046edd577..b570ae60ee91 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -562,7 +562,7 @@ public class WallpaperManager { } if (returnDefault) { Bitmap defaultWallpaper = mDefaultWallpaper; - if (defaultWallpaper == null) { + if (defaultWallpaper == null || defaultWallpaper.isRecycled()) { defaultWallpaper = getDefaultWallpaper(context, which); synchronized (this) { mDefaultWallpaper = defaultWallpaper; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 0e04ad3768c7..12444ab9ab09 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -13740,6 +13740,23 @@ public class DevicePolicyManager { } /** + * Clears organization ID set by the DPC and resets the precomputed enrollment specific ID. + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public void clearOrganizationId() { + if (mService == null) { + return; + } + try { + mService.clearOrganizationIdForUser(myUserId()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Creates and provisions a managed profile and sets the * {@link ManagedProfileProvisioningParams#getProfileAdminComponentName()} as the profile * owner. diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index b6c48a1c057b..b1364b500d4a 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -377,6 +377,7 @@ interface IDevicePolicyManager { void setOrganizationColor(in ComponentName admin, in int color); void setOrganizationColorForUser(in int color, in int userId); + void clearOrganizationIdForUser(int userHandle); int getOrganizationColor(in ComponentName admin); int getOrganizationColorForUser(int userHandle); diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java index 063ba1174cdc..2e94dd1a47c4 100644 --- a/core/java/android/appwidget/AppWidgetProviderInfo.java +++ b/core/java/android/appwidget/AppWidgetProviderInfo.java @@ -143,7 +143,7 @@ public class AppWidgetProviderInfo implements Parcelable { public ComponentName provider; /** - * The default height of the widget when added to a host, in dp. The widget will get + * The default height of the widget when added to a host, in px. The widget will get * at least this width, and will often be given more, depending on the host. * * <p>This field corresponds to the <code>android:minWidth</code> attribute in @@ -152,7 +152,7 @@ public class AppWidgetProviderInfo implements Parcelable { public int minWidth; /** - * The default height of the widget when added to a host, in dp. The widget will get + * The default height of the widget when added to a host, in px. The widget will get * at least this height, and will often be given more, depending on the host. * * <p>This field corresponds to the <code>android:minHeight</code> attribute in @@ -161,7 +161,7 @@ public class AppWidgetProviderInfo implements Parcelable { public int minHeight; /** - * Minimum width (in dp) which the widget can be resized to. This field has no effect if it + * Minimum width (in px) which the widget can be resized to. This field has no effect if it * is greater than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}). * * <p>This field corresponds to the <code>android:minResizeWidth</code> attribute in @@ -170,7 +170,7 @@ public class AppWidgetProviderInfo implements Parcelable { public int minResizeWidth; /** - * Minimum height (in dp) which the widget can be resized to. This field has no effect if it + * Minimum height (in px) which the widget can be resized to. This field has no effect if it * is greater than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}). * * <p>This field corresponds to the <code>android:minResizeHeight</code> attribute in @@ -179,7 +179,7 @@ public class AppWidgetProviderInfo implements Parcelable { public int minResizeHeight; /** - * Maximum width (in dp) which the widget can be resized to. This field has no effect if it is + * Maximum width (in px) which the widget can be resized to. This field has no effect if it is * smaller than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}). * * <p>This field corresponds to the <code>android:maxResizeWidth</code> attribute in the @@ -189,7 +189,7 @@ public class AppWidgetProviderInfo implements Parcelable { public int maxResizeWidth; /** - * Maximum height (in dp) which the widget can be resized to. This field has no effect if it is + * Maximum height (in px) which the widget can be resized to. This field has no effect if it is * smaller than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}). * * <p>This field corresponds to the <code>android:maxResizeHeight</code> attribute in the diff --git a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java new file mode 100644 index 000000000000..603b06ddabaa --- /dev/null +++ b/core/java/android/hardware/biometrics/BiometricOverlayConstants.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.biometrics; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Common constants for biometric overlays. + * @hide + */ +public interface BiometricOverlayConstants { + /** Unknown usage. */ + int REASON_UNKNOWN = 0; + /** User is about to enroll. */ + int REASON_ENROLL_FIND_SENSOR = 1; + /** User is enrolling. */ + int REASON_ENROLL_ENROLLING = 2; + /** Usage from BiometricPrompt. */ + int REASON_AUTH_BP = 3; + /** Usage from Keyguard. */ + int REASON_AUTH_KEYGUARD = 4; + /** Non-specific usage (from FingerprintManager). */ + int REASON_AUTH_OTHER = 5; + + @IntDef({REASON_UNKNOWN, + REASON_ENROLL_FIND_SENSOR, + REASON_ENROLL_ENROLLING, + REASON_AUTH_BP, + REASON_AUTH_KEYGUARD, + REASON_AUTH_OTHER}) + @Retention(RetentionPolicy.SOURCE) + @interface ShowReason {} +} diff --git a/core/java/android/hardware/biometrics/SensorLocationInternal.aidl b/core/java/android/hardware/biometrics/SensorLocationInternal.aidl new file mode 100644 index 000000000000..098190449d53 --- /dev/null +++ b/core/java/android/hardware/biometrics/SensorLocationInternal.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.biometrics; + +// @hide +parcelable SensorLocationInternal; diff --git a/core/java/android/hardware/biometrics/SensorLocationInternal.java b/core/java/android/hardware/biometrics/SensorLocationInternal.java new file mode 100644 index 000000000000..fb25a2fcd823 --- /dev/null +++ b/core/java/android/hardware/biometrics/SensorLocationInternal.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.biometrics; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * The location of a sensor relative to a physical display. + * + * Note that the location may change depending on other attributes of the device, such as + * fold status, which are not yet included in this class. + * @hide + */ +public class SensorLocationInternal implements Parcelable { + + /** Default value to use when the sensor's location is unknown or undefined. */ + public static final SensorLocationInternal DEFAULT = new SensorLocationInternal("", 0, 0, 0); + + /** + * The stable display id. + */ + @NonNull + public final String displayId; + + /** + * The location of the center of the sensor if applicable. For example, sensors of type + * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the + * distance in pixels, measured from the left edge of the screen. + */ + public final int sensorLocationX; + + /** + * The location of the center of the sensor if applicable. For example, sensors of type + * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the + * distance in pixels, measured from the top edge of the screen. + * + */ + public final int sensorLocationY; + + /** + * The radius of the sensor if applicable. For example, sensors of type + * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the radius + * of the sensor, in pixels. + */ + public final int sensorRadius; + + public SensorLocationInternal(@Nullable String displayId, + int sensorLocationX, int sensorLocationY, int sensorRadius) { + this.displayId = displayId != null ? displayId : ""; + this.sensorLocationX = sensorLocationX; + this.sensorLocationY = sensorLocationY; + this.sensorRadius = sensorRadius; + } + + protected SensorLocationInternal(Parcel in) { + displayId = in.readString16NoHelper(); + sensorLocationX = in.readInt(); + sensorLocationY = in.readInt(); + sensorRadius = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(displayId); + dest.writeInt(sensorLocationX); + dest.writeInt(sensorLocationY); + dest.writeInt(sensorRadius); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<SensorLocationInternal> CREATOR = + new Creator<SensorLocationInternal>() { + @Override + public SensorLocationInternal createFromParcel(Parcel in) { + return new SensorLocationInternal(in); + } + + @Override + public SensorLocationInternal[] newArray(int size) { + return new SensorLocationInternal[size]; + } + }; + + @Override + public String toString() { + return "[id: " + displayId + + ", x: " + sensorLocationX + + ", y: " + sensorLocationY + + ", r: " + sensorRadius + "]"; + } +} diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index fc728a22ed5a..4708f3e0664f 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -104,6 +104,9 @@ public class CameraDeviceImpl extends CameraDevice private SparseArray<CaptureCallbackHolder> mCaptureCallbackMap = new SparseArray<CaptureCallbackHolder>(); + /** map request IDs which have batchedOutputs to requestCount*/ + private HashMap<Integer, Integer> mBatchOutputMap = new HashMap<>(); + private int mRepeatingRequestId = REQUEST_ID_NONE; // Latest repeating request list's types private int[] mRepeatingRequestTypes; @@ -973,6 +976,7 @@ public class CameraDeviceImpl extends CameraDevice mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(REQUEST_ID_NONE, null); mIdle = true; mCaptureCallbackMap = new SparseArray<CaptureCallbackHolder>(); + mBatchOutputMap = new HashMap<>(); mFrameNumberTracker = new FrameNumberTracker(); mCurrentSession.closeWithoutDraining(); @@ -1179,6 +1183,41 @@ public class CameraDeviceImpl extends CameraDevice return requestTypes; } + private boolean hasBatchedOutputs(List<CaptureRequest> requestList) { + boolean hasBatchedOutputs = true; + for (int i = 0; i < requestList.size(); i++) { + CaptureRequest request = requestList.get(i); + if (!request.isPartOfCRequestList()) { + hasBatchedOutputs = false; + break; + } + if (i == 0) { + Collection<Surface> targets = request.getTargets(); + if (targets.size() != 2) { + hasBatchedOutputs = false; + break; + } + } + } + return hasBatchedOutputs; + } + + private void updateTracker(int requestId, long frameNumber, + int requestType, CaptureResult result, boolean isPartialResult) { + int requestCount = 1; + // If the request has batchedOutputs update each frame within the batch. + if (mBatchOutputMap.containsKey(requestId)) { + requestCount = mBatchOutputMap.get(requestId); + for (int i = 0; i < requestCount; i++) { + mFrameNumberTracker.updateTracker(frameNumber - (requestCount - 1 - i), + result, isPartialResult, requestType); + } + } else { + mFrameNumberTracker.updateTracker(frameNumber, result, + isPartialResult, requestType); + } + } + private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback, Executor executor, boolean repeating) throws CameraAccessException { @@ -1224,6 +1263,14 @@ public class CameraDeviceImpl extends CameraDevice request.recoverStreamIdToSurface(); } + // If the request has batched outputs, then store the + // requestCount and requestId in the map. + boolean hasBatchedOutputs = hasBatchedOutputs(requestList); + if (hasBatchedOutputs) { + int requestCount = requestList.size(); + mBatchOutputMap.put(requestInfo.getRequestId(), requestCount); + } + if (callback != null) { mCaptureCallbackMap.put(requestInfo.getRequestId(), new CaptureCallbackHolder( @@ -1839,8 +1886,18 @@ public class CameraDeviceImpl extends CameraDevice if (DEBUG) { Log.v(TAG, String.format("got error frame %d", frameNumber)); } - mFrameNumberTracker.updateTracker(frameNumber, - /*error*/true, request.getRequestType()); + + // Update FrameNumberTracker for every frame during HFR mode. + if (mBatchOutputMap.containsKey(requestId)) { + for (int i = 0; i < mBatchOutputMap.get(requestId); i++) { + mFrameNumberTracker.updateTracker(frameNumber - (subsequenceId - i), + /*error*/true, request.getRequestType()); + } + } else { + mFrameNumberTracker.updateTracker(frameNumber, + /*error*/true, request.getRequestType()); + } + checkAndFireSequenceComplete(); // Dispatch the failure callback @@ -2023,7 +2080,6 @@ public class CameraDeviceImpl extends CameraDevice public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[]) throws RemoteException { - int requestId = resultExtras.getRequestId(); long frameNumber = resultExtras.getFrameNumber(); @@ -2064,8 +2120,8 @@ public class CameraDeviceImpl extends CameraDevice + frameNumber); } - mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult, - requestType); + updateTracker(requestId, frameNumber, requestType, /*result*/null, + isPartialResult); return; } @@ -2077,8 +2133,9 @@ public class CameraDeviceImpl extends CameraDevice + frameNumber); } - mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult, - requestType); + updateTracker(requestId, frameNumber, requestType, /*result*/null, + isPartialResult); + return; } @@ -2184,9 +2241,7 @@ public class CameraDeviceImpl extends CameraDevice Binder.restoreCallingIdentity(ident); } - // Collect the partials for a total result; or mark the frame as totally completed - mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult, - requestType); + updateTracker(requestId, frameNumber, requestType, finalResult, isPartialResult); // Fire onCaptureSequenceCompleted if (!isPartialResult) { diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerInternal.java b/core/java/android/hardware/devicestate/DeviceStateManagerInternal.java new file mode 100644 index 000000000000..4c91c160f891 --- /dev/null +++ b/core/java/android/hardware/devicestate/DeviceStateManagerInternal.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.devicestate; + +/** + * Device state manager local system service interface. + * + * @hide Only for use within the system server. + */ +public abstract class DeviceStateManagerInternal { + + /** Returns the list of currently supported device state identifiers. */ + public abstract int[] getSupportedStateIdentifiers(); +} diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 4f205530ef0d..5bb51c19342d 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -35,6 +35,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Display manager local system service interface. @@ -129,6 +130,14 @@ public abstract class DisplayManagerInternal { public abstract DisplayInfo getDisplayInfo(int displayId); /** + * Returns a set of DisplayInfo, for the states that may be assumed by either the given display, + * or any other display within that display's group. + * + * @param displayId The logical display id to fetch DisplayInfo for. + */ + public abstract Set<DisplayInfo> getPossibleDisplayInfo(int displayId); + + /** * Returns the position of the display's projection. * * @param displayId The logical display id. diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java index 45c6b290be11..4bf9a740f971 100644 --- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java +++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java @@ -21,7 +21,9 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFP import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC; import android.annotation.NonNull; +import android.annotation.Nullable; import android.hardware.biometrics.ComponentInfoInternal; +import android.hardware.biometrics.SensorLocationInternal; import android.hardware.biometrics.SensorProperties; import android.hardware.biometrics.SensorPropertiesInternal; import android.os.Parcel; @@ -38,34 +40,14 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna */ public final @FingerprintSensorProperties.SensorType int sensorType; - /** - * The location of the center of the sensor if applicable. For example, sensors of type - * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the - * distance in pixels, measured from the left edge of the screen. - */ - public final int sensorLocationX; - - /** - * The location of the center of the sensor if applicable. For example, sensors of type - * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the - * distance in pixels, measured from the top edge of the screen. - * - */ - public final int sensorLocationY; - - /** - * The radius of the sensor if applicable. For example, sensors of type - * {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the radius - * of the sensor, in pixels. - */ - public final int sensorRadius; + private final List<SensorLocationInternal> mSensorLocations; public FingerprintSensorPropertiesInternal(int sensorId, @SensorProperties.Strength int strength, int maxEnrollmentsPerUser, @NonNull List<ComponentInfoInternal> componentInfo, @FingerprintSensorProperties.SensorType int sensorType, - boolean resetLockoutRequiresHardwareAuthToken, int sensorLocationX, int sensorLocationY, - int sensorRadius) { + boolean resetLockoutRequiresHardwareAuthToken, + @NonNull List<SensorLocationInternal> sensorLocations) { // IBiometricsFingerprint@2.1 handles lockout in the framework, so the challenge is not // required as it can only be generated/attested/verified by TEE components. // IFingerprint@1.0 handles lockout below the HAL, but does not require a challenge. See @@ -73,9 +55,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna super(sensorId, strength, maxEnrollmentsPerUser, componentInfo, resetLockoutRequiresHardwareAuthToken, false /* resetLockoutRequiresChallenge */); this.sensorType = sensorType; - this.sensorLocationX = sensorLocationX; - this.sensorLocationY = sensorLocationY; - this.sensorRadius = sensorRadius; + this.mSensorLocations = List.copyOf(sensorLocations); } /** @@ -88,16 +68,15 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna boolean resetLockoutRequiresHardwareAuthToken) { // TODO(b/179175438): Value should be provided from the HAL this(sensorId, strength, maxEnrollmentsPerUser, componentInfo, sensorType, - resetLockoutRequiresHardwareAuthToken, 540 /* sensorLocationX */, - 1636 /* sensorLocationY */, 130 /* sensorRadius */); + resetLockoutRequiresHardwareAuthToken, List.of(new SensorLocationInternal( + "" /* displayId */, 540 /* sensorLocationX */, 1636 /* sensorLocationY */, + 130 /* sensorRadius */))); } protected FingerprintSensorPropertiesInternal(Parcel in) { super(in); sensorType = in.readInt(); - sensorLocationX = in.readInt(); - sensorLocationY = in.readInt(); - sensorRadius = in.readInt(); + mSensorLocations = in.createTypedArrayList(SensorLocationInternal.CREATOR); } public static final Creator<FingerprintSensorPropertiesInternal> CREATOR = @@ -122,9 +101,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeInt(sensorType); - dest.writeInt(sensorLocationX); - dest.writeInt(sensorLocationY); - dest.writeInt(sensorRadius); + dest.writeTypedList(mSensorLocations); } public boolean isAnyUdfpsType() { @@ -150,6 +127,44 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna } } + /** + * Get the default location. + * + * Use this method when the sensor's relationship to the displays on the device do not + * matter. + * @return + */ + @NonNull + public SensorLocationInternal getLocation() { + final SensorLocationInternal location = getLocation("" /* displayId */); + return location != null ? location : SensorLocationInternal.DEFAULT; + } + + /** + * Get the location of a sensor relative to a physical display layout. + * + * @param displayId stable display id + * @return location or null if none is specified + */ + @Nullable + public SensorLocationInternal getLocation(String displayId) { + for (SensorLocationInternal location : mSensorLocations) { + if (location.displayId.equals(displayId)) { + return location; + } + } + return null; + } + + /** + * Gets all locations relative to all supported display layouts. + * @return supported locations + */ + @NonNull + public List<SensorLocationInternal> getAllLocations() { + return mSensorLocations; + } + @Override public String toString() { return "ID: " + sensorId + ", Strength: " + sensorStrength + ", Type: " + sensorType; diff --git a/core/java/android/hardware/fingerprint/ISidefpsController.aidl b/core/java/android/hardware/fingerprint/ISidefpsController.aidl index 00f40048cbae..684f18f3fd94 100644 --- a/core/java/android/hardware/fingerprint/ISidefpsController.aidl +++ b/core/java/android/hardware/fingerprint/ISidefpsController.aidl @@ -21,9 +21,9 @@ package android.hardware.fingerprint; */ oneway interface ISidefpsController { - // Shows the overlay. - void show(); + // Shows the overlay for the given sensor with a reason from BiometricOverlayConstants. + void show(int sensorId, int reason); // Hides the overlay. - void hide(); + void hide(int sensorId); } diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl index f18360ff4108..648edda62171 100644 --- a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl +++ b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl @@ -22,14 +22,7 @@ import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; * @hide */ oneway interface IUdfpsOverlayController { - const int REASON_UNKNOWN = 0; - const int REASON_ENROLL_FIND_SENSOR = 1; - const int REASON_ENROLL_ENROLLING = 2; - const int REASON_AUTH_BP = 3; // BiometricPrompt - const int REASON_AUTH_FPM_KEYGUARD = 4; // FingerprintManager usage from Keyguard - const int REASON_AUTH_FPM_OTHER = 5; // Other FingerprintManager usage - - // Shows the overlay. + // Shows the overlay for the given sensor with a reason from BiometricOverlayConstants. void showUdfpsOverlay(int sensorId, int reason, IUdfpsOverlayControllerCallback callback); // Hides the overlay. diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index e7e53b39982a..e58419fb688d 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -258,6 +258,13 @@ public final class DeviceConfig { public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler"; /** + * Namespace for all lmkd related features. + * + * @hide + */ + public static final String NAMESPACE_LMKD_NATIVE = "lmkd_native"; + + /** * Namespace for all location related features. * * @hide diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java index 8e4a68e52697..40041486f6a6 100644 --- a/core/java/android/service/notification/StatusBarNotification.java +++ b/core/java/android/service/notification/StatusBarNotification.java @@ -436,7 +436,7 @@ public class StatusBarNotification implements Parcelable { try { ApplicationInfo ai = context.getPackageManager() .getApplicationInfoAsUser(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES, - getUserId()); + getNormalizedUserId()); mContext = context.createApplicationContext(ai, Context.CONTEXT_RESTRICTED); } catch (PackageManager.NameNotFoundException e) { diff --git a/core/java/android/view/InsetsVisibilities.java b/core/java/android/view/InsetsVisibilities.java index 30668bad3424..7d259fb91634 100644 --- a/core/java/android/view/InsetsVisibilities.java +++ b/core/java/android/view/InsetsVisibilities.java @@ -116,6 +116,10 @@ public class InsetsVisibilities implements Parcelable { dest.writeIntArray(mVisibilities); } + public void readFromParcel(@NonNull Parcel in) { + in.readIntArray(mVisibilities); + } + public static final @NonNull Creator<InsetsVisibilities> CREATOR = new Creator<InsetsVisibilities>() { diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 0fc6b08ae02b..7631269d9c1c 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -374,8 +374,8 @@ public final class WindowManagerImpl implements WindowManager { currentDisplayInfo = possibleDisplayInfos.get(i); // Calculate max bounds for this rotation and state. - Rect maxBounds = new Rect(0, 0, currentDisplayInfo.getNaturalWidth(), - currentDisplayInfo.getNaturalHeight()); + Rect maxBounds = new Rect(0, 0, currentDisplayInfo.logicalWidth, + currentDisplayInfo.logicalHeight); // Calculate insets for the rotated max bounds. // TODO(181127261) calculate insets for each display rotation and state. diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl index 8b8dba89ea67..69bc1b5f7763 100644 --- a/core/java/android/window/ITaskOrganizer.aidl +++ b/core/java/android/window/ITaskOrganizer.aidl @@ -88,4 +88,9 @@ oneway interface ITaskOrganizer { * user has pressed back on the root activity of a task controlled by the task organizer. */ void onBackPressedOnTaskRoot(in ActivityManager.RunningTaskInfo taskInfo); + + /** + * Called when the IME has drawn on the organized task. + */ + void onImeDrawnOnTask(int taskId); } diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java index 8c64474dc887..10d21a0ff003 100644 --- a/core/java/android/window/StartingWindowInfo.java +++ b/core/java/android/window/StartingWindowInfo.java @@ -26,6 +26,7 @@ import android.content.pm.ActivityInfo; import android.os.Parcel; import android.os.Parcelable; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.WindowManager; /** @@ -165,7 +166,13 @@ public final class StartingWindowInfo implements Parcelable { * TaskSnapshot. * @hide */ - public TaskSnapshot mTaskSnapshot; + public TaskSnapshot taskSnapshot; + + /** + * The requested insets visibility of the top main window. + * @hide + */ + public final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); public StartingWindowInfo() { @@ -190,7 +197,8 @@ public final class StartingWindowInfo implements Parcelable { dest.writeTypedObject(mainWindowLayoutParams, flags); dest.writeInt(splashScreenThemeResId); dest.writeBoolean(isKeyguardOccluded); - dest.writeTypedObject(mTaskSnapshot, flags); + dest.writeTypedObject(taskSnapshot, flags); + requestedVisibilities.writeToParcel(dest, flags); } void readFromParcel(@NonNull Parcel source) { @@ -203,7 +211,8 @@ public final class StartingWindowInfo implements Parcelable { mainWindowLayoutParams = source.readTypedObject(WindowManager.LayoutParams.CREATOR); splashScreenThemeResId = source.readInt(); isKeyguardOccluded = source.readBoolean(); - mTaskSnapshot = source.readTypedObject(TaskSnapshot.CREATOR); + taskSnapshot = source.readTypedObject(TaskSnapshot.CREATOR); + requestedVisibilities.readFromParcel(source); } @Override diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index 6f250fc0ce7a..845c13d9a01b 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -144,6 +144,10 @@ public class TaskOrganizer extends WindowOrganizer { @BinderThread public void onBackPressedOnTaskRoot(@NonNull ActivityManager.RunningTaskInfo taskInfo) {} + /** @hide */ + @BinderThread + public void onImeDrawnOnTask(int taskId) {} + /** * Creates a persistent root task in WM for a particular windowing-mode. * @param displayId The display to create the root task on. @@ -288,6 +292,11 @@ public class TaskOrganizer extends WindowOrganizer { public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo info) { mExecutor.execute(() -> TaskOrganizer.this.onBackPressedOnTaskRoot(info)); } + + @Override + public void onImeDrawnOnTask(int taskId) { + mExecutor.execute(() -> TaskOrganizer.this.onImeDrawnOnTask(taskId)); + } }; @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 387837d82acf..9d6488d7aa14 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -512,17 +512,16 @@ public final class WindowContainerTransaction implements Parcelable { * @param fragmentToken2 client assigned unique token to create TaskFragment with specified * in {@link TaskFragmentCreationParams#getFragmentToken()}. If it is * {@code null}, the transaction will reset the adjacent TaskFragment. - * @hide */ @NonNull public WindowContainerTransaction setAdjacentTaskFragments( @NonNull IBinder fragmentToken1, @Nullable IBinder fragmentToken2, - @Nullable TaskFragmentAdjacentOptions options) { + @Nullable TaskFragmentAdjacentParams params) { final HierarchyOp hierarchyOp = new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS) .setContainer(fragmentToken1) .setReparentContainer(fragmentToken2) - .setLaunchOptions(options != null ? options.toBundle() : null) + .setLaunchOptions(params != null ? params.toBundle() : null) .build(); mHierarchyOps.add(hierarchyOp); return this; @@ -1304,9 +1303,8 @@ public final class WindowContainerTransaction implements Parcelable { /** * Helper class for building an options Bundle that can be used to set adjacent rules of * TaskFragments. - * @hide */ - public static class TaskFragmentAdjacentOptions { + public static class TaskFragmentAdjacentParams { private static final String DELAY_PRIMARY_LAST_ACTIVITY_REMOVAL = "android:transaction.adjacent.option.delay_primary_removal"; private static final String DELAY_SECONDARY_LAST_ACTIVITY_REMOVAL = @@ -1315,29 +1313,43 @@ public final class WindowContainerTransaction implements Parcelable { private boolean mDelayPrimaryLastActivityRemoval; private boolean mDelaySecondaryLastActivityRemoval; - public TaskFragmentAdjacentOptions() { + public TaskFragmentAdjacentParams() { } - public TaskFragmentAdjacentOptions(@NonNull Bundle bundle) { + public TaskFragmentAdjacentParams(@NonNull Bundle bundle) { mDelayPrimaryLastActivityRemoval = bundle.getBoolean( DELAY_PRIMARY_LAST_ACTIVITY_REMOVAL); mDelaySecondaryLastActivityRemoval = bundle.getBoolean( DELAY_SECONDARY_LAST_ACTIVITY_REMOVAL); } - public void setDelayPrimaryLastActivityRemoval(boolean delay) { + /** @see #shouldDelayPrimaryLastActivityRemoval() */ + public void setShouldDelayPrimaryLastActivityRemoval(boolean delay) { mDelayPrimaryLastActivityRemoval = delay; } - public void setDelaySecondaryLastActivityRemoval(boolean delay) { + /** @see #shouldDelaySecondaryLastActivityRemoval() */ + public void setShouldDelaySecondaryLastActivityRemoval(boolean delay) { mDelaySecondaryLastActivityRemoval = delay; } - public boolean isDelayPrimaryLastActivityRemoval() { + /** + * Whether to delay the last activity of the primary adjacent TaskFragment being immediately + * removed while finishing. + * <p> + * It is usually set to {@code true} to give organizer an opportunity to perform other + * actions or animations. An example is to finish together with the adjacent TaskFragment. + * </p> + */ + public boolean shouldDelayPrimaryLastActivityRemoval() { return mDelayPrimaryLastActivityRemoval; } - public boolean isDelaySecondaryLastActivityRemoval() { + /** + * Similar to {@link #shouldDelayPrimaryLastActivityRemoval()}, but for the secondary + * TaskFragment. + */ + public boolean shouldDelaySecondaryLastActivityRemoval() { return mDelaySecondaryLastActivityRemoval; } diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java index 0b92b93f2c88..874e3f4ae26a 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java @@ -47,6 +47,8 @@ import java.util.List; public class AccessibilityShortcutChooserActivity extends Activity { @ShortcutType private final int mShortcutType = ACCESSIBILITY_SHORTCUT_KEY; + private static final String KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE = + "accessibility_shortcut_menu_mode"; private final List<AccessibilityTarget> mTargets = new ArrayList<>(); private AlertDialog mMenuDialog; private AlertDialog mPermissionDialog; @@ -66,14 +68,30 @@ public class AccessibilityShortcutChooserActivity extends Activity { mMenuDialog = createMenuDialog(); mMenuDialog.setOnShowListener(dialog -> updateDialogListeners()); mMenuDialog.show(); + + if (savedInstanceState != null) { + final int restoreShortcutMenuMode = + savedInstanceState.getInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE, + ShortcutMenuMode.LAUNCH); + if (restoreShortcutMenuMode == ShortcutMenuMode.EDIT) { + onEditButtonClicked(); + } + } } @Override protected void onDestroy() { + mMenuDialog.setOnDismissListener(null); mMenuDialog.dismiss(); super.onDestroy(); } + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE, mTargetAdapter.getShortcutMenuMode()); + } + private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) { final AccessibilityTarget target = mTargets.get(position); target.onSelected(); diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java index ce75f45d0897..068b882eb4f7 100644 --- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java +++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java @@ -165,7 +165,7 @@ public final class SelectableTargetInfo implements ChooserTargetInfo { // Now fetch app icon and raster with no badging even in work profile Bitmap appIcon = mSelectableTargetInfoCommunicator.makePresentationGetter(info) - .getIconBitmap(android.os.Process.myUserHandle()); + .getIconBitmap(mContext.getUser()); // Raster target drawable with appIcon as a badge SimpleIconFactory sif = SimpleIconFactory.obtain(mContext); diff --git a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java index 4ce6f609ef73..3eb980465214 100644 --- a/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java +++ b/core/java/com/android/internal/notification/NotificationAccessConfirmationActivityContract.java @@ -17,19 +17,27 @@ package com.android.internal.notification; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; +import com.android.internal.R; + +/** + * This class provides methods to create intents for NotificationAccessConfirmationActivity. + */ public final class NotificationAccessConfirmationActivityContract { - private static final ComponentName COMPONENT_NAME = new ComponentName( - "com.android.settings", - "com.android.settings.notification.NotificationAccessConfirmationActivity"); public static final String EXTRA_USER_ID = "user_id"; public static final String EXTRA_COMPONENT_NAME = "component_name"; public static final String EXTRA_PACKAGE_TITLE = "package_title"; - public static Intent launcherIntent(int userId, ComponentName component, String packageTitle) { + /** + * Creates a launcher intent for NotificationAccessConfirmationActivity. + */ + public static Intent launcherIntent(Context context, int userId, ComponentName component, + String packageTitle) { return new Intent() - .setComponent(COMPONENT_NAME) + .setComponent(ComponentName.unflattenFromString(context.getString( + R.string.config_notificationAccessConfirmationActivity))) .putExtra(EXTRA_USER_ID, userId) .putExtra(EXTRA_COMPONENT_NAME, component) .putExtra(EXTRA_PACKAGE_TITLE, packageTitle); diff --git a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java index b321ac08912b..a09c8236b47d 100644 --- a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java +++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java @@ -124,7 +124,6 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { - sendCloseSystemWindows(); mContext.startActivity(intent); } catch (ActivityNotFoundException e) { startCallActivity(); @@ -147,7 +146,6 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { dispatcher.performedLongPress(event); if (isUserSetupComplete()) { mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - sendCloseSystemWindows(); // Broadcast an intent that the Camera button was longpressed Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); @@ -178,7 +176,6 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - sendCloseSystemWindows(); getSearchManager().stopSearch(); mContext.startActivity(intent); // Only clear this if we successfully start the @@ -272,7 +269,6 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { @UnsupportedAppUsage void startCallActivity() { - sendCloseSystemWindows(); Intent intent = new Intent(Intent.ACTION_CALL_BUTTON); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { @@ -319,10 +315,6 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler { return mMediaSessionManager; } - void sendCloseSystemWindows() { - PhoneWindow.sendCloseSystemWindows(mContext, null); - } - private void handleVolumeKeyEvent(KeyEvent keyEvent) { getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(keyEvent, AudioManager.USE_DEFAULT_STREAM_TYPE); diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index f040462dafdc..5144a91706a1 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -14,15 +14,20 @@ package com.android.internal.util; +import static android.os.Trace.TRACE_TAG_APP; + import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.os.Build; import android.os.SystemClock; import android.os.Trace; import android.provider.DeviceConfig; +import android.text.TextUtils; import android.util.EventLog; import android.util.Log; -import android.util.SparseLongArray; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.EventLogTags; @@ -31,6 +36,7 @@ import com.android.internal.os.BackgroundThread; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; /** * Class to track various latencies in SystemUI. It then writes the latency to statsd and also @@ -44,6 +50,7 @@ public class LatencyTracker { private static final String TAG = "LatencyTracker"; private static final String SETTINGS_ENABLED_KEY = "enabled"; private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval"; + private static final boolean DEBUG = false; /** Default to being enabled on debug builds. */ private static final boolean DEFAULT_ENABLED = Build.IS_DEBUGGABLE; /** Default to collecting data for 1/5 of all actions (randomly sampled). */ @@ -162,7 +169,8 @@ public class LatencyTracker { private static LatencyTracker sLatencyTracker; private final Object mLock = new Object(); - private final SparseLongArray mStartRtc = new SparseLongArray(); + @GuardedBy("mLock") + private final SparseArray<Session> mSessions = new SparseArray<>(); @GuardedBy("mLock") private final int[] mTraceThresholdPerAction = new int[ACTIONS_ALL.length]; @GuardedBy("mLock") @@ -244,8 +252,12 @@ public class LatencyTracker { } } - private static String getTraceNameOfAction(@Action int action) { - return "L<" + getNameOfAction(STATSD_ACTION[action]) + ">"; + private static String getTraceNameOfAction(@Action int action, String tag) { + if (TextUtils.isEmpty(tag)) { + return "L<" + getNameOfAction(STATSD_ACTION[action]) + ">"; + } else { + return "L<" + getNameOfAction(STATSD_ACTION[action]) + "::" + tag + ">"; + } } private static String getTraceTriggerNameForAction(@Action int action) { @@ -263,35 +275,82 @@ public class LatencyTracker { } /** - * Notifies that an action is starting. This needs to be called from the main thread. + * Notifies that an action is starting. <s>This needs to be called from the main thread.</s> * * @param action The action to start. One of the ACTION_* values. */ public void onActionStart(@Action int action) { - if (!isEnabled()) { - return; + onActionStart(action, null); + } + + /** + * Notifies that an action is starting. <s>This needs to be called from the main thread.</s> + * + * @param action The action to start. One of the ACTION_* values. + * @param tag The brief description of the action. + */ + public void onActionStart(@Action int action, String tag) { + synchronized (mLock) { + if (!isEnabled()) { + return; + } + // skip if the action is already instrumenting. + if (mSessions.get(action) != null) { + return; + } + Session session = new Session(action, tag); + session.begin(() -> onActionCancel(action)); + mSessions.put(action, session); + + if (DEBUG) { + Log.d(TAG, "onActionStart: " + session.name() + ", start=" + session.mStartRtc); + } } - Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, getTraceNameOfAction(action), 0); - mStartRtc.put(action, SystemClock.elapsedRealtime()); } /** - * Notifies that an action has ended. This needs to be called from the main thread. + * Notifies that an action has ended. <s>This needs to be called from the main thread.</s> * * @param action The action to end. One of the ACTION_* values. */ public void onActionEnd(@Action int action) { - if (!isEnabled()) { - return; + synchronized (mLock) { + if (!isEnabled()) { + return; + } + Session session = mSessions.get(action); + if (session == null) { + return; + } + session.end(); + mSessions.delete(action); + logAction(action, session.duration()); + + if (DEBUG) { + Log.d(TAG, "onActionEnd:" + session.name() + ", duration=" + session.duration()); + } } - long endRtc = SystemClock.elapsedRealtime(); - long startRtc = mStartRtc.get(action, -1); - if (startRtc == -1) { - return; + } + + /** + * Notifies that an action has canceled. <s>This needs to be called from the main thread.</s> + * + * @param action The action to cancel. One of the ACTION_* values. + * @hide + */ + public void onActionCancel(@Action int action) { + synchronized (mLock) { + Session session = mSessions.get(action); + if (session == null) { + return; + } + session.cancel(); + mSessions.delete(action); + + if (DEBUG) { + Log.d(TAG, "onActionCancel: " + session.name()); + } } - mStartRtc.delete(action); - Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, getTraceNameOfAction(action), 0); - logAction(action, (int) (endRtc - startRtc)); } /** @@ -332,4 +391,57 @@ public class LatencyTracker { FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED, STATSD_ACTION[action], duration); } } + + static class Session { + @Action + private final int mAction; + private final String mTag; + private final String mName; + private Runnable mTimeoutRunnable; + private long mStartRtc = -1; + private long mEndRtc = -1; + + Session(@Action int action, @Nullable String tag) { + mAction = action; + mTag = tag; + mName = TextUtils.isEmpty(mTag) + ? getNameOfAction(STATSD_ACTION[mAction]) + : getNameOfAction(STATSD_ACTION[mAction]) + "::" + mTag; + } + + String name() { + return mName; + } + + String traceName() { + return getTraceNameOfAction(mAction, mTag); + } + + void begin(@NonNull Runnable timeoutAction) { + mStartRtc = SystemClock.elapsedRealtime(); + Trace.asyncTraceBegin(TRACE_TAG_APP, traceName(), 0); + + // start counting timeout. + mTimeoutRunnable = timeoutAction; + BackgroundThread.getHandler() + .postDelayed(mTimeoutRunnable, TimeUnit.SECONDS.toMillis(2)); + } + + void end() { + mEndRtc = SystemClock.elapsedRealtime(); + Trace.asyncTraceEnd(TRACE_TAG_APP, traceName(), 0); + BackgroundThread.getHandler().removeCallbacks(mTimeoutRunnable); + mTimeoutRunnable = null; + } + + void cancel() { + Trace.asyncTraceEnd(TRACE_TAG_APP, traceName(), 0); + BackgroundThread.getHandler().removeCallbacks(mTimeoutRunnable); + mTimeoutRunnable = null; + } + + int duration() { + return (int) (mEndRtc - mStartRtc); + } + } } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 498505cd46ff..cd1bbb6bc6fe 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -17,6 +17,7 @@ package com.android.internal.widget; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC; +import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; @@ -1272,6 +1273,14 @@ public class LockPatternUtils { } /** + * Whether the user is not allowed to set any credentials via PASSWORD_QUALITY_MANAGED. + */ + public boolean isCredentialsDisabledForUser(int userId) { + return getDevicePolicyManager().getPasswordQuality(/* admin= */ null, userId) + == PASSWORD_QUALITY_MANAGED; + } + + /** * @see StrongAuthTracker#isTrustAllowedForUser */ public boolean isTrustAllowedForUser(int userId) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e53d379ea92a..63d61fc5ef9e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5876,7 +5876,6 @@ android:excludeFromRecents="true" android:documentLaunchMode="never" android:relinquishTaskIdentity="true" - android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:process=":ui" android:visibleToInstantApps="true"> <intent-filter> diff --git a/core/res/res/drawable-nodpi/default_wallpaper.png b/core/res/res/drawable-nodpi/default_wallpaper.png Binary files differindex ce546f0a11e7..490ebeeb75c1 100644 --- a/core/res/res/drawable-nodpi/default_wallpaper.png +++ b/core/res/res/drawable-nodpi/default_wallpaper.png diff --git a/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png b/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png Binary files differdeleted file mode 100644 index af8e2512385a..000000000000 --- a/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png +++ /dev/null diff --git a/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png b/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png Binary files differdeleted file mode 100644 index cb00d82a826f..000000000000 --- a/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png +++ /dev/null diff --git a/core/res/res/layout/accessibility_shortcut_chooser_item.xml b/core/res/res/layout/accessibility_shortcut_chooser_item.xml index 7cca1292af68..4d7946b2138b 100644 --- a/core/res/res/layout/accessibility_shortcut_chooser_item.xml +++ b/core/res/res/layout/accessibility_shortcut_chooser_item.xml @@ -39,15 +39,20 @@ android:layout_height="48dp" android:scaleType="fitCenter"/> - <TextView - android:id="@+id/accessibility_shortcut_target_label" + <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:layout_weight="1" - android:textSize="20sp" - android:textColor="?attr/textColorPrimary" - android:fontFamily="sans-serif-medium"/> + android:layout_weight="1"> + + <TextView + android:id="@+id/accessibility_shortcut_target_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="20sp" + android:textColor="?attr/textColorPrimary" + android:fontFamily="sans-serif-medium"/> + </LinearLayout> <TextView android:id="@+id/accessibility_shortcut_target_status" diff --git a/core/res/res/layout/transient_notification.xml b/core/res/res/layout/transient_notification.xml index 8a3d73491af3..3259201f6e9b 100644 --- a/core/res/res/layout/transient_notification.xml +++ b/core/res/res/layout/transient_notification.xml @@ -40,6 +40,5 @@ android:maxLines="2" android:paddingTop="12dp" android:paddingBottom="12dp" - android:lineHeight="20sp" android:textAppearance="@style/TextAppearance.Toast"/> </LinearLayout> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index d5f4cad699c5..985d81099538 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3219,6 +3219,11 @@ and one pSIM) --> <integer name="config_num_physical_slots">1</integer> + <!-- When a radio power off request is received, we will delay completing the request until + either IMS moves to the deregistered state or the timeout defined by this configuration + elapses. If 0, this feature is disabled and we do not delay radio power off requests.--> + <integer name="config_delay_for_ims_dereg_millis">0</integer> + <!--Thresholds for LTE dbm in status bar--> <integer-array translatable="false" name="config_lteDbmThresholds"> <item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN --> @@ -4576,6 +4581,20 @@ --> </integer-array> + <!-- An array of arrays of side fingerprint sensor properties relative to each display. + Note: this value is temporary and is expected to be queried directly + from the HAL in the future. --> + <array name="config_sfps_sensor_props" translatable="false"> + <!-- + <array> + <item>displayId</item> + <item>sensorLocationX</item> + <item>sensorLocationY</item> + <item>sensorRadius</item> + <array> + --> + </array> + <!-- How long it takes for the HW to start illuminating after the illumination is requested. --> <integer name="config_udfps_illumination_transition_ms">50</integer> @@ -5174,6 +5193,12 @@ <item>@array/config_secondaryBuiltInDisplayWaterfallCutout</item> </array> + <!-- The component name of the activity for the companion-device-manager notification access + confirmation. --> + <string name="config_notificationAccessConfirmationActivity" translatable="false"> + com.android.settings/com.android.settings.notification.NotificationAccessConfirmationActivity + </string> + <!-- Whether the airplane mode should be reset when device boots in non-safemode after exiting from safemode. This flag should be enabled only when the product does not have any UI to toggle airplane diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index bac9cf23b176..9553f95fe5c9 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -966,6 +966,7 @@ please see styles_device_defaults.xml. <style name="TextAppearance.Toast"> <item name="fontFamily">@*android:string/config_bodyFontFamily</item> <item name="textSize">14sp</item> + <item name="lineHeight">20sp</item> <item name="textColor">?android:attr/textColorPrimary</item> </style> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7fe73e5067b9..97d5e28036c2 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -486,6 +486,7 @@ <java-symbol type="string" name="config_deviceSpecificDevicePolicyManagerService" /> <java-symbol type="string" name="config_deviceSpecificAudioService" /> <java-symbol type="integer" name="config_num_physical_slots" /> + <java-symbol type="integer" name="config_delay_for_ims_dereg_millis" /> <java-symbol type="array" name="config_integrityRuleProviderPackages" /> <java-symbol type="bool" name="config_useAssistantVolume" /> <java-symbol type="string" name="config_bandwidthEstimateSource" /> @@ -2221,6 +2222,7 @@ <java-symbol type="bool" name="config_enableSecondaryLocationTimeZoneProvider" /> <java-symbol type="string" name="config_secondaryLocationTimeZoneProviderPackageName" /> <java-symbol type="bool" name="config_autoResetAirplaneMode" /> + <java-symbol type="string" name="config_notificationAccessConfirmationActivity" /> <java-symbol type="layout" name="resolver_list" /> <java-symbol type="id" name="resolver_list" /> @@ -2608,6 +2610,7 @@ <java-symbol type="array" name="config_biometric_sensors" /> <java-symbol type="bool" name="allow_test_udfps" /> <java-symbol type="array" name="config_udfps_sensor_props" /> + <java-symbol type="array" name="config_sfps_sensor_props" /> <java-symbol type="integer" name="config_udfps_illumination_transition_ms" /> <java-symbol type="bool" name="config_is_powerbutton_fps" /> diff --git a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java index a5a98a98f0be..109b7ab73d6f 100644 --- a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java +++ b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java @@ -17,6 +17,8 @@ package android.service.notification; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNotSame; import static junit.framework.Assert.assertNull; import static org.junit.Assert.assertFalse; @@ -51,6 +53,7 @@ public class StatusBarNotificationTest { private final Context mMockContext = mock(Context.class); @Mock + private Context mRealContext; private PackageManager mPm; private static final String PKG = "com.example.o"; @@ -75,6 +78,8 @@ public class StatusBarNotificationTest { InstrumentationRegistry.getContext().getResources()); when(mMockContext.getPackageManager()).thenReturn(mPm); when(mMockContext.getApplicationInfo()).thenReturn(new ApplicationInfo()); + + mRealContext = InstrumentationRegistry.getContext(); } @Test @@ -199,6 +204,19 @@ public class StatusBarNotificationTest { } + @Test + public void testGetPackageContext_worksWithUserAll() { + String pkg = "com.android.systemui"; + int uid = 1000; + Notification notification = getNotificationBuilder(GROUP_ID_1, CHANNEL_ID).build(); + StatusBarNotification sbn = new StatusBarNotification( + pkg, pkg, ID, TAG, uid, uid, notification, UserHandle.ALL, null, UID); + Context resultContext = sbn.getPackageContext(mRealContext); + assertNotNull(resultContext); + assertNotSame(mRealContext, resultContext); + assertEquals(pkg, resultContext.getPackageName()); + } + private StatusBarNotification getNotification(String pkg, String group, String channelId) { return getNotification(pkg, getNotificationBuilder(group, channelId)); } diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 0d7225be9e03..119c939135a6 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1207,6 +1207,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-779535710": { + "message": "Transition %d: Set %s as transient-launch", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-775004869": { "message": "Not a match: %s", "level": "DEBUG", diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java index 9212a0f5e6b9..46c8ffe286bd 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java @@ -210,17 +210,17 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) { - WindowContainerTransaction.TaskFragmentAdjacentOptions adjacentOptions = null; + WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null; final boolean finishSecondaryWithPrimary = splitRule != null && SplitContainer.shouldFinishSecondaryWithPrimary(splitRule); final boolean finishPrimaryWithSecondary = splitRule != null && SplitContainer.shouldFinishPrimaryWithSecondary(splitRule); if (finishSecondaryWithPrimary || finishPrimaryWithSecondary) { - adjacentOptions = new WindowContainerTransaction.TaskFragmentAdjacentOptions(); - adjacentOptions.setDelayPrimaryLastActivityRemoval(finishSecondaryWithPrimary); - adjacentOptions.setDelaySecondaryLastActivityRemoval(finishPrimaryWithSecondary); + adjacentParams = new WindowContainerTransaction.TaskFragmentAdjacentParams(); + adjacentParams.setShouldDelayPrimaryLastActivityRemoval(finishSecondaryWithPrimary); + adjacentParams.setShouldDelaySecondaryLastActivityRemoval(finishPrimaryWithSecondary); } - wct.setAdjacentTaskFragments(primary, secondary, adjacentOptions); + wct.setAdjacentTaskFragments(primary, secondary, adjacentParams); } TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken, diff --git a/libs/WindowManager/Shell/res/drawable/split_rounded_bottom.xml b/libs/WindowManager/Shell/res/drawable/split_rounded_bottom.xml deleted file mode 100644 index 18dc909ae955..000000000000 --- a/libs/WindowManager/Shell/res/drawable/split_rounded_bottom.xml +++ /dev/null @@ -1,30 +0,0 @@ -<!-- - ~ Copyright (C) 2021 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/split_divider_corner_size" - android:height="@dimen/split_divider_corner_size" - android:viewportWidth="42" - android:viewportHeight="42"> - - <group android:pivotX="21" - android:pivotY="21" - android:rotation="180"> - <path - android:fillColor="@color/split_divider_background" - android:pathData="m 0 0 c 8 0 16 8 16 16 h 10 c 0 -8 8 -16 16 -16 z" /> - </group> - -</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/split_rounded_left.xml b/libs/WindowManager/Shell/res/drawable/split_rounded_left.xml deleted file mode 100644 index 931cacf887cd..000000000000 --- a/libs/WindowManager/Shell/res/drawable/split_rounded_left.xml +++ /dev/null @@ -1,30 +0,0 @@ -<!-- - ~ Copyright (C) 2021 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/split_divider_corner_size" - android:height="@dimen/split_divider_corner_size" - android:viewportWidth="42" - android:viewportHeight="42"> - - <group android:pivotX="21" - android:pivotY="21" - android:rotation="-90"> - <path - android:fillColor="@color/split_divider_background" - android:pathData="m 0 0 c 8 0 16 8 16 16 h 10 c 0 -8 8 -16 16 -16 z" /> - </group> - -</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/split_rounded_right.xml b/libs/WindowManager/Shell/res/drawable/split_rounded_right.xml deleted file mode 100644 index 54e47612faa8..000000000000 --- a/libs/WindowManager/Shell/res/drawable/split_rounded_right.xml +++ /dev/null @@ -1,30 +0,0 @@ -<!-- - ~ Copyright (C) 2021 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/split_divider_corner_size" - android:height="@dimen/split_divider_corner_size" - android:viewportWidth="42" - android:viewportHeight="42"> - - <group android:pivotX="21" - android:pivotY="21" - android:rotation="90"> - <path - android:fillColor="@color/split_divider_background" - android:pathData="m 0 0 c 8 0 16 8 16 16 h 10 c 0 -8 8 -16 16 -16 z" /> - </group> - -</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/split_rounded_top.xml b/libs/WindowManager/Shell/res/drawable/split_rounded_top.xml deleted file mode 100644 index 9115b5a2352e..000000000000 --- a/libs/WindowManager/Shell/res/drawable/split_rounded_top.xml +++ /dev/null @@ -1,26 +0,0 @@ -<!-- - ~ Copyright (C) 2021 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="@dimen/split_divider_corner_size" - android:height="@dimen/split_divider_corner_size" - android:viewportWidth="42" - android:viewportHeight="42"> - - <path - android:fillColor="@color/split_divider_background" - android:pathData="m 0 0 c 8 0 16 8 16 16 h 10 c 0 -8 8 -16 16 -16 z" /> - -</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/split_divider.xml b/libs/WindowManager/Shell/res/layout/split_divider.xml index 94182cdba0dd..e3be700469a7 100644 --- a/libs/WindowManager/Shell/res/layout/split_divider.xml +++ b/libs/WindowManager/Shell/res/layout/split_divider.xml @@ -24,20 +24,20 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <View style="@style/DockedDividerTopLeftRoundCorner"/> - <View style="@style/DockedDividerBackground" android:id="@+id/docked_divider_background"/> - <View style="@style/DockedDividerBottomRightRoundCorner"/> - <com.android.wm.shell.common.split.DividerHandleView style="@style/DockedDividerHandle" android:id="@+id/docked_divider_handle" android:contentDescription="@string/accessibility_divider" android:background="@null"/> + <com.android.wm.shell.common.split.DividerRoundedCorner + android:layout_width="match_parent" + android:layout_height="match_parent"/> + </FrameLayout> </com.android.wm.shell.common.split.DividerView> diff --git a/libs/WindowManager/Shell/res/values-land/styles.xml b/libs/WindowManager/Shell/res/values-land/styles.xml index e5707f3170d8..9eddac48e6de 100644 --- a/libs/WindowManager/Shell/res/values-land/styles.xml +++ b/libs/WindowManager/Shell/res/values-land/styles.xml @@ -28,20 +28,6 @@ <item name="android:layout_height">96dp</item> </style> - <style name="DockedDividerTopLeftRoundCorner"> - <item name="android:layout_gravity">center_horizontal|top</item> - <item name="android:background">@drawable/split_rounded_top</item> - <item name="android:layout_width">@dimen/split_divider_corner_size</item> - <item name="android:layout_height">@dimen/split_divider_corner_size</item> - </style> - - <style name="DockedDividerBottomRightRoundCorner"> - <item name="android:layout_gravity">center_horizontal|bottom</item> - <item name="android:background">@drawable/split_rounded_bottom</item> - <item name="android:layout_width">@dimen/split_divider_corner_size</item> - <item name="android:layout_height">@dimen/split_divider_corner_size</item> - </style> - <style name="DockedDividerMinimizedShadow"> <item name="android:layout_width">8dp</item> <item name="android:layout_height">match_parent</item> diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml index 28ff25ae0fbe..cb6d4de71a45 100644 --- a/libs/WindowManager/Shell/res/values/styles.xml +++ b/libs/WindowManager/Shell/res/values/styles.xml @@ -37,20 +37,6 @@ <item name="android:background">@color/split_divider_background</item> </style> - <style name="DockedDividerTopLeftRoundCorner"> - <item name="android:layout_gravity">center_vertical|left</item> - <item name="android:background">@drawable/split_rounded_left</item> - <item name="android:layout_width">@dimen/split_divider_corner_size</item> - <item name="android:layout_height">@dimen/split_divider_corner_size</item> - </style> - - <style name="DockedDividerBottomRightRoundCorner"> - <item name="android:layout_gravity">center_vertical|right</item> - <item name="android:background">@drawable/split_rounded_right</item> - <item name="android:layout_width">@dimen/split_divider_corner_size</item> - <item name="android:layout_height">@dimen/split_divider_corner_size</item> - </style> - <style name="DockedDividerMinimizedShadow"> <item name="android:layout_width">match_parent</item> <item name="android:layout_height">8dp</item> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index b5dffba7a0f3..d3265346036a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -340,6 +340,13 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } @Override + public void onImeDrawnOnTask(int taskId) { + if (mStartingWindow != null) { + mStartingWindow.onImeDrawnOnTask(taskId); + } + } + + @Override public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { synchronized (mLock) { onTaskAppeared(new TaskAppearedInfo(taskInfo, leash)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java index 3800b8d234f3..c2cb72a530a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java @@ -43,6 +43,7 @@ import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.SurfaceUtils; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitLayout; +import com.android.wm.shell.common.split.SplitWindowManager; import java.io.PrintWriter; @@ -70,6 +71,19 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou private final DisplayImeController mDisplayImeController; private SplitLayout mSplitLayout; + private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = + new SplitWindowManager.ParentContainerCallbacks() { + @Override + public void attachToParentSurface(SurfaceControl.Builder b) { + b.setParent(mRootTaskLeash); + } + + @Override + public void onLeashReady(SurfaceControl leash) { + mSyncQueue.runInSync(t -> t.show(leash)); + } + }; + AppPair(AppPairsController controller) { mController = controller; mSyncQueue = controller.getSyncTransactionQueue(); @@ -110,8 +124,7 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou mSplitLayout = new SplitLayout(TAG + "SplitDivider", mDisplayController.getDisplayContext(mRootTaskInfo.displayId), mRootTaskInfo.configuration, this /* layoutChangeListener */, - b -> b.setParent(mRootTaskLeash), mDisplayImeController, - mController.getTaskOrganizer()); + mParentContainerCallbacks, mDisplayImeController, mController.getTaskOrganizer()); final WindowContainerToken token1 = task1.token; final WindowContainerToken token2 = task2.token; @@ -218,8 +231,6 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou if (mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) { onLayoutChanged(mSplitLayout); } - // updateConfiguration re-inits the dividerbar, so show it now - mSyncQueue.runInSync(t -> t.show(mSplitLayout.getDividerLeash())); } } else if (taskInfo.taskId == getTaskId1()) { mTaskInfo1 = taskInfo; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 9d65d28b21b4..05ebbba4e955 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -422,14 +422,6 @@ public class Bubble implements BubbleViewProvider { } } - @Override - public void setExpandedContentAlpha(float alpha) { - if (mExpandedView != null) { - mExpandedView.setAlpha(alpha); - mExpandedView.setTaskViewAlpha(alpha); - } - } - /** * Set visibility of bubble in the expanded state. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 9dafefebf62b..c126f32387f0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -69,6 +69,7 @@ import android.util.SparseArray; import android.util.SparseSetArray; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; import android.window.WindowContainerTransaction; @@ -144,7 +145,6 @@ public class BubbleController { private BubbleLogger mLogger; private BubbleData mBubbleData; - private View mBubbleScrim; @Nullable private BubbleStackView mStackView; private BubbleIconFactory mBubbleIconFactory; private BubblePositioner mBubblePositioner; @@ -189,6 +189,9 @@ public class BubbleController { /** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */ private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED; + /** Saved insets, used to detect WindowInset changes. */ + private WindowInsets mWindowInsets; + private boolean mInflateSynchronously; /** True when user is in status bar unlock shade. */ @@ -629,8 +632,11 @@ public class BubbleController { mBubbleData.getOverflow().initialize(this); mWindowManager.addView(mStackView, mWmLayoutParams); mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> { - mBubblePositioner.update(); - mStackView.onDisplaySizeChanged(); + if (!windowInsets.equals(mWindowInsets)) { + mWindowInsets = windowInsets; + mBubblePositioner.update(); + mStackView.onDisplaySizeChanged(); + } return windowInsets; }); } catch (IllegalStateException e) { @@ -1372,8 +1378,9 @@ public class BubbleController { private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedTaskListener { @Override public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { + mBubblePositioner.setImeVisible(imeVisible, imeHeight); if (mStackView != null) { - mStackView.onImeVisibilityChanged(imeVisible, imeHeight); + mStackView.animateForIme(imeVisible); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index 7d7bfb2a92a3..a87aad4261a6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -398,6 +398,7 @@ public class BubbleExpandedView extends LinearLayout { updatePointerView(); } + /** Updates the size and visuals of the pointer. **/ private void updatePointerView() { LayoutParams lp = (LayoutParams) mPointerView.getLayoutParams(); if (mCurrentPointer == mLeftPointer || mCurrentPointer == mRightPointer) { @@ -524,9 +525,8 @@ public class BubbleExpandedView extends LinearLayout { if (mTaskView != null) { mTaskView.setAlpha(alpha); } - if (mManageButton != null && mManageButton.getVisibility() == View.VISIBLE) { - mManageButton.setAlpha(alpha); - } + mPointerView.setAlpha(alpha); + setAlpha(alpha); } /** @@ -545,6 +545,7 @@ public class BubbleExpandedView extends LinearLayout { mIsContentVisible = visibility; if (mTaskView != null && !mIsAlphaAnimating) { mTaskView.setAlpha(visibility ? 1f : 0f); + mPointerView.setAlpha(visibility ? 1f : 0f); } } @@ -689,7 +690,7 @@ public class BubbleExpandedView extends LinearLayout { * the bubble if showing vertically. * @param onLeft whether the stack was on the left side of the screen when expanded. */ - public void setPointerPosition(float bubblePosition, boolean onLeft) { + public void setPointerPosition(float bubblePosition, boolean onLeft, boolean animate) { // Pointer gets drawn in the padding final boolean showVertically = mPositioner.showBubblesVertically(); final float paddingLeft = (showVertically && onLeft) @@ -710,6 +711,8 @@ public class BubbleExpandedView extends LinearLayout { : pointerPosition; // Post because we need the width of the view post(() -> { + mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer; + updatePointerView(); float pointerY; float pointerX; if (showVertically) { @@ -721,11 +724,13 @@ public class BubbleExpandedView extends LinearLayout { pointerY = mPointerOverlap; pointerX = bubbleCenter - (mPointerWidth / 2f); } - mPointerView.setTranslationY(pointerY); - mPointerView.setTranslationX(pointerX); - mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer; - updatePointerView(); - mPointerView.setVisibility(VISIBLE); + if (animate) { + mPointerView.animate().translationX(pointerX).translationY(pointerY).start(); + } else { + mPointerView.setTranslationY(pointerY); + mPointerView.setTranslationX(pointerX); + mPointerView.setVisibility(VISIBLE); + } }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index 705a12a5e65b..0c3a6b2dbd84 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -154,10 +154,6 @@ class BubbleOverflow( return dotPath } - override fun setExpandedContentAlpha(alpha: Float) { - expandedView?.alpha = alpha - } - override fun setTaskViewVisibility(visible: Boolean) { // Overflow does not have a TaskView. } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 306224bd316c..127d5a8a9966 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -70,13 +70,16 @@ public class BubblePositioner { private Context mContext; private WindowManager mWindowManager; - private Rect mPositionRect; private Rect mScreenRect; private @Surface.Rotation int mRotation = Surface.ROTATION_0; private Insets mInsets; + private boolean mImeVisible; + private int mImeHeight; + private boolean mIsLargeScreen; + + private Rect mPositionRect; private int mDefaultMaxBubbles; private int mMaxBubbles; - private int mBubbleSize; private int mSpacingBetweenBubbles; @@ -98,7 +101,6 @@ public class BubblePositioner { private PointF mRestingStackPosition; private int[] mPaddings = new int[4]; - private boolean mIsLargeScreen; private boolean mShowingInTaskbar; private @TaskbarPosition int mTaskbarPosition = TASKBAR_POSITION_NONE; private int mTaskbarIconSize; @@ -302,6 +304,17 @@ public class BubblePositioner { return mMaxBubbles; } + /** The height for the IME if it's visible. **/ + public int getImeHeight() { + return mImeVisible ? mImeHeight : 0; + } + + /** Sets whether the IME is visible. **/ + public void setImeVisible(boolean visible, int height) { + mImeVisible = visible; + mImeHeight = height; + } + /** * Calculates the padding for the bubble expanded view. * @@ -357,7 +370,7 @@ public class BubblePositioner { } /** Gets the y position of the expanded view if it was top-aligned. */ - private float getExpandedViewYTopAligned() { + public float getExpandedViewYTopAligned() { final int top = getAvailableRect().top; if (showBubblesVertically()) { return top - mPointerWidth + mExpandedViewPadding; @@ -366,10 +379,6 @@ public class BubblePositioner { } } - public float getExpandedBubblesY() { - return getAvailableRect().top + mExpandedViewPadding; - } - /** * Calculate the maximum height the expanded view can be depending on where it's placed on * the screen and the size of the elements around it (e.g. padding, pointer, manage button). @@ -464,18 +473,21 @@ public class BubblePositioner { : bubblePosition + (normalizedSize / 2f) - mPointerWidth; } + private int getExpandedStackSize(int numberOfBubbles) { + return (numberOfBubbles * mBubbleSize) + + ((numberOfBubbles - 1) * mSpacingBetweenBubbles); + } + /** * Returns the position of the bubble on-screen when the stack is expanded. * * @param index the index of the bubble in the stack. - * @param numberOfBubbles the total number of bubbles in the stack. - * @param onLeftEdge whether the stack would rest on the left edge of the screen when collapsed. - * @return the x, y position of the bubble on-screen when the stack is expanded. + * @param state state information about the stack to help with calculations. + * @return the position of the bubble on-screen when the stack is expanded. */ - public PointF getExpandedBubbleXY(int index, int numberOfBubbles, boolean onLeftEdge) { + public PointF getExpandedBubbleXY(int index, BubbleStackView.StackViewState state) { final float positionInRow = index * (mBubbleSize + mSpacingBetweenBubbles); - final float expandedStackSize = (numberOfBubbles * mBubbleSize) - + ((numberOfBubbles - 1) * mSpacingBetweenBubbles); + final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles); final float centerPosition = showBubblesVertically() ? mPositionRect.centerY() : mPositionRect.centerX(); @@ -491,17 +503,66 @@ public class BubblePositioner { int right = mIsLargeScreen ? mPositionRect.right - mExpandedViewLargeScreenInset + mExpandedViewPadding : mPositionRect.right - mBubbleSize; - x = onLeftEdge + x = state.onLeft ? left : right; } else { y = mPositionRect.top + mExpandedViewPadding; x = rowStart + positionInRow; } + + if (showBubblesVertically() && mImeVisible) { + return new PointF(x, getExpandedBubbleYForIme(index, state.numberOfBubbles)); + } return new PointF(x, y); } /** + * Returns the position of the bubble on-screen when the stack is expanded and the IME + * is showing. + * + * @param index the index of the bubble in the stack. + * @param numberOfBubbles the total number of bubbles in the stack. + * @return y position of the bubble on-screen when the stack is expanded. + */ + private float getExpandedBubbleYForIme(int index, int numberOfBubbles) { + final float top = getAvailableRect().top + mExpandedViewPadding; + if (!showBubblesVertically()) { + // Showing horizontally: align to top + return top; + } + + // Showing vertically: might need to translate the bubbles above the IME. + // Subtract spacing here to provide a margin between top of IME and bottom of bubble row. + final float bottomInset = getImeHeight() + mInsets.bottom - (mSpacingBetweenBubbles * 2); + final float expandedStackSize = getExpandedStackSize(numberOfBubbles); + final float centerPosition = showBubblesVertically() + ? mPositionRect.centerY() + : mPositionRect.centerX(); + final float rowBottom = centerPosition + (expandedStackSize / 2f); + final float rowTop = centerPosition - (expandedStackSize / 2f); + float rowTopForIme = rowTop; + if (rowBottom > bottomInset) { + // We overlap with IME, must shift the bubbles + float translationY = rowBottom - bottomInset; + rowTopForIme = Math.max(rowTop - translationY, top); + if (rowTop - translationY < top) { + // Even if we shift the bubbles, they will still overlap with the IME. + // Hide the overflow for a lil more space: + final float expandedStackSizeNoO = getExpandedStackSize(numberOfBubbles - 1); + final float centerPositionNoO = showBubblesVertically() + ? mPositionRect.centerY() + : mPositionRect.centerX(); + final float rowBottomNoO = centerPositionNoO + (expandedStackSizeNoO / 2f); + final float rowTopNoO = centerPositionNoO - (expandedStackSizeNoO / 2f); + translationY = rowBottomNoO - bottomInset; + rowTopForIme = rowTopNoO - translationY; + } + } + return rowTopForIme + (index * (mBubbleSize + mSpacingBetweenBubbles)); + } + + /** * @return the width of the bubble flyout (message originating from the bubble). */ public float getMaxFlyoutSize() { 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 5bc6128d6c9e..9d0dd3c7fcdb 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 @@ -28,6 +28,8 @@ import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RES import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.content.ContentResolver; @@ -188,6 +190,7 @@ public class BubbleStackView extends FrameLayout }; private final BubbleController mBubbleController; private final BubbleData mBubbleData; + private StackViewState mStackViewState = new StackViewState(); private final ValueAnimator mDismissBubbleAnimator; @@ -246,7 +249,6 @@ public class BubbleStackView extends FrameLayout private int mBubbleTouchPadding; private int mExpandedViewPadding; private int mCornerRadius; - private int mImeOffset; @Nullable private BubbleViewProvider mExpandedBubble; private boolean mIsExpanded; @@ -757,7 +759,6 @@ public class BubbleStackView extends FrameLayout mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size); mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding); - mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset); mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); @@ -779,7 +780,7 @@ public class BubbleStackView extends FrameLayout this::animateShadows /* onStackAnimationFinished */, mPositioner); mExpandedAnimationController = new ExpandedAnimationController(mPositioner, - onBubbleAnimatedOut); + onBubbleAnimatedOut, this); mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER; // Force LTR by default since most of the Bubbles UI is positioned manually by the user, or @@ -890,7 +891,7 @@ public class BubbleStackView extends FrameLayout // Re-draw bubble row and pointer for new orientation. beforeExpandedViewAnimation(); updateOverflowVisibility(); - updatePointerPosition(); + updatePointerPosition(false /* forIme */); mExpandedAnimationController.expandFromStack(() -> { afterExpandedViewAnimation(); } /* after */); @@ -968,7 +969,8 @@ public class BubbleStackView extends FrameLayout }); mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> { if (mExpandedBubble != null) { - mExpandedBubble.setExpandedContentAlpha((float) valueAnimator.getAnimatedValue()); + mExpandedBubble.getExpandedView().setTaskViewAlpha( + (float) valueAnimator.getAnimatedValue()); } }); @@ -1558,7 +1560,7 @@ public class BubbleStackView extends FrameLayout } else { bubble.cleanupViews(); } - updatePointerPosition(); + updatePointerPosition(false /* forIme */); updateExpandedView(); logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED); return; @@ -1599,7 +1601,7 @@ public class BubbleStackView extends FrameLayout .map(b -> b.getIconView()).collect(Collectors.toList()); mStackAnimationController.animateReorder(bubbleViews, reorder); } - updatePointerPosition(); + updatePointerPosition(false /* forIme */); } /** @@ -1670,7 +1672,6 @@ public class BubbleStackView extends FrameLayout private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) { final BubbleViewProvider previouslySelected = mExpandedBubble; mExpandedBubble = bubbleToSelect; - updatePointerPosition(); if (mIsExpanded) { hideCurrentInputMethod(); @@ -1762,6 +1763,7 @@ public class BubbleStackView extends FrameLayout * not. */ void hideCurrentInputMethod() { + mPositioner.setImeVisible(false, 0); mBubbleController.hideCurrentInputMethod(); } @@ -1864,7 +1866,7 @@ public class BubbleStackView extends FrameLayout updateBadges(false /* setBadgeForCollapsedStack */); mBubbleContainer.setActiveController(mExpandedAnimationController); updateOverflowVisibility(); - updatePointerPosition(); + updatePointerPosition(false /* forIme */); mExpandedAnimationController.expandFromStack(() -> { if (mIsExpanded && mExpandedBubble.getExpandedView() != null) { maybeShowManageEdu(); @@ -1876,8 +1878,7 @@ public class BubbleStackView extends FrameLayout } else { index = getBubbleIndex(mExpandedBubble); } - PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleContainer.getChildCount(), - mStackOnLeftOrWillBe); + PointF p = mPositioner.getExpandedBubbleXY(index, getState()); final float translationY = mPositioner.getExpandedViewY(mExpandedBubble, mPositioner.showBubblesVertically() ? p.y : p.x); mExpandedViewContainer.setTranslationX(0f); @@ -1928,7 +1929,7 @@ public class BubbleStackView extends FrameLayout mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); if (mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.setExpandedContentAlpha(0f); + mExpandedBubble.getExpandedView().setTaskViewAlpha(0f); // We'll be starting the alpha animation after a slight delay, so set this flag early // here. @@ -2011,8 +2012,7 @@ public class BubbleStackView extends FrameLayout index = mBubbleData.getBubbles().indexOf(mExpandedBubble); } // Value the bubble is animating from (back into the stack). - final PointF p = mPositioner.getExpandedBubbleXY(index, - mBubbleContainer.getChildCount(), mStackOnLeftOrWillBe); + final PointF p = mPositioner.getExpandedBubbleXY(index, getState()); if (mPositioner.showBubblesVertically()) { float pivotX; float pivotY = p.y + mBubbleSize / 2f; @@ -2109,7 +2109,7 @@ public class BubbleStackView extends FrameLayout PointF p = mPositioner.getExpandedBubbleXY(isOverflow ? mBubbleContainer.getChildCount() - 1 : mBubbleData.getBubbles().indexOf(mExpandedBubble), - mBubbleContainer.getChildCount(), mStackOnLeftOrWillBe); + getState()); mExpandedViewContainer.setAlpha(1f); mExpandedViewContainer.setVisibility(View.VISIBLE); @@ -2187,9 +2187,20 @@ public class BubbleStackView extends FrameLayout } } - /** Moves the bubbles out of the way if they're going to be over the keyboard. */ - public void onImeVisibilityChanged(boolean visible, int height) { - mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0); + /** + * Updates the stack based for IME changes. When collapsed it'll move the stack if it + * overlaps where they IME would be. When expanded it'll shift the expanded bubbles + * if they might overlap with the IME (this only happens for large screens). + */ + public void animateForIme(boolean visible) { + if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) { + // This will update the animation so the bubbles move to position for the IME + mExpandedAnimationController.expandFromStack(() -> { + updatePointerPosition(false /* forIme */); + afterExpandedViewAnimation(); + } /* after */); + return; + } if (!mIsExpanded && getBubbleCount() > 0) { final float stackDestinationY = @@ -2208,9 +2219,20 @@ public class BubbleStackView extends FrameLayout FLYOUT_IME_ANIMATION_SPRING_CONFIG) .start(); } - } else if (mIsExpanded && mExpandedBubble != null - && mExpandedBubble.getExpandedView() != null) { + } else if (mPositioner.showBubblesVertically() && mIsExpanded + && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { mExpandedBubble.getExpandedView().setImeVisible(visible); + List<Animator> animList = new ArrayList(); + for (int i = 0; i < mBubbleContainer.getChildCount(); i++) { + View child = mBubbleContainer.getChildAt(i); + float transY = mPositioner.getExpandedBubbleXY(i, getState()).y; + ObjectAnimator anim = ObjectAnimator.ofFloat(child, TRANSLATION_Y, transY); + animList.add(anim); + } + updatePointerPosition(true /* forIme */); + AnimatorSet set = new AnimatorSet(); + set.playTogether(animList); + set.start(); } } @@ -2514,7 +2536,7 @@ public class BubbleStackView extends FrameLayout // Account for the IME in the touchable region so that the touchable region of the // Bubble window doesn't obscure the IME. The touchable region affects which areas // of the screen can be excluded by lower windows (IME is just above the embedded task) - outRect.bottom -= (int) mStackAnimationController.getImeHeight(); + outRect.bottom -= mPositioner.getImeHeight(); } if (mFlyout.getVisibility() == View.VISIBLE) { @@ -2773,13 +2795,13 @@ public class BubbleStackView extends FrameLayout } if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { PointF p = mPositioner.getExpandedBubbleXY(getBubbleIndex(mExpandedBubble), - mBubbleContainer.getChildCount(), mStackOnLeftOrWillBe); + getState()); mExpandedViewContainer.setTranslationY(mPositioner.getExpandedViewY(mExpandedBubble, mPositioner.showBubblesVertically() ? p.y : p.x)); mExpandedViewContainer.setTranslationX(0f); mExpandedBubble.getExpandedView().updateView( mExpandedViewContainer.getLocationOnScreen()); - updatePointerPosition(); + updatePointerPosition(false /* forIme */); } mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); @@ -2850,7 +2872,13 @@ public class BubbleStackView extends FrameLayout } } - private void updatePointerPosition() { + /** + * Updates the position of the pointer based on the expanded bubble. + * + * @param forIme whether the position is being updated due to the ime appearing, in this case + * the pointer is animated to the location. + */ + private void updatePointerPosition(boolean forIme) { if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { return; } @@ -2858,13 +2886,12 @@ public class BubbleStackView extends FrameLayout if (index == -1) { return; } - PointF bubblePosition = mPositioner.getExpandedBubbleXY(index, - mBubbleContainer.getChildCount(), - mStackOnLeftOrWillBe); - mExpandedBubble.getExpandedView().setPointerPosition(mPositioner.showBubblesVertically() - ? bubblePosition.y - : bubblePosition.x, - mStackOnLeftOrWillBe); + PointF position = mPositioner.getExpandedBubbleXY(index, getState()); + float bubblePosition = mPositioner.showBubblesVertically() + ? position.y + : position.x; + mExpandedBubble.getExpandedView().setPointerPosition(bubblePosition, + mStackOnLeftOrWillBe, forIme /* animate */); } /** @@ -2947,6 +2974,26 @@ public class BubbleStackView extends FrameLayout return bubbles; } + /** @return the current stack state. */ + public StackViewState getState() { + mStackViewState.numberOfBubbles = mBubbleContainer.getChildCount(); + mStackViewState.selectedIndex = getBubbleIndex(mExpandedBubble); + mStackViewState.onLeft = mStackOnLeftOrWillBe; + return mStackViewState; + } + + /** + * Holds some commonly queried information about the stack. + */ + public static class StackViewState { + // Number of bubbles (including the overflow itself) in the stack. + public int numberOfBubbles; + // The selected index if the stack is expanded. + public int selectedIndex; + // Whether the stack is resting on the left or right side of the screen when collapsed. + public boolean onLeft; + } + /** * Representation of stack position that uses relative properties rather than absolute * coordinates. This is used to maintain similar stack positions across configuration changes. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java index 38b3ba9dfda0..7e552826e94a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java @@ -29,12 +29,6 @@ public interface BubbleViewProvider { @Nullable BubbleExpandedView getExpandedView(); /** - * Sets the alpha of the expanded view content. This will be applied to both the expanded view - * container itself (the manage button, etc.) as well as the TaskView within it. - */ - void setExpandedContentAlpha(float alpha); - - /** * Sets whether the contents of the bubble's TaskView should be visible. */ void setTaskViewVisibility(boolean visible); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java index c32be98866cf..f0f78748e343 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java @@ -21,7 +21,6 @@ import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RES import android.content.res.Resources; import android.graphics.Path; import android.graphics.PointF; -import android.graphics.Rect; import android.view.View; import androidx.annotation.NonNull; @@ -33,6 +32,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.animation.PhysicsAnimator; import com.android.wm.shell.bubbles.BubblePositioner; +import com.android.wm.shell.bubbles.BubbleStackView; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.google.android.collect.Sets; @@ -124,12 +124,15 @@ public class ExpandedAnimationController private BubblePositioner mPositioner; + private BubbleStackView mBubbleStackView; + public ExpandedAnimationController(BubblePositioner positioner, - Runnable onBubbleAnimatedOutAction) { + Runnable onBubbleAnimatedOutAction, BubbleStackView stackView) { mPositioner = positioner; updateResources(); mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction; mCollapsePoint = mPositioner.getDefaultStartPosition(); + mBubbleStackView = stackView; } /** @@ -239,10 +242,7 @@ public class ExpandedAnimationController final Path path = new Path(); path.moveTo(bubble.getTranslationX(), bubble.getTranslationY()); - boolean onLeft = mPositioner.isStackOnLeft(mCollapsePoint); - final PointF p = mPositioner.getExpandedBubbleXY(index, - mLayout.getChildCount(), - onLeft); + final PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleStackView.getState()); if (expanding) { // If we're expanding, first draw a line from the bubble's current position to where // it'll end up @@ -364,7 +364,7 @@ public class ExpandedAnimationController bubbleView.setTranslationY(y); } - final float expandedY = mPositioner.getExpandedBubblesY(); + final float expandedY = mPositioner.getExpandedViewYTopAligned(); final boolean draggedOutEnough = y > expandedY + mBubbleSizePx || y < expandedY - mBubbleSizePx; if (draggedOutEnough != mBubbleDraggedOutEnough) { @@ -410,8 +410,7 @@ public class ExpandedAnimationController return; } final int index = mLayout.indexOfChild(bubbleView); - final PointF p = mPositioner.getExpandedBubbleXY(index, mLayout.getChildCount(), - mPositioner.isStackOnLeft(mCollapsePoint)); + final PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleStackView.getState()); animationForChildAtIndex(index) .position(p.x, p.y) .withPositionStartVelocities(velX, velY) @@ -429,16 +428,6 @@ public class ExpandedAnimationController updateBubblePositions(); } - /** - * Animates the bubbles to the y position. Used in response to IME showing. - */ - public void updateYPosition(Runnable after) { - if (mLayout == null) return; - animationsForChildrenFromIndex( - 0, (i, anim) -> anim.translationY(mPositioner.getExpandedBubblesY())) - .startAll(after); - } - /** Description of current animation controller state. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("ExpandedAnimationController state:"); @@ -496,36 +485,33 @@ public class ExpandedAnimationController startOrUpdatePathAnimation(false /* expanding */); } else { boolean onLeft = mPositioner.isStackOnLeft(mCollapsePoint); - final PointF p = mPositioner.getExpandedBubbleXY(index, - mLayout.getChildCount(), - onLeft); + final PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleStackView.getState()); if (mPositioner.showBubblesVertically()) { child.setTranslationY(p.y); } else { child.setTranslationX(p.x); } - if (!mPreparingToCollapse) { - // Only animate if we're not collapsing as that animation will handle placing the + + if (mPreparingToCollapse) { + // Don't animate if we're collapsing, as that animation will handle placing the // new bubble in the stacked position. - if (mPositioner.showBubblesVertically()) { - Rect availableRect = mPositioner.getAvailableRect(); - float fromX = onLeft - ? -mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR - : availableRect.right + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR; - animationForChild(child) - .translationX(fromX, p.y) - .start(); - } else { - // Only animate if we're not collapsing as that animation will handle placing - // the new bubble in the stacked position. - float fromY = mPositioner.getExpandedBubblesY() - mBubbleSizePx - * ANIMATE_TRANSLATION_FACTOR; - animationForChild(child) - .translationY(fromY, p.y) - .start(); - } - updateBubblePositions(); + return; } + + if (mPositioner.showBubblesVertically()) { + float fromX = onLeft + ? p.x - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR + : p.x + mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR; + animationForChild(child) + .translationX(fromX, p.y) + .start(); + } else { + float fromY = p.y - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR; + animationForChild(child) + .translationY(fromY, p.y) + .start(); + } + updateBubblePositions(); } } @@ -572,7 +558,6 @@ public class ExpandedAnimationController if (mAnimatingExpand || mAnimatingCollapse) { return; } - boolean onLeft = mPositioner.isStackOnLeft(mCollapsePoint); for (int i = 0; i < mLayout.getChildCount(); i++) { final View bubble = mLayout.getChildAt(i); @@ -582,7 +567,7 @@ public class ExpandedAnimationController return; } - final PointF p = mPositioner.getExpandedBubbleXY(i, mLayout.getChildCount(), onLeft); + final PointF p = mPositioner.getExpandedBubbleXY(i, mBubbleStackView.getState()); animationForChild(bubble) .translationX(p.x) .translationY(p.y) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java index 9a08190675b6..60b64333114e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java @@ -127,9 +127,6 @@ public class StackAnimationController extends /** Whether or not the stack's start position has been set. */ private boolean mStackMovedToStartPosition = false; - /** The height of the most recently visible IME. */ - private float mImeHeight = 0f; - /** * The Y position of the stack before the IME became visible, or {@link Float#MIN_VALUE} if the * IME is not visible or the user moved the stack since the IME became visible. @@ -173,7 +170,7 @@ public class StackAnimationController extends */ private boolean mSpringToTouchOnNextMotionEvent = false; - /** Horizontal offset of bubbles in the stack. */ + /** Offset of bubbles in the stack (i.e. how much they overlap). */ private float mStackOffset; /** Offset between stack y and animation y for bubble swap. */ private float mSwapAnimationOffset; @@ -521,16 +518,6 @@ public class StackAnimationController extends removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y); } - /** Save the current IME height so that we know where the stack bounds should be. */ - public void setImeHeight(int imeHeight) { - mImeHeight = imeHeight; - } - - /** Returns the current IME height that the stack is offset by. */ - public float getImeHeight() { - return mImeHeight; - } - /** * Animates the stack either away from the newly visible IME, or back to its original position * due to the IME going away. @@ -589,11 +576,14 @@ public class StackAnimationController extends */ public RectF getAllowableStackPositionRegion() { final RectF allowableRegion = new RectF(mPositioner.getAvailableRect()); + final int imeHeight = mPositioner.getImeHeight(); + final float bottomPadding = getBubbleCount() > 1 + ? mBubblePaddingTop + mStackOffset + : mBubblePaddingTop; allowableRegion.left -= mBubbleOffscreen; allowableRegion.top += mBubblePaddingTop; allowableRegion.right += mBubbleOffscreen - mBubbleSize; - allowableRegion.bottom -= mBubblePaddingTop + mBubbleSize - + (mImeHeight != UNSET ? mImeHeight + mBubblePaddingTop : 0f); + allowableRegion.bottom -= imeHeight + bottomPadding + mBubbleSize; return allowableRegion; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java new file mode 100644 index 000000000000..364bb651d55d --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common.split; + +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT; +import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT; +import static android.view.RoundedCorner.POSITION_TOP_LEFT; +import static android.view.RoundedCorner.POSITION_TOP_RIGHT; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Point; +import android.util.AttributeSet; +import android.view.RoundedCorner; +import android.view.View; + +import androidx.annotation.Nullable; + +import com.android.wm.shell.R; + +/** + * Draws inverted rounded corners beside divider bar to keep splitting tasks cropped with proper + * rounded corners. + */ +public class DividerRoundedCorner extends View { + private final int mDividerWidth; + private final Paint mDividerBarBackground; + private final Point mStartPos = new Point(); + private InvertedRoundedCornerDrawInfo mTopLeftCorner; + private InvertedRoundedCornerDrawInfo mTopRightCorner; + private InvertedRoundedCornerDrawInfo mBottomLeftCorner; + private InvertedRoundedCornerDrawInfo mBottomRightCorner; + + public DividerRoundedCorner(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + mDividerWidth = getResources().getDimensionPixelSize(R.dimen.split_divider_bar_width); + mDividerBarBackground = new Paint(); + mDividerBarBackground.setColor( + getResources().getColor(R.color.split_divider_background, null)); + mDividerBarBackground.setFlags(Paint.ANTI_ALIAS_FLAG); + mDividerBarBackground.setStyle(Paint.Style.FILL); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mTopLeftCorner = new InvertedRoundedCornerDrawInfo(POSITION_TOP_LEFT); + mTopRightCorner = new InvertedRoundedCornerDrawInfo(POSITION_TOP_RIGHT); + mBottomLeftCorner = new InvertedRoundedCornerDrawInfo(POSITION_BOTTOM_LEFT); + mBottomRightCorner = new InvertedRoundedCornerDrawInfo(POSITION_BOTTOM_RIGHT); + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.save(); + + mTopLeftCorner.calculateStartPos(mStartPos); + canvas.translate(mStartPos.x, mStartPos.y); + canvas.drawPath(mTopLeftCorner.mPath, mDividerBarBackground); + + canvas.translate(-mStartPos.x, -mStartPos.y); + mTopRightCorner.calculateStartPos(mStartPos); + canvas.translate(mStartPos.x, mStartPos.y); + canvas.drawPath(mTopRightCorner.mPath, mDividerBarBackground); + + canvas.translate(-mStartPos.x, -mStartPos.y); + mBottomLeftCorner.calculateStartPos(mStartPos); + canvas.translate(mStartPos.x, mStartPos.y); + canvas.drawPath(mBottomLeftCorner.mPath, mDividerBarBackground); + + canvas.translate(-mStartPos.x, -mStartPos.y); + mBottomRightCorner.calculateStartPos(mStartPos); + canvas.translate(mStartPos.x, mStartPos.y); + canvas.drawPath(mBottomRightCorner.mPath, mDividerBarBackground); + + canvas.restore(); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } + + private boolean isLandscape() { + return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE; + } + + /** + * Holds draw information of the inverted rounded corner at a specific position. + * + * @see {@link com.android.launcher3.taskbar.TaskbarDragLayer} + */ + private class InvertedRoundedCornerDrawInfo { + @RoundedCorner.Position + private final int mCornerPosition; + + private final int mRadius; + + private final Path mPath = new Path(); + + InvertedRoundedCornerDrawInfo(@RoundedCorner.Position int cornerPosition) { + mCornerPosition = cornerPosition; + + final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(cornerPosition); + mRadius = roundedCorner == null ? 0 : roundedCorner.getRadius(); + + // Starts with a filled square, and then subtracting out a circle from the appropriate + // corner. + final Path square = new Path(); + square.addRect(0, 0, mRadius, mRadius, Path.Direction.CW); + final Path circle = new Path(); + circle.addCircle( + isLeftCorner() ? mRadius : 0 /* x */, + isTopCorner() ? mRadius : 0 /* y */, + mRadius, Path.Direction.CW); + mPath.op(square, circle, Path.Op.DIFFERENCE); + } + + private void calculateStartPos(Point outPos) { + if (isLandscape()) { + // Place left corner at the right side of the divider bar. + outPos.x = isLeftCorner() + ? getWidth() / 2 + mDividerWidth / 2 + : getWidth() / 2 - mDividerWidth / 2 - mRadius; + outPos.y = isTopCorner() ? 0 : getHeight() - mRadius; + } else { + outPos.x = isLeftCorner() ? 0 : getWidth() - mRadius; + // Place top corner at the bottom of the divider bar. + outPos.y = isTopCorner() + ? getHeight() / 2 + mDividerWidth / 2 + : getHeight() / 2 - mDividerWidth / 2 - mRadius; + } + } + + private boolean isLeftCorner() { + return mCornerPosition == POSITION_TOP_LEFT || mCornerPosition == POSITION_BOTTOM_LEFT; + } + + private boolean isTopCorner() { + return mCornerPosition == POSITION_TOP_LEFT || mCornerPosition == POSITION_TOP_RIGHT; + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index f35c8e1c5929..f9ba97f8f9b1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -24,7 +24,6 @@ import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Property; -import android.util.TypedValue; import android.view.GestureDetector; import android.view.InsetsSource; import android.view.InsetsState; @@ -178,10 +177,12 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { return true; } + // Convert to use screen-based coordinates to prevent lost track of motion events while + // moving divider bar and calculating dragging velocity. + event.setLocation(event.getRawX(), event.getRawY()); final int action = event.getAction() & MotionEvent.ACTION_MASK; final boolean isLandscape = isLandscape(); - // Using raw xy to prevent lost track of motion events while moving divider bar. - final int touchPos = isLandscape ? (int) event.getRawX() : (int) event.getRawY(); + final int touchPos = (int) (isLandscape ? event.getX() : event.getY()); switch (action) { case MotionEvent.ACTION_DOWN: mVelocityTracker = VelocityTracker.obtain(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 754b8dadbfad..27c8d7ac0032 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -125,8 +125,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mRotation = configuration.windowConfiguration.getRotation(); mSplitLayoutHandler = splitLayoutHandler; mDisplayImeController = displayImeController; - mSplitWindowManager = new SplitWindowManager( - windowName, mContext, configuration, parentContainerCallbacks); + mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration, + parentContainerCallbacks); mTaskOrganizer = taskOrganizer; mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId()); mDismissingParallaxPolicy = new DismissingParallaxPolicy(); @@ -181,22 +181,19 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public boolean updateConfiguration(Configuration configuration) { boolean affectsLayout = false; - // Make sure to render the divider bar with proper resources that matching the screen - // orientation. - final int orientation = configuration.orientation; - if (orientation != mOrientation) { - mOrientation = orientation; - mContext = mContext.createConfigurationContext(configuration); - mSplitWindowManager.setConfiguration(configuration); - affectsLayout = true; - } - // Update the split bounds when necessary. Besides root bounds changed, split bounds need to // be updated when the rotation changed to cover the case that users rotated the screen 180 // degrees. + // Make sure to render the divider bar with proper resources that matching the screen + // orientation. final int rotation = configuration.windowConfiguration.getRotation(); final Rect rootBounds = configuration.windowConfiguration.getBounds(); - if (rotation != mRotation || !mRootBounds.equals(rootBounds)) { + final int orientation = configuration.orientation; + if (rotation != mRotation || !mRootBounds.equals(rootBounds) + || orientation != mOrientation) { + mContext = mContext.createConfigurationContext(configuration); + mSplitWindowManager.setConfiguration(configuration); + mOrientation = orientation; mTempRect.set(mRootBounds); mRootBounds.set(rootBounds); mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java index fc7edfc4bceb..47dceb392183 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java @@ -64,6 +64,7 @@ public final class SplitWindowManager extends WindowlessWindowManager { public interface ParentContainerCallbacks { void attachToParentSurface(SurfaceControl.Builder b); + void onLeashReady(SurfaceControl leash); } public SplitWindowManager(String windowName, Context context, Configuration config, @@ -100,6 +101,7 @@ public final class SplitWindowManager extends WindowlessWindowManager { .setCallsite("SplitWindowManager#attachToParentSurface"); mParentContainerCallbacks.attachToParentSurface(builder); mLeash = builder.build(); + mParentContainerCallbacks.onLeashReady(mLeash); b.setParent(mLeash); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 954ca14b4960..e511bffad247 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -445,6 +445,9 @@ public class OneHandedController implements RemoteCallable<OneHandedController> mOneHandedSettingsUtil.registerSettingsKeyObserver( Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mContext.getContentResolver(), mShortcutEnabledObserver, newUserId); + mOneHandedSettingsUtil.registerSettingsKeyObserver( + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, + mContext.getContentResolver(), mShortcutEnabledObserver, newUserId); } private void unregisterSettingObservers() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index d9708f05e7dd..493870d7fd72 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -18,8 +18,6 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; @@ -90,6 +88,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.common.split.SplitLayout.SplitPosition; +import com.android.wm.shell.common.split.SplitWindowManager; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.transition.Transitions; @@ -163,6 +162,19 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDismissTop = NO_DISMISS; }; + private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = + new SplitWindowManager.ParentContainerCallbacks() { + @Override + public void attachToParentSurface(SurfaceControl.Builder b) { + mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b); + } + + @Override + public void onLeashReady(SurfaceControl leash) { + mSyncQueue.runInSync(t -> applyDividerVisibility(t)); + } + }; + StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer, DisplayImeController displayImeController, @@ -501,7 +513,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStage.removeAllTasks(wct, childrenToTop == mSideStage); mMainStage.deactivate(wct, childrenToTop == mMainStage); mTaskOrganizer.applyTransaction(wct); - // Reset divider position. + // Hide divider and reset its position. + setDividerVisibility(false); mSplitLayout.resetDividerPosition(); mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; if (childrenToTop != null) { @@ -635,10 +648,16 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void onStageVisibilityChanged(StageListenerImpl stageListener) { final boolean sideStageVisible = mSideStageListener.mVisible; final boolean mainStageVisible = mMainStageListener.mVisible; - // Divider is only visible if both the main stage and side stages are visible - setDividerVisibility(isSplitScreenVisible()); + final boolean bothStageVisible = sideStageVisible && mainStageVisible; + final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible; + final boolean sameVisibility = sideStageVisible == mainStageVisible; + // Only add or remove divider when both visible or both invisible to avoid sometimes we only + // got one stage visibility changed for a moment and it will cause flicker. + if (sameVisibility) { + setDividerVisibility(bothStageVisible); + } - if (!mainStageVisible && !sideStageVisible) { + if (bothStageInvisible) { if (mExitSplitScreenOnHide // Don't dismiss staged split when both stages are not visible due to sleeping display, // like the cases keyguard showing or screen off. @@ -655,59 +674,32 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, exitSplitScreen(toTop, SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP); } - // When both stage's visibility changed to visible, main stage might receives visibility - // changed before side stage if it has higher z-order than side stage. Make sure we only - // update main stage's windowing mode with the visibility changed of side stage to prevent - // stacking multiple windowing mode transactions which result to flicker issue. - if (mainStageVisible && stageListener == mSideStageListener) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - if (sideStageVisible) { - // The main stage configuration should to follow split layout when side stage is - // visible. - mMainStage.updateConfiguration( - WINDOWING_MODE_MULTI_WINDOW, getMainStageBounds(), wct); - } else if (!mSideStage.mRootTaskInfo.isSleeping) { - // We want the main stage configuration to be fullscreen when the side stage isn't - // visible. - // We should not do it when side stage are not visible due to sleeping display too. - mMainStage.updateConfiguration(WINDOWING_MODE_FULLSCREEN, null, wct); - } - // TODO: Change to `mSyncQueue.queue(wct)` once BLAST is stable. - mTaskOrganizer.applyTransaction(wct); - } - mSyncQueue.runInSync(t -> { final SurfaceControl sideStageLeash = mSideStage.mRootLeash; final SurfaceControl mainStageLeash = mMainStage.mRootLeash; if (sideStageVisible) { final Rect sideStageBounds = getSideStageBounds(); - t.show(sideStageLeash) - .setPosition(sideStageLeash, - sideStageBounds.left, sideStageBounds.top) + t.setPosition(sideStageLeash, + sideStageBounds.left, sideStageBounds.top) .setWindowCrop(sideStageLeash, sideStageBounds.width(), sideStageBounds.height()); - } else { - t.hide(sideStageLeash); } if (mainStageVisible) { final Rect mainStageBounds = getMainStageBounds(); - t.show(mainStageLeash); - if (sideStageVisible) { - t.setPosition(mainStageLeash, mainStageBounds.left, mainStageBounds.top) - .setWindowCrop(mainStageLeash, - mainStageBounds.width(), mainStageBounds.height()); - } else { - // Clear window crop and position if side stage isn't visible. - t.setPosition(mainStageLeash, 0, 0) - .setWindowCrop(mainStageLeash, null); - } - } else { - t.hide(mainStageLeash); + t.setPosition(mainStageLeash, mainStageBounds.left, mainStageBounds.top) + .setWindowCrop(mainStageLeash, + mainStageBounds.width(), mainStageBounds.height()); } - applyDividerVisibility(t); + // Same above, we only set root tasks and divider leash visibility when both stage + // change to visible or invisible to avoid flicker. + if (sameVisibility) { + t.setVisibility(sideStageLeash, bothStageVisible) + .setVisibility(mainStageLeash, bothStageVisible); + applyDividerVisibility(t); + } }); } @@ -726,7 +718,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } else { t.hide(dividerLeash); } - } private void onStageHasChildrenChanged(StageListenerImpl stageListener) { @@ -852,8 +843,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDisplayAreaInfo = displayAreaInfo; if (mSplitLayout == null) { mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext, - mDisplayAreaInfo.configuration, this, - b -> mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b), + mDisplayAreaInfo.configuration, this, mParentContainerCallbacks, mDisplayImeController, mTaskOrganizer); mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout); } @@ -871,7 +861,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration) && mMainStage.isActive()) { onLayoutChanged(mSplitLayout); - mSyncQueue.runInSync(t -> applyDividerVisibility(t)); } } 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 debe6d56dd92..ff3428cd9a6e 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 @@ -559,6 +559,15 @@ public class StartingSurfaceDrawer { removeWindowSynced(taskId, null, null, false); } + void onImeDrawnOnTask(int taskId) { + final StartingWindowRecord record = mStartingWindowRecords.get(taskId); + if (record != null && record.mTaskSnapshotWindow != null + && record.mTaskSnapshotWindow.hasImeSurface()) { + record.mTaskSnapshotWindow.removeImmediately(); + } + mStartingWindowRecords.remove(taskId); + } + protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame, boolean playRevealAnimation) { final StartingWindowRecord record = mStartingWindowRecords.get(taskId); @@ -586,14 +595,15 @@ public class StartingSurfaceDrawer { Slog.e(TAG, "Found empty splash screen, remove!"); removeWindowInner(record.mDecorView, false); } + mStartingWindowRecords.remove(taskId); } if (record.mTaskSnapshotWindow != null) { if (DEBUG_TASK_SNAPSHOT) { Slog.v(TAG, "Removing task snapshot window for " + taskId); } - record.mTaskSnapshotWindow.remove(); + record.mTaskSnapshotWindow.scheduleRemove( + () -> mStartingWindowRecords.remove(taskId)); } - mStartingWindowRecords.remove(taskId); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java index a5c47c41180e..4433e275cd1b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java @@ -134,7 +134,7 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken, suggestionType); } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) { - final TaskSnapshot snapshot = windowInfo.mTaskSnapshot; + final TaskSnapshot snapshot = windowInfo.taskSnapshot; mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken, snapshot); } @@ -177,6 +177,13 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo } /** + * Called when the IME has drawn on the organized task. + */ + public void onImeDrawnOnTask(int taskId) { + mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.onImeDrawnOnTask(taskId)); + } + + /** * Called when the content of a task is ready to show, starting window can be removed. */ public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index cdc0795e0331..3538fd2d84a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -16,7 +16,6 @@ package com.android.wm.shell.startingsurface; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.graphics.Color.WHITE; import static android.graphics.Color.alpha; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; @@ -64,7 +63,6 @@ import android.graphics.RectF; import android.hardware.HardwareBuffer; import android.os.IBinder; import android.os.RemoteException; -import android.os.SystemClock; import android.os.Trace; import android.util.MergedConfiguration; import android.util.Slog; @@ -72,7 +70,6 @@ import android.view.IWindowSession; import android.view.InputChannel; import android.view.InsetsSourceControl; import android.view.InsetsState; -import android.view.InsetsVisibilities; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.View; @@ -120,7 +117,12 @@ public class TaskSnapshotWindow { private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s"; private static final long DELAY_REMOVAL_TIME_GENERAL = 100; - private static final long DELAY_REMOVAL_TIME_IME_VISIBLE = 350; + /** + * The max delay time in milliseconds for removing the task snapshot window with IME visible. + * Ideally the delay time will be shorter when receiving + * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}. + */ + private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 450; //tmp vars for unused relayout params private static final Point TMP_SURFACE_SIZE = new Point(); @@ -139,7 +141,6 @@ public class TaskSnapshotWindow { private final RectF mTmpDstFrame = new RectF(); private final CharSequence mTitle; private boolean mHasDrawn; - private long mShownTime; private boolean mSizeMismatch; private final Paint mBackgroundPaint = new Paint(); private final int mActivityType; @@ -149,6 +150,8 @@ public class TaskSnapshotWindow { private final SurfaceControl.Transaction mTransaction; private final Matrix mSnapshotMatrix = new Matrix(); private final float[] mTmpFloat9 = new float[9]; + private Runnable mScheduledRunnable; + private final boolean mHasImeSurface; static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken, TaskSnapshot snapshot, ShellExecutor splashScreenExecutor, @@ -217,7 +220,7 @@ public class TaskSnapshotWindow { taskDescription.setBackgroundColor(WHITE); } - final long delayRemovalTime = snapshot.hasImeSurface() ? DELAY_REMOVAL_TIME_IME_VISIBLE + final long delayRemovalTime = snapshot.hasImeSurface() ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE : DELAY_REMOVAL_TIME_GENERAL; final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow( @@ -227,13 +230,12 @@ public class TaskSnapshotWindow { final Window window = snapshotSurface.mWindow; final InsetsState tmpInsetsState = new InsetsState(); - final InsetsVisibilities tmpRequestedVisibilities = new InsetsVisibilities(); final InputChannel tmpInputChannel = new InputChannel(); try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay"); final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId, - tmpRequestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls); + info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (res < 0) { Slog.w(TAG, "Failed to add snapshot starting window res=" + res); @@ -283,12 +285,17 @@ public class TaskSnapshotWindow { mDelayRemovalTime = delayRemovalTime; mTransaction = new SurfaceControl.Transaction(); mClearWindowHandler = clearWindowHandler; + mHasImeSurface = snapshot.hasImeSurface(); } int getBackgroundColor() { return mBackgroundPaint.getColor(); } + boolean hasImeSurface() { + return mHasImeSurface; + } + /** * Ask system bar background painter to draw status bar background. * @hide @@ -306,21 +313,26 @@ public class TaskSnapshotWindow { mSystemBarBackgroundPainter.drawNavigationBarBackground(c); } - void remove() { - final long now = SystemClock.uptimeMillis(); - if ((now - mShownTime < mDelayRemovalTime) - // Show the latest content as soon as possible for unlocking to home. - && mActivityType != ACTIVITY_TYPE_HOME) { - final long delayTime = mShownTime + mDelayRemovalTime - now; - mSplashScreenExecutor.executeDelayed(() -> remove(), delayTime); - if (DEBUG) { - Slog.d(TAG, "Defer removing snapshot surface in " + delayTime); - } - return; + void scheduleRemove(Runnable onRemove) { + if (mScheduledRunnable != null) { + mSplashScreenExecutor.removeCallbacks(mScheduledRunnable); + mScheduledRunnable = null; + } + mScheduledRunnable = () -> { + TaskSnapshotWindow.this.removeImmediately(); + onRemove.run(); + }; + mSplashScreenExecutor.executeDelayed(mScheduledRunnable, mDelayRemovalTime); + if (DEBUG) { + Slog.d(TAG, "Defer removing snapshot surface in " + mDelayRemovalTime); } + } + + void removeImmediately() { + mSplashScreenExecutor.removeCallbacks(mScheduledRunnable); try { if (DEBUG) { - Slog.d(TAG, "Removing snapshot surface, mHasDrawn: " + mHasDrawn); + Slog.d(TAG, "Removing taskSnapshot surface, mHasDrawn: " + mHasDrawn); } mSession.remove(mWindow); } catch (RemoteException e) { @@ -358,7 +370,6 @@ public class TaskSnapshotWindow { } else { drawSizeMatchSnapshot(); } - mShownTime = SystemClock.uptimeMillis(); mHasDrawn = true; reportDrawn(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java index 848eff4b56f3..e6d6028b2aba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java @@ -107,7 +107,7 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor * rotation must be the same). */ private boolean isSnapshotCompatible(StartingWindowInfo windowInfo) { - final TaskSnapshot snapshot = windowInfo.mTaskSnapshot; + final TaskSnapshot snapshot = windowInfo.taskSnapshot; if (snapshot == null) { if (DEBUG_SPLASH_SCREEN || DEBUG_TASK_SNAPSHOT) { Slog.d(TAG, "isSnapshotCompatible no snapshot " + windowInfo.taskInfo.taskId); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java index 9732a8890e0e..2b9bdce45a6c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java @@ -16,10 +16,12 @@ package com.android.wm.shell.bubbles.animation; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static org.mockito.Mockito.when; import android.annotation.SuppressLint; import android.content.res.Configuration; @@ -37,6 +39,7 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.BubblePositioner; +import com.android.wm.shell.bubbles.BubbleStackView; import org.junit.Before; import org.junit.Ignore; @@ -56,18 +59,22 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC private int mStackOffset; private PointF mExpansionPoint; private BubblePositioner mPositioner; + private BubbleStackView.StackViewState mStackViewState; @SuppressLint("VisibleForTests") @Before public void setUp() throws Exception { super.setUp(); + BubbleStackView stackView = mock(BubbleStackView.class); + when(stackView.getState()).thenReturn(getStackViewState()); mPositioner = new BubblePositioner(getContext(), mock(WindowManager.class)); mPositioner.updateInternal(Configuration.ORIENTATION_PORTRAIT, Insets.of(0, 0, 0, 0), new Rect(0, 0, mDisplayWidth, mDisplayHeight)); mExpandedController = new ExpandedAnimationController(mPositioner, - mOnBubbleAnimatedOutAction); + mOnBubbleAnimatedOutAction, + stackView); spyOn(mExpandedController); addOneMoreThanBubbleLimitBubbles(); @@ -78,6 +85,13 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC mExpansionPoint = new PointF(100, 100); } + public BubbleStackView.StackViewState getStackViewState() { + mStackViewState.numberOfBubbles = mLayout.getChildCount(); + mStackViewState.selectedIndex = 0; + mStackViewState.onLeft = mPositioner.isStackOnLeft(mExpansionPoint); + return mStackViewState; + } + @Test @Ignore public void testExpansionAndCollapse() throws InterruptedException { @@ -141,12 +155,10 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC /** Check that children are in the correct positions for being expanded. */ private void testBubblesInCorrectExpandedPositions() { - boolean onLeft = mPositioner.isStackOnLeft(mExpansionPoint); // Check all the visible bubbles to see if they're in the right place. for (int i = 0; i < mLayout.getChildCount(); i++) { PointF expectedPosition = mPositioner.getExpandedBubbleXY(i, - mLayout.getChildCount(), - onLeft); + getStackViewState()); assertEquals(expectedPosition.x, mLayout.getChildAt(i).getTranslationX(), 2f); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 3557906531b2..defa58d7fe93 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -29,7 +29,6 @@ import static org.mockito.Mockito.verify; import android.content.res.Configuration; import android.graphics.Rect; -import android.view.SurfaceControl; import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -53,7 +52,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class SplitLayoutTests extends ShellTestCase { @Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler; - @Mock SurfaceControl mRootLeash; + @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks; @Mock DisplayImeController mDisplayImeController; @Mock ShellTaskOrganizer mTaskOrganizer; @Captor ArgumentCaptor<Runnable> mRunnableCaptor; @@ -67,7 +66,7 @@ public class SplitLayoutTests extends ShellTestCase { mContext, getConfiguration(), mSplitLayoutHandler, - b -> b.setParent(mRootLeash), + mCallbacks, mDisplayImeController, mTaskOrganizer)); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java index c456c7de8821..9bb54a18063f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java @@ -23,7 +23,6 @@ import static org.mockito.Mockito.when; import android.content.res.Configuration; import android.graphics.Rect; import android.view.InsetsState; -import android.view.SurfaceControl; import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -41,8 +40,8 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) public class SplitWindowManagerTests extends ShellTestCase { - @Mock SurfaceControl mSurfaceControl; @Mock SplitLayout mSplitLayout; + @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks; private SplitWindowManager mSplitWindowManager; @Before @@ -51,7 +50,7 @@ public class SplitWindowManagerTests extends ShellTestCase { final Configuration configuration = new Configuration(); configuration.setToDefaults(); mSplitWindowManager = new SplitWindowManager("TestSplitDivider", mContext, configuration, - b -> b.setParent(mSurfaceControl)); + mCallbacks); when(mSplitLayout.getDividerBounds()).thenReturn( new Rect(0, 0, configuration.windowConfiguration.getBounds().width(), configuration.windowConfiguration.getBounds().height())); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index 2994e7181369..36722d9147ab 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -15,28 +15,38 @@ */ package com.android.wm.shell.startingsurface; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.graphics.ColorSpace; +import android.graphics.Point; import android.graphics.Rect; +import android.hardware.HardwareBuffer; import android.net.Uri; import android.os.Handler; import android.os.IBinder; @@ -44,11 +54,16 @@ import android.os.Looper; import android.os.UserHandle; import android.testing.TestableContext; import android.view.Display; +import android.view.IWindowSession; +import android.view.InsetsState; +import android.view.Surface; import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import android.view.WindowMetrics; import android.window.StartingWindowInfo; +import android.window.TaskSnapshot; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -63,6 +78,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; import java.util.function.IntSupplier; @@ -80,6 +96,7 @@ public class StartingSurfaceDrawerTests { private TransactionPool mTransactionPool; private final Handler mTestHandler = new Handler(Looper.getMainLooper()); + private ShellExecutor mTestExecutor; private final TestableContext mTestContext = new TestContext( InstrumentationRegistry.getInstrumentation().getTargetContext()); TestStartingSurfaceDrawer mStartingSurfaceDrawer; @@ -138,9 +155,9 @@ public class StartingSurfaceDrawerTests { doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics(); doNothing().when(mMockWindowManager).addView(any(), any()); - - mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(mTestContext, - new HandlerExecutor(mTestHandler), mTransactionPool)); + mTestExecutor = new HandlerExecutor(mTestHandler); + mStartingSurfaceDrawer = spy( + new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mTransactionPool)); } @Test @@ -208,6 +225,48 @@ public class StartingSurfaceDrawerTests { assertEquals(0, windowColor3.mReuseCount); } + @Test + public void testRemoveTaskSnapshotWithImeSurfaceWhenOnImeDrawn() throws Exception { + final int taskId = 1; + final StartingWindowInfo windowInfo = + createWindowInfo(taskId, android.R.style.Theme); + TaskSnapshot snapshot = createTaskSnapshot(100, 100, new Point(100, 100), + new Rect(0, 0, 0, 50), true /* hasImeSurface */); + final IWindowSession session = WindowManagerGlobal.getWindowSession(); + spyOn(session); + doReturn(WindowManagerGlobal.ADD_OKAY).when(session).addToDisplay( + any() /* window */, any() /* attrs */, + anyInt() /* viewVisibility */, anyInt() /* displayId */, + any() /* requestedVisibility */, any() /* outInputChannel */, + any() /* outInsetsState */, any() /* outActiveControls */); + TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo, + mBinder, + snapshot, mTestExecutor, () -> { + }); + spyOn(mockSnapshotWindow); + try (AutoCloseable mockTaskSnapshotSession = new AutoCloseable() { + MockitoSession mockSession = mockitoSession() + .initMocks(this) + .mockStatic(TaskSnapshotWindow.class) + .startMocking(); + @Override + public void close() { + mockSession.finishMocking(); + } + }) { + when(TaskSnapshotWindow.create(eq(windowInfo), eq(mBinder), eq(snapshot), any(), + any())).thenReturn(mockSnapshotWindow); + // Simulate a task snapshot window created with IME snapshot shown. + mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, mBinder, snapshot); + waitHandlerIdle(mTestHandler); + + // Verify the task snapshot with IME snapshot will be removed when received the real IME + // drawn callback. + mStartingSurfaceDrawer.onImeDrawnOnTask(1); + verify(mockSnapshotWindow).removeImmediately(); + } + } + private StartingWindowInfo createWindowInfo(int taskId, int themeResId) { StartingWindowInfo windowInfo = new StartingWindowInfo(); final ActivityInfo info = new ActivityInfo(); @@ -219,10 +278,27 @@ public class StartingSurfaceDrawerTests { taskInfo.taskId = taskId; windowInfo.targetActivityInfo = info; windowInfo.taskInfo = taskInfo; + windowInfo.topOpaqueWindowInsetsState = new InsetsState(); + windowInfo.mainWindowLayoutParams = new WindowManager.LayoutParams(); + windowInfo.topOpaqueWindowLayoutParams = new WindowManager.LayoutParams(); return windowInfo; } private static void waitHandlerIdle(Handler handler) { handler.runWithScissors(() -> { }, 0 /* timeout */); } + + private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize, + Rect contentInsets, boolean hasImeSurface) { + final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, + 1, HardwareBuffer.USAGE_CPU_READ_RARELY); + return new TaskSnapshot( + System.currentTimeMillis(), + new ComponentName("", ""), buffer, + ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, + Surface.ROTATION_0, taskSize, contentInsets, false, + true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, + 0 /* systemUiVisibility */, false /* isTranslucent */, + hasImeSurface /* hasImeSurface */); + } } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 5db9ddfac870..38f9607c9529 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2443,17 +2443,11 @@ public class AudioManager { /** * Return a handle to the optional platform's {@link Spatializer} - * @return {@code null} if spatialization is not supported, the {@code Spatializer} instance - * otherwise. + * @return the {@code Spatializer} instance. + * @see Spatializer#getImmersiveAudioLevel() to check for the level of support of the effect + * on the platform */ - public @Nullable Spatializer getSpatializer() { - int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; - try { - level = getService().getSpatializerImmersiveAudioLevel(); - } catch (Exception e) { /* using NONE */ } - if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { - return null; - } + public @NonNull Spatializer getSpatializer() { return new Spatializer(this); } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 8480c5289d15..bc1040115417 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -36,6 +36,8 @@ import android.media.IRecordingConfigDispatcher; import android.media.IRingtonePlayer; import android.media.IStrategyPreferredDevicesDispatcher; import android.media.ISpatializerCallback; +import android.media.ISpatializerHeadTrackingModeCallback; +import android.media.ISpatializerHeadToSoundStagePoseCallback; import android.media.IVolumeController; import android.media.IVolumeController; import android.media.PlayerBase; @@ -405,13 +407,33 @@ interface IAudioService { boolean canBeSpatialized(in AudioAttributes aa, in AudioFormat af); - void registerSpatializerCallback(in ISpatializerCallback callback); + void registerSpatializerCallback(in ISpatializerCallback cb); - void unregisterSpatializerCallback(in ISpatializerCallback callback); + void unregisterSpatializerCallback(in ISpatializerCallback cb); + + void registerSpatializerHeadTrackingCallback(in ISpatializerHeadTrackingModeCallback cb); + + void unregisterSpatializerHeadTrackingCallback(in ISpatializerHeadTrackingModeCallback cb); + + void registerHeadToSoundstagePoseCallback(in ISpatializerHeadToSoundStagePoseCallback cb); + + void unregisterHeadToSoundstagePoseCallback(in ISpatializerHeadToSoundStagePoseCallback cb); List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices(); void addSpatializerCompatibleAudioDevice(in AudioDeviceAttributes ada); void removeSpatializerCompatibleAudioDevice(in AudioDeviceAttributes ada); + + void setDesiredHeadTrackingMode(int mode); + + int getDesiredHeadTrackingMode(); + + int[] getSupportedHeadTrackingModes(); + + int getActualHeadTrackingMode(); + + oneway void setSpatializerGlobalTransform(in float[] transform); + + oneway void recenterHeadTracker(); } diff --git a/media/java/android/media/ISpatializerHeadToSoundStagePoseCallback.aidl b/media/java/android/media/ISpatializerHeadToSoundStagePoseCallback.aidl new file mode 100644 index 000000000000..01a146599394 --- /dev/null +++ b/media/java/android/media/ISpatializerHeadToSoundStagePoseCallback.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +/** + * AIDL for the AudioService to signal Spatializer state changes. + * + * {@hide} + */ +oneway interface ISpatializerHeadToSoundStagePoseCallback { + + /** + * The pose is sent as an array of 6 float values, the first 3 are the translation vector, the + * other 3 are the rotation vector. + */ + void dispatchPoseChanged(in float[] pose); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java b/media/java/android/media/ISpatializerHeadTrackingModeCallback.aidl index b36b67dc02c0..c61f86e4c60e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.java +++ b/media/java/android/media/ISpatializerHeadTrackingModeCallback.aidl @@ -14,26 +14,16 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package android.media; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.util.ViewController; - -/** Controller for {@link PhoneStatusBarView}. */ -public class PhoneStatusBarViewController extends ViewController<PhoneStatusBarView> { - - protected PhoneStatusBarViewController( - PhoneStatusBarView view, - CommandQueue commandQueue) { - super(view); - mView.setPanelEnabledProvider(commandQueue::panelsEnabled); - } +/** + * AIDL for the AudioService to signal Spatializer head tracking mode changes. + * + * {@hide} + */ +oneway interface ISpatializerHeadTrackingModeCallback { - @Override - protected void onViewAttached() { - } + void dispatchSpatializerActualHeadTrackingModeChanged(int mode); - @Override - protected void onViewDetached() { - } + void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode); } diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java index 3ed8b58959a1..b062eea5017c 100644 --- a/media/java/android/media/Spatializer.java +++ b/media/java/android/media/Spatializer.java @@ -49,21 +49,9 @@ public class Spatializer { private final @NonNull AudioManager mAm; - private final Object mStateListenerLock = new Object(); - private static final String TAG = "Spatializer"; /** - * List of listeners for state listener and their associated Executor. - * List is lazy-initialized on first registration - */ - @GuardedBy("mStateListenerLock") - private @Nullable ArrayList<StateListenerInfo> mStateListeners; - - @GuardedBy("mStateListenerLock") - private SpatializerInfoDispatcherStub mInfoDispatcherStub; - - /** * @hide * Constructor with AudioManager acting as proxy to AudioService * @param am a non-null AudioManager @@ -114,6 +102,7 @@ public class Spatializer { /** @hide */ @IntDef(flag = false, value = { + SPATIALIZER_IMMERSIVE_LEVEL_OTHER, SPATIALIZER_IMMERSIVE_LEVEL_NONE, SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL, }) @@ -121,20 +110,110 @@ public class Spatializer { public @interface ImmersiveAudioLevel {}; /** - * @hide + * Constant indicating the {@code Spatializer} on this device supports a spatialization + * mode that differs from the ones available at this SDK level. + * @see #getImmersiveAudioLevel() + */ + public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1; + + /** * Constant indicating there are no spatialization capabilities supported on this device. - * @see AudioManager#getImmersiveAudioLevel() + * @see #getImmersiveAudioLevel() */ public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0; /** - * @hide - * Constant indicating the {@link Spatializer} on this device supports multichannel + * Constant indicating the {@code Spatializer} on this device supports multichannel * spatialization. - * @see AudioManager#getImmersiveAudioLevel() + * @see #getImmersiveAudioLevel() */ public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1; + /** @hide */ + @IntDef(flag = false, value = { + HEAD_TRACKING_MODE_UNSUPPORTED, + HEAD_TRACKING_MODE_DISABLED, + HEAD_TRACKING_MODE_RELATIVE_WORLD, + HEAD_TRACKING_MODE_RELATIVE_DEVICE, + }) public @interface HeadTrackingMode {}; + + /** @hide */ + @IntDef(flag = false, value = { + HEAD_TRACKING_MODE_DISABLED, + HEAD_TRACKING_MODE_RELATIVE_WORLD, + HEAD_TRACKING_MODE_RELATIVE_DEVICE, + }) public @interface HeadTrackingModeSet {}; + + /** @hide */ + @IntDef(flag = false, value = { + HEAD_TRACKING_MODE_RELATIVE_WORLD, + HEAD_TRACKING_MODE_RELATIVE_DEVICE, + }) public @interface HeadTrackingModeSupported {}; + + /** + * @hide + * Constant indicating head tracking is not supported by this {@code Spatializer} + * @see #getHeadTrackingMode() + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public static final int HEAD_TRACKING_MODE_UNSUPPORTED = -2; + + /** + * @hide + * Constant indicating head tracking is disabled on this {@code Spatializer} + * @see #getHeadTrackingMode() + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public static final int HEAD_TRACKING_MODE_DISABLED = -1; + + /** + * @hide + * Constant indicating head tracking is in a mode whose behavior is unknown. This is not an + * error state but represents a customized behavior not defined by this API. + * @see #getHeadTrackingMode() + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public static final int HEAD_TRACKING_MODE_OTHER = 0; + + /** + * @hide + * Constant indicating head tracking is tracking the user's position / orientation relative to + * the world around them + * @see #getHeadTrackingMode() + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public static final int HEAD_TRACKING_MODE_RELATIVE_WORLD = 1; + + /** + * @hide + * Constant indicating head tracking is tracking the user's position / orientation relative to + * the device + * @see #getHeadTrackingMode() + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2; + + /** + * Return the level of support for the spatialization feature on this device. + * This level of support is independent of whether the {@code Spatializer} is currently + * enabled or available and will not change over time. + * @return the level of spatialization support + * @see #isEnabled() + * @see #isAvailable() + */ + public @ImmersiveAudioLevel int getImmersiveAudioLevel() { + int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + try { + level = mAm.getService().getSpatializerImmersiveAudioLevel(); + } catch (Exception e) { /* using NONE */ } + return level; + } + /** * @hide * Enables / disables the spatializer effect. @@ -154,7 +233,7 @@ public class Spatializer { } /** - * An interface to be notified of changes to the state of the spatializer. + * An interface to be notified of changes to the state of the spatializer effect. */ public interface OnSpatializerStateChangedListener { /** @@ -178,6 +257,58 @@ public class Spatializer { } /** + * @hide + * An interface to be notified of changes to the head tracking mode, used by the spatializer + * effect. + * Changes to the mode may come from explicitly setting a different mode + * (see {@link #setDesiredHeadTrackingMode(int)}) or a change in system conditions (see + * {@link #getHeadTrackingMode()} + * @see #addOnHeadTrackingModeChangedListener(Executor, OnHeadTrackingModeChangedListener) + * @see #removeOnHeadTrackingModeChangedListener(OnHeadTrackingModeChangedListener) + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + public interface OnHeadTrackingModeChangedListener { + /** + * Called when the actual head tracking mode of the spatializer changed. + * @param spatializer the {@code Spatializer} instance whose head tracking mode is changing + * @param mode the new head tracking mode + */ + void onHeadTrackingModeChanged(@NonNull Spatializer spatializer, + @HeadTrackingMode int mode); + + /** + * Called when the desired head tracking mode of the spatializer changed + * @param spatializer the {@code Spatializer} instance whose head tracking mode was set + * @param mode the newly set head tracking mode + */ + void onDesiredHeadTrackingModeChanged(@NonNull Spatializer spatializer, + @HeadTrackingModeSet int mode); + } + + /** + * @hide + * An interface to be notified of updates to the head to soundstage pose, as represented by the + * current head tracking mode. + * @see #setOnHeadToSoundstagePoseUpdatedListener(Executor, OnHeadToSoundstagePoseUpdatedListener) + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + public interface OnHeadToSoundstagePoseUpdatedListener { + /** + * Called when the head to soundstage transform is updated + * @param spatializer the {@code Spatializer} instance affected by the pose update + * @param pose the new pose data representing the transform between the frame + * of reference for the current head tracking mode (see + * {@link #getHeadTrackingMode()}) and the device being tracked (for + * instance a pair of headphones with a head tracker).<br> + * The head pose data is represented as an array of six float values, where + * the first three values are the translation vector, and the next three + * are the rotation vector. + */ + void onHeadToSoundstagePoseUpdated(@NonNull Spatializer spatializer, + @NonNull float[] pose); + } + + /** * Returns whether audio of the given {@link AudioFormat}, played with the given * {@link AudioAttributes} can be spatialized. * Note that the result reflects the capabilities of the device and may change when @@ -320,6 +451,17 @@ public class Spatializer { } } + private final Object mStateListenerLock = new Object(); + /** + * List of listeners for state listener and their associated Executor. + * List is lazy-initialized on first registration + */ + @GuardedBy("mStateListenerLock") + private @Nullable ArrayList<StateListenerInfo> mStateListeners; + + @GuardedBy("mStateListenerLock") + private @Nullable SpatializerInfoDispatcherStub mInfoDispatcherStub; + private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub { @Override public void dispatchSpatializerEnabledChanged(boolean enabled) { @@ -401,4 +543,378 @@ public class Spatializer { } return false; } + + + /** + * @hide + * Return the current head tracking mode as used by the system. + * Note this may differ from the desired head tracking mode. Reasons for the two to differ + * include: a head tracking device is not available for the current audio output device, + * the transmission conditions between the tracker and device have deteriorated and tracking + * has been disabled. + * @see #getDesiredHeadTrackingMode() + * @return the current head tracking mode + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public @HeadTrackingMode int getHeadTrackingMode() { + try { + return mAm.getService().getActualHeadTrackingMode(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling getActualHeadTrackingMode", e); + return HEAD_TRACKING_MODE_UNSUPPORTED; + } + + } + + /** + * @hide + * Return the desired head tracking mode. + * Note this may differ from the actual head tracking mode, reflected by + * {@link #getHeadTrackingMode()}. + * @return the desired head tring mode + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public @HeadTrackingMode int getDesiredHeadTrackingMode() { + try { + return mAm.getService().getDesiredHeadTrackingMode(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling getDesiredHeadTrackingMode", e); + return HEAD_TRACKING_MODE_UNSUPPORTED; + } + } + + /** + * @hide + * Returns the list of supported head tracking modes. + * @return the list of modes that can be used in {@link #setDesiredHeadTrackingMode(int)} to + * enable head tracking. The list will be empty if {@link #getHeadTrackingMode()} + * is {@link #HEAD_TRACKING_MODE_UNSUPPORTED}. Values can be + * {@link #HEAD_TRACKING_MODE_OTHER}, + * {@link #HEAD_TRACKING_MODE_RELATIVE_WORLD} or + * {@link #HEAD_TRACKING_MODE_RELATIVE_DEVICE} + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public @NonNull List<Integer> getSupportedHeadTrackingModes() { + try { + final int[] modes = mAm.getService().getSupportedHeadTrackingModes(); + final ArrayList<Integer> list = new ArrayList<>(0); + for (int mode : modes) { + list.add(mode); + } + return list; + } catch (RemoteException e) { + Log.e(TAG, "Error calling getSupportedHeadTrackModes", e); + return new ArrayList(0); + } + } + + /** + * @hide + * Sets the desired head tracking mode. + * Note a set desired mode may differ from the actual head tracking mode. + * @see #getHeadTrackingMode() + * @param mode the desired head tracking mode, one of the values returned by + * {@link #getSupportedHeadTrackModes()}, or {@link #HEAD_TRACKING_MODE_DISABLED} to + * disable head tracking. + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public void setDesiredHeadTrackingMode(@HeadTrackingModeSet int mode) { + try { + mAm.getService().setDesiredHeadTrackingMode(mode); + } catch (RemoteException e) { + Log.e(TAG, "Error calling setDesiredHeadTrackingMode to " + mode, e); + } + } + + /** + * @hide + * Recenters the head tracking at the current position / orientation. + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public void recenterHeadTracker() { + try { + mAm.getService().recenterHeadTracker(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling recenterHeadTracker", e); + } + } + + /** + * @hide + * Adds a listener to be notified of changes to the head tracking mode of the + * {@code Spatializer} + * @param executor the {@code Executor} handling the callbacks + * @param listener the listener to register + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public void addOnHeadTrackingModeChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnHeadTrackingModeChangedListener listener) { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + synchronized (mHeadTrackingListenerLock) { + if (hasListener(listener, mHeadTrackingListeners)) { + throw new IllegalArgumentException( + "Called addOnHeadTrackingModeChangedListener() " + + "on a previously registered listener"); + } + // lazy initialization of the list of strategy-preferred device listener + if (mHeadTrackingListeners == null) { + mHeadTrackingListeners = new ArrayList<>(); + } + mHeadTrackingListeners.add( + new ListenerInfo<OnHeadTrackingModeChangedListener>(listener, executor)); + if (mHeadTrackingListeners.size() == 1) { + // register binder for callbacks + if (mHeadTrackingDispatcherStub == null) { + mHeadTrackingDispatcherStub = + new SpatializerHeadTrackingDispatcherStub(); + } + try { + mAm.getService().registerSpatializerHeadTrackingCallback( + mHeadTrackingDispatcherStub); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + + /** + * @hide + * Removes a previously added listener for changes to the head tracking mode of the + * {@code Spatializer}. + * @param listener the listener to unregister + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public void removeOnHeadTrackingModeChangedListener( + @NonNull OnHeadTrackingModeChangedListener listener) { + Objects.requireNonNull(listener); + synchronized (mHeadTrackingListenerLock) { + if (!removeListener(listener, mHeadTrackingListeners)) { + throw new IllegalArgumentException( + "Called removeOnHeadTrackingModeChangedListener() " + + "on an unregistered listener"); + } + if (mHeadTrackingListeners.size() == 0) { + // unregister binder for callbacks + try { + mAm.getService().unregisterSpatializerHeadTrackingCallback( + mHeadTrackingDispatcherStub); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } finally { + mHeadTrackingDispatcherStub = null; + mHeadTrackingListeners = null; + } + } + } + } + + /** + * @hide + * Set the listener to receive head to soundstage pose updates. + * @param executor the {@code Executor} handling the callbacks + * @param listener the listener to register + * @see #clearOnHeadToSoundstagePoseUpdatedListener() + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public void setOnHeadToSoundstagePoseUpdatedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnHeadToSoundstagePoseUpdatedListener listener) { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + synchronized (mPoseListenerLock) { + if (mPoseListener != null) { + throw new IllegalStateException("Trying to overwrite existing listener"); + } + mPoseListener = + new ListenerInfo<OnHeadToSoundstagePoseUpdatedListener>(listener, executor); + mPoseDispatcher = new SpatializerPoseDispatcherStub(); + try { + mAm.getService().registerHeadToSoundstagePoseCallback(mPoseDispatcher); + } catch (RemoteException e) { + mPoseListener = null; + mPoseDispatcher = null; + } + } + } + + /** + * @hide + * Clears the listener for head to soundstage pose updates + * @see #setOnHeadToSoundstagePoseUpdatedListener(Executor, OnHeadToSoundstagePoseUpdatedListener) + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public void clearOnHeadToSoundstagePoseUpdatedListener() { + synchronized (mPoseListenerLock) { + if (mPoseDispatcher == null) { + throw (new IllegalStateException("No listener to clear")); + } + try { + mAm.getService().unregisterHeadToSoundstagePoseCallback(mPoseDispatcher); + } catch (RemoteException e) { } + mPoseListener = null; + mPoseDispatcher = null; + } + } + + /** + * @hide + * Sets an additional transform over the soundstage. + * The transform represents the pose of the soundstage, relative + * to either the device (in {@link #HEAD_TRACKING_MODE_RELATIVE_DEVICE} mode), the world (in + * {@link #HEAD_TRACKING_MODE_RELATIVE_WORLD}) or the listener’s head (in + * {@link #HEAD_TRACKING_MODE_DISABLED} mode). + * @param transform an array of 6 float values, the first 3 are the translation vector, the + * other 3 are the rotation vector. + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public void setGlobalTransform(@NonNull float[] transform) { + if (Objects.requireNonNull(transform).length != 6) { + throw new IllegalArgumentException("transform array must be of size 6, was " + + transform.length); + } + try { + mAm.getService().setSpatializerGlobalTransform(transform); + } catch (RemoteException e) { + Log.e(TAG, "Error calling setGlobalTransform", e); + } + } + + //----------------------------------------------------------------------------- + // callback helper definitions + + private static class ListenerInfo<T> { + final @NonNull T mListener; + final @NonNull Executor mExecutor; + + ListenerInfo(T listener, Executor exe) { + mListener = listener; + mExecutor = exe; + } + } + + private static <T> ListenerInfo<T> getListenerInfo( + T listener, ArrayList<ListenerInfo<T>> listeners) { + if (listeners == null) { + return null; + } + for (ListenerInfo<T> info : listeners) { + if (info.mListener == listener) { + return info; + } + } + return null; + } + + private static <T> boolean hasListener(T listener, ArrayList<ListenerInfo<T>> listeners) { + return getListenerInfo(listener, listeners) != null; + } + + private static <T> boolean removeListener(T listener, ArrayList<ListenerInfo<T>> listeners) { + final ListenerInfo<T> infoToRemove = getListenerInfo(listener, listeners); + if (infoToRemove != null) { + listeners.remove(infoToRemove); + return true; + } + return false; + } + + //----------------------------------------------------------------------------- + // head tracking callback management and stub + + private final Object mHeadTrackingListenerLock = new Object(); + /** + * List of listeners for head tracking mode listener and their associated Executor. + * List is lazy-initialized on first registration + */ + @GuardedBy("mHeadTrackingListenerLock") + private @Nullable ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>> + mHeadTrackingListeners; + + @GuardedBy("mHeadTrackingListenerLock") + private @Nullable SpatializerHeadTrackingDispatcherStub mHeadTrackingDispatcherStub; + + private final class SpatializerHeadTrackingDispatcherStub + extends ISpatializerHeadTrackingModeCallback.Stub { + @Override + public void dispatchSpatializerActualHeadTrackingModeChanged(int mode) { + // make a shallow copy of listeners so callback is not executed under lock + final ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>> headTrackingListeners; + synchronized (mHeadTrackingListenerLock) { + if (mHeadTrackingListeners == null || mHeadTrackingListeners.size() == 0) { + return; + } + headTrackingListeners = (ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>) + mHeadTrackingListeners.clone(); + } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + for (ListenerInfo<OnHeadTrackingModeChangedListener> info : headTrackingListeners) { + info.mExecutor.execute(() -> info.mListener + .onHeadTrackingModeChanged(Spatializer.this, mode)); + } + } + } + + @Override + public void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode) { + // make a shallow copy of listeners so callback is not executed under lock + final ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>> headTrackingListeners; + synchronized (mHeadTrackingListenerLock) { + if (mHeadTrackingListeners == null || mHeadTrackingListeners.size() == 0) { + return; + } + headTrackingListeners = (ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>) + mHeadTrackingListeners.clone(); + } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + for (ListenerInfo<OnHeadTrackingModeChangedListener> info : headTrackingListeners) { + info.mExecutor.execute(() -> info.mListener + .onDesiredHeadTrackingModeChanged(Spatializer.this, mode)); + } + } + } + } + + //----------------------------------------------------------------------------- + // head pose callback management and stub + private final Object mPoseListenerLock = new Object(); + /** + * Listener for head to soundstage updates + */ + @GuardedBy("mPoseListenerLock") + private @Nullable ListenerInfo<OnHeadToSoundstagePoseUpdatedListener> mPoseListener; + @GuardedBy("mPoseListenerLock") + private @Nullable SpatializerPoseDispatcherStub mPoseDispatcher; + + private final class SpatializerPoseDispatcherStub + extends ISpatializerHeadToSoundStagePoseCallback.Stub { + + @Override + public void dispatchPoseChanged(float[] pose) { + // make a copy of ref to listener so callback is not executed under lock + final ListenerInfo<OnHeadToSoundstagePoseUpdatedListener> listener; + synchronized (mPoseListenerLock) { + listener = mPoseListener; + } + if (listener == null) { + return; + } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + listener.mExecutor.execute(() -> listener.mListener + .onHeadToSoundstagePoseUpdated(Spatializer.this, pose)); + } + } + } } diff --git a/packages/SettingsLib/res/values-as/arrays.xml b/packages/SettingsLib/res/values-as/arrays.xml index 50bfbe07b205..89739f6f6fc4 100644 --- a/packages/SettingsLib/res/values-as/arrays.xml +++ b/packages/SettingsLib/res/values-as/arrays.xml @@ -228,7 +228,7 @@ <item msgid="8612549335720461635">"৪কে. (সুৰক্ষিত)"</item> <item msgid="7322156123728520872">"৪কে. (বৰ্ধিত)"</item> <item msgid="7735692090314849188">"৪কে. (বৰ্ধিত, সুৰক্ষিত)"</item> - <item msgid="7346816300608639624">"৭২০পি., ১০৮০পি. (দ্বৈত স্ক্ৰীণ)"</item> + <item msgid="7346816300608639624">"৭২০পি., ১০৮০পি. (দ্বৈত স্ক্ৰীন)"</item> </string-array> <string-array name="enable_opengl_traces_entries"> <item msgid="4433736508877934305">"নাই"</item> @@ -243,7 +243,7 @@ </string-array> <string-array name="track_frame_time_entries"> <item msgid="634406443901014984">"অফ হৈ আছে"</item> - <item msgid="1288760936356000927">"স্ক্ৰীণত দণ্ড হিচাপে"</item> + <item msgid="1288760936356000927">"স্ক্ৰীনত দণ্ড হিচাপে"</item> <item msgid="5023908510820531131">"<xliff:g id="AS_TYPED_COMMAND">adb shell dumpsys gfxinfo</xliff:g>ত"</item> </string-array> <string-array name="debug_hw_overdraw_entries"> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index 1ae3452ac847..9f9b11d1e3b1 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -238,7 +238,7 @@ <string name="bugreport_in_power" msgid="8664089072534638709">"বাগ ৰিপৰ্টৰ শ্ৱৰ্টকাট"</string> <string name="bugreport_in_power_summary" msgid="1885529649381831775">"পাৱাৰ মেনুত বাগ প্ৰতিবেদন গ্ৰহণ কৰিবলৈ এটা বুটাম দেখুৱাওক"</string> <string name="keep_screen_on" msgid="1187161672348797558">"জাগ্ৰত কৰি ৰাখক"</string> - <string name="keep_screen_on_summary" msgid="1510731514101925829">"চ্চাৰ্জ হৈ থকাৰ সময়ত স্ক্ৰীণ কেতিয়াও সুপ্ত অৱস্থালৈ নাযায়"</string> + <string name="keep_screen_on_summary" msgid="1510731514101925829">"চ্চাৰ্জ হৈ থকাৰ সময়ত স্ক্ৰীন কেতিয়াও সুপ্ত অৱস্থালৈ নাযায়"</string> <string name="bt_hci_snoop_log" msgid="7291287955649081448">"ব্লুটুথ HCI স্নুপ ল’গ সক্ষম কৰক"</string> <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"ব্লুটুথ পেকেট সংগ্ৰহ কৰক। (এই ছেটিংটো সলনি কৰাৰ পিছত ব্লুটুথ ট’গল কৰক)"</string> <string name="oem_unlock_enable" msgid="5334869171871566731">"ঔইএম আনলক"</string> @@ -331,9 +331,9 @@ <string name="media_category" msgid="8122076702526144053">"মিডিয়া"</string> <string name="debug_monitoring_category" msgid="1597387133765424994">"নিৰীক্ষণ কৰি থকা হৈছে"</string> <string name="strict_mode" msgid="889864762140862437">"কঠোৰ ম’ড সক্ষম কৰা হৈছে"</string> - <string name="strict_mode_summary" msgid="1838248687233554654">"যেতিয়া এপসমূহে মুখ্য থ্ৰেডত দীঘলীয়া কাৰ্যকলাপ চলাই, তেতিয়া স্ক্ৰীণ ফ্লাশ্ব কৰক"</string> + <string name="strict_mode_summary" msgid="1838248687233554654">"যেতিয়া এপ্সমূহে মুখ্য থ্ৰেডত দীঘলীয়া কাৰ্যকলাপ চলাই, তেতিয়া স্ক্ৰীন ফ্লাশ্ব কৰক"</string> <string name="pointer_location" msgid="7516929526199520173">"পইণ্টাৰৰ অৱস্থান"</string> - <string name="pointer_location_summary" msgid="957120116989798464">"চলিত স্পৰ্শ-বিষয়ক তথ্যসহ স্ক্ৰীণ অভাৰলে\'"</string> + <string name="pointer_location_summary" msgid="957120116989798464">"চলিত স্পৰ্শ-বিষয়ক তথ্যসহ স্ক্ৰীন অভাৰলে’"</string> <string name="show_touches" msgid="8437666942161289025">"টেপসমূহ দেখুৱাওক"</string> <string name="show_touches_summary" msgid="3692861665994502193">"টিপিলে দৃশ্যায়িত ফীডবেক দিয়ক"</string> <string name="show_screen_updates" msgid="2078782895825535494">"পৃষ্ঠভাগৰ আপডেইট দেখুৱাওক"</string> @@ -344,7 +344,7 @@ <string name="show_hw_layers_updates_summary" msgid="5850955890493054618">"হাৰ্ডৱেৰ লেয়াৰ আপডেইট হওতে সিঁহতক সেউজীয়া ৰঙেৰে ফ্লাশ্ব কৰক"</string> <string name="debug_hw_overdraw" msgid="8944851091008756796">"GPU অভাৰড্ৰ ডিবাগ কৰক"</string> <string name="disable_overlays" msgid="4206590799671557143">"HW অ’ভাৰলে অক্ষম কৰক"</string> - <string name="disable_overlays_summary" msgid="1954852414363338166">"স্ক্ৰীণ কম্প’জিট কৰাৰ বাবে সদায় জিপিইউ ব্যৱহাৰ কৰক"</string> + <string name="disable_overlays_summary" msgid="1954852414363338166">"স্ক্ৰীন কম্প’জিট কৰাৰ বাবে সদায় জিপিইউ ব্যৱহাৰ কৰক"</string> <string name="simulate_color_space" msgid="1206503300335835151">"ৰঙৰ ঠাই ছিমিউলেইট কৰক"</string> <string name="enable_opengl_traces_title" msgid="4638773318659125196">"OpenGL ট্ৰেছ সক্ষম কৰক"</string> <string name="usb_audio_disable_routing" msgid="3367656923544254975">"ইউএছবি অডিঅ\' ৰাউটিং অক্ষম কৰক"</string> @@ -352,7 +352,7 @@ <string name="debug_layout" msgid="1659216803043339741">"লেআউটৰ সময় দেখুৱাওক"</string> <string name="debug_layout_summary" msgid="8825829038287321978">"ক্লিপ বাউণ্ড, মাৰ্জিন আদিসমূহ দেখুৱাওক"</string> <string name="force_rtl_layout_all_locales" msgid="8690762598501599796">"আৰটিএল চানেকিৰ দিশ বলেৰে সলনি কৰক"</string> - <string name="force_rtl_layout_all_locales_summary" msgid="6663016859517239880">"সকলো ভাষাৰ বাবে স্ক্ৰীণৰ চানেকিৰ দিশ RTLলৈ বলেৰে সলনি কৰক"</string> + <string name="force_rtl_layout_all_locales_summary" msgid="6663016859517239880">"সকলো ভাষাৰ বাবে স্ক্ৰীনৰ চানেকিৰ দিশ RTLলৈ বলেৰে সলনি কৰক"</string> <string name="window_blurs" msgid="6831008984828425106">"ৱিণ্ড’ স্তৰত অস্পষ্ট কৰাৰ অনুমতি দিয়ক"</string> <string name="force_msaa" msgid="4081288296137775550">"বল ৪গুণ MSAA"</string> <string name="force_msaa_summary" msgid="9070437493586769500">"OpenGL ES 2.0 এপত ৪গুণ MSAA সক্ষম কৰক"</string> @@ -373,7 +373,7 @@ <string name="show_all_anrs" msgid="9160563836616468726">"নেপথ্য এএনআৰবোৰ দেখুৱাওক"</string> <string name="show_all_anrs_summary" msgid="8562788834431971392">"নেপথ্য এপসমূহৰ বাবে এপে সঁহাৰি দিয়া নাই ডায়ল\'গ প্ৰদৰ্শন কৰক"</string> <string name="show_notification_channel_warnings" msgid="3448282400127597331">"জাননী চ্চেনেলৰ সকীয়নিসমূহ দেখুৱাওক"</string> - <string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"কোনো এপে বৈধ চ্চেনেল নোহোৱাকৈ কোনো জাননী প\'ষ্ট কৰিলে স্ক্ৰীণত সকীয়নি প্ৰদৰ্শন হয়"</string> + <string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"কোনো এপে বৈধ চ্চেনেল নোহোৱাকৈ কোনো জাননী প\'ষ্ট কৰিলে স্ক্ৰীনত সকীয়নি প্ৰদৰ্শন হয়"</string> <string name="force_allow_on_external" msgid="9187902444231637880">"বাহ্যিক সঞ্চয়াগাৰত এপক বলেৰে অনুমতি দিয়ক"</string> <string name="force_allow_on_external_summary" msgid="8525425782530728238">"মেনিফেষ্টৰ মান যিয়েই নহওক, বাহ্যিক সঞ্চয়াগাৰত লিখিবলৈ যিকোনো এপক উপযুক্ত কৰি তোলে"</string> <string name="force_resizable_activities" msgid="7143612144399959606">"বলেৰে কাৰ্যকলাপসমূহৰ আকাৰ সলনি কৰিব পৰা কৰক"</string> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index 921caba34e13..108d86d185e0 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -286,7 +286,7 @@ <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Réduit la décharge de la batterie et améliore les performances du réseau"</string> <string name="wifi_enhanced_mac_randomization_summary" msgid="1210663439867489931">"Quand ce mode est activé, l\'adresse MAC de cet appareil peut changer chaque fois qu\'il se connecte à un réseau Wi-Fi où le changement aléatoire d\'adresse MAC est activé"</string> <string name="wifi_metered_label" msgid="8737187690304098638">"Facturé à l\'usage"</string> - <string name="wifi_unmetered_label" msgid="6174142840934095093">"Non facturé à l\'usage"</string> + <string name="wifi_unmetered_label" msgid="6174142840934095093">"Sans compteur"</string> <string name="select_logd_size_title" msgid="1604578195914595173">"Tailles des tampons de l\'enregistreur"</string> <string name="select_logd_size_dialog_title" msgid="2105401994681013578">"Tailles enreg. par tampon journal"</string> <string name="dev_logpersist_clear_warning_title" msgid="8631859265777337991">"Effacer l\'espace de stockage persistant de l\'enregistreur ?"</string> diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index cbf92af607d2..0f381c966932 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -542,7 +542,7 @@ <string name="delete_blob_text" msgid="2819192607255625697">"Ортақ деректерді жою"</string> <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"Осы ортақ деректерді шынымен жойғыңыз келе ме?"</string> <string name="user_add_user_item_summary" msgid="5748424612724703400">"Пайдаланушылардың өздерінің қолданбалары мен мазмұны болады"</string> - <string name="user_add_profile_item_summary" msgid="5418602404308968028">"Өз есептік жазбаңыздан қолданбалар мен мазмұнға қол жетімділікті шектеуіңізге болады"</string> + <string name="user_add_profile_item_summary" msgid="5418602404308968028">"Өз аккаунтыңыздан қолданбалар мен мазмұнға қол жетімділікті шектеуіңізге болады"</string> <string name="user_add_user_item_title" msgid="2394272381086965029">"Пайдаланушы"</string> <string name="user_add_profile_item_title" msgid="3111051717414643029">"Шектелген профайл"</string> <string name="user_add_user_title" msgid="5457079143694924885">"Жаңа пайдаланушы қосылсын ба?"</string> diff --git a/packages/SettingsLib/res/values-ko/arrays.xml b/packages/SettingsLib/res/values-ko/arrays.xml index 801c037684f9..195fc47e8c64 100644 --- a/packages/SettingsLib/res/values-ko/arrays.xml +++ b/packages/SettingsLib/res/values-ko/arrays.xml @@ -28,7 +28,7 @@ <item msgid="2837871868181677206">"IP 주소를 가져오는 중..."</item> <item msgid="4613015005934755724">"연결됨"</item> <item msgid="3763530049995655072">"일시 정지됨"</item> - <item msgid="7852381437933824454">"연결을 끊는 중…"</item> + <item msgid="7852381437933824454">"연결 해제 중…"</item> <item msgid="5046795712175415059">"연결 끊김"</item> <item msgid="2473654476624070462">"실패"</item> <item msgid="9146847076036105115">"차단됨"</item> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index 97ad0a0ef347..c455c14b1226 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -65,7 +65,7 @@ <string name="wifi_passpoint_expired" msgid="6540867261754427561">"만료됨"</string> <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string> <string name="bluetooth_disconnected" msgid="7739366554710388701">"연결 끊김"</string> - <string name="bluetooth_disconnecting" msgid="7638892134401574338">"연결을 끊는 중…"</string> + <string name="bluetooth_disconnecting" msgid="7638892134401574338">"연결 해제 중…"</string> <string name="bluetooth_connecting" msgid="5871702668260192755">"연결 중…"</string> <string name="bluetooth_connected" msgid="8065345572198502293">"연결됨<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string> <string name="bluetooth_pairing" msgid="4269046942588193600">"페어링 중..."</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java index c501b3aab4d4..2e8f36834584 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java @@ -110,6 +110,18 @@ public class PowerAllowlistBackend { } /** + * Check if target package is in allow list except idle app + */ + public boolean isAllowlistedExceptIdle(String pkg) { + try { + return mDeviceIdleService.isPowerSaveWhitelistExceptIdleApp(pkg); + } catch (RemoteException e) { + Log.w(TAG, "Unable to reach IDeviceIdleController", e); + return true; + } + } + + /** * * @param pkgs a list of packageName * @return true when one of package is in allow list diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java index 4f11fb1f782f..6caf7624e1bc 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java @@ -151,4 +151,14 @@ public class PowerAllowlistBackendTest { assertThat(mPowerAllowlistBackend.isSysAllowlisted(PACKAGE_TWO)).isFalse(); assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE)).isFalse(); } + + @Test + public void testIsPowerSaveWhitelistExceptIdleApp() throws Exception { + doReturn(true).when(mDeviceIdleService) + .isPowerSaveWhitelistExceptIdleApp(PACKAGE_ONE); + + mPowerAllowlistBackend.refreshList(); + + assertThat(mPowerAllowlistBackend.isAllowlistedExceptIdle(PACKAGE_ONE)).isTrue(); + } } diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index d051290cb4b4..6671308ef66a 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -92,6 +92,7 @@ android_library { "iconloader_base", "SystemUI-tags", "SystemUI-proto", + "monet", "dagger2", "jsr330", "lottie", @@ -179,6 +180,7 @@ android_library { "mockito-target-extended-minus-junit4", "testables", "truth-prebuilt", + "monet", "dagger2", "jsr330", "WindowManager-Shell", diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 0d18b8dea284..e509777633e7 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -22,6 +22,9 @@ }, { "exclude-annotation": "android.platform.test.annotations.Postsubmit" + }, + { + "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly" } ] }, @@ -82,6 +85,9 @@ }, { "exclude-annotation": "android.platform.helpers.Staging" + }, + { + "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly" } ] } @@ -101,6 +107,9 @@ }, { "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly" } ] } @@ -117,5 +126,24 @@ } ] } + ], + "large-screen-postsubmit": [ + { + "name": "PlatformScenarioTests", + "options" : [ + { + "include-filter": "android.platform.test.scenario.sysui" + }, + { + "include-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly" + }, + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } ] } diff --git a/packages/SystemUI/monet/Android.bp b/packages/SystemUI/monet/Android.bp new file mode 100644 index 000000000000..507ea25083e1 --- /dev/null +++ b/packages/SystemUI/monet/Android.bp @@ -0,0 +1,31 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_library { + name: "monet", + platform_apis: true, + static_libs: [ + "androidx.annotation_annotation", + "androidx.core_core", + ], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], +} diff --git a/packages/SystemUI/monet/AndroidManifest.xml b/packages/SystemUI/monet/AndroidManifest.xml new file mode 100644 index 000000000000..1fab52877847 --- /dev/null +++ b/packages/SystemUI/monet/AndroidManifest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.systemui.monet"> +</manifest> diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt new file mode 100644 index 000000000000..b8039e13b2a5 --- /dev/null +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.monet + +import android.annotation.ColorInt +import android.app.WallpaperColors +import android.graphics.Color +import com.android.internal.graphics.ColorUtils +import com.android.internal.graphics.cam.Cam +import com.android.internal.graphics.cam.CamUtils.lstarFromInt +import kotlin.math.absoluteValue +import kotlin.math.roundToInt + +const val TAG = "ColorScheme" + +const val ACCENT1_CHROMA = 48.0f +const val ACCENT2_CHROMA = 16.0f +const val ACCENT3_CHROMA = 32.0f +const val ACCENT3_HUE_SHIFT = 60.0f + +const val NEUTRAL1_CHROMA = 4.0f +const val NEUTRAL2_CHROMA = 8.0f + +const val GOOGLE_BLUE = 0xFF1b6ef3.toInt() + +const val MIN_CHROMA = 15 +const val MIN_LSTAR = 10 + +public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) { + + val accent1: List<Int> + val accent2: List<Int> + val accent3: List<Int> + val neutral1: List<Int> + val neutral2: List<Int> + + constructor(wallpaperColors: WallpaperColors, darkTheme: Boolean): + this(getSeedColor(wallpaperColors), darkTheme) + + val allAccentColors: List<Int> + get() { + val allColors = mutableListOf<Int>() + allColors.addAll(accent1) + allColors.addAll(accent2) + allColors.addAll(accent3) + return allColors + } + + val allNeutralColors: List<Int> + get() { + val allColors = mutableListOf<Int>() + allColors.addAll(neutral1) + allColors.addAll(neutral2) + return allColors + } + + val backgroundColor + get() = ColorUtils.setAlphaComponent(if (darkTheme) neutral1[8] else neutral1[0], 0xFF) + + val accentColor + get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1[2] else accent1[6], 0xFF) + + init { + val seedArgb = if (seed == Color.TRANSPARENT) GOOGLE_BLUE else seed + val camSeed = Cam.fromInt(seedArgb) + val hue = camSeed.hue + val chroma = camSeed.chroma.coerceAtLeast(ACCENT1_CHROMA) + accent1 = Shades.of(hue, chroma).toList() + accent2 = Shades.of(hue, ACCENT2_CHROMA).toList() + accent3 = Shades.of(hue + ACCENT3_HUE_SHIFT, ACCENT3_CHROMA).toList() + neutral1 = Shades.of(hue, NEUTRAL1_CHROMA).toList() + neutral2 = Shades.of(hue, NEUTRAL2_CHROMA).toList() + } + + override fun toString(): String { + return "ColorScheme {\n" + + " neutral1: ${humanReadable(neutral1)}\n" + + " neutral2: ${humanReadable(neutral2)}\n" + + " accent1: ${humanReadable(accent1)}\n" + + " accent2: ${humanReadable(accent2)}\n" + + " accent3: ${humanReadable(accent3)}\n" + + "}" + } + + companion object { + /** + * Identifies a color to create a color scheme from. + * + * @param wallpaperColors Colors extracted from an image via quantization. + * @return ARGB int representing the color + */ + @JvmStatic + @ColorInt + fun getSeedColor(wallpaperColors: WallpaperColors): Int { + return getSeedColors(wallpaperColors).first() + } + + /** + * Filters and ranks colors from WallpaperColors. + * + * @param wallpaperColors Colors extracted from an image via quantization. + * @return List of ARGB ints, ordered from highest scoring to lowest. + */ + @JvmStatic + fun getSeedColors(wallpaperColors: WallpaperColors): List<Int> { + val totalPopulation = wallpaperColors.allColors.values.reduce { a, b -> a + b } + .toDouble() + val totalPopulationMeaningless = (totalPopulation == 0.0) + if (totalPopulationMeaningless) { + // WallpaperColors with a population of 0 indicate the colors didn't come from + // quantization. Instead of scoring, trust the ordering of the provided primary + // secondary/tertiary colors. + // + // In this case, the colors are usually from a Live Wallpaper. + val distinctColors = wallpaperColors.mainColors.map { + it.toArgb() + }.distinct().filter { + val cam = Cam.fromInt(it) + val lstar = lstarFromInt(it) + cam.chroma >= MIN_CHROMA && lstar >= MIN_LSTAR + }.toList() + + if (distinctColors.isEmpty()) { + return listOf(GOOGLE_BLUE) + } + return distinctColors + } + + val intToProportion = wallpaperColors.allColors.mapValues { + it.value.toDouble() / totalPopulation + } + val intToCam = wallpaperColors.allColors.mapValues { Cam.fromInt(it.key) } + + // Get an array with 360 slots. A slot contains the percentage of colors with that hue. + val hueProportions = huePopulations(intToCam, intToProportion) + // Map each color to the percentage of the image with its hue. + val intToHueProportion = wallpaperColors.allColors.mapValues { + val cam = intToCam[it.key]!! + val hue = cam.hue.roundToInt() + var proportion = 0.0 + for (i in hue - 15..hue + 15) { + proportion += hueProportions[wrapDegrees(i)] + } + proportion + } + // Remove any inappropriate seed colors. For example, low chroma colors look grayscale + // raising their chroma will turn them to a much louder color that may not have been + // in the image. + val filteredIntToCam = intToCam.filter { + val cam = it.value + val lstar = lstarFromInt(it.key) + val proportion = intToHueProportion[it.key]!! + cam.chroma >= MIN_CHROMA && lstar >= MIN_LSTAR && + (totalPopulationMeaningless || proportion > 0.01) + } + // Sort the colors by score, from high to low. + val seeds = mutableListOf<Int>() + val intToScoreIntermediate = filteredIntToCam.mapValues { + score(it.value, intToHueProportion[it.key]!!) + } + val intToScore = intToScoreIntermediate.entries.toMutableList() + intToScore.sortByDescending { it.value } + + // Go through the colors, from high score to low score. If there isn't already a seed + // color with a hue close to color being examined, add the color being examined to the + // seed colors. + for (entry in intToScore) { + val int = entry.key + val existingSeedNearby = seeds.find { + val hueA = intToCam[int]!!.hue + val hueB = intToCam[it]!!.hue + hueDiff(hueA, hueB) < 15 } != null + if (existingSeedNearby) { + continue + } + seeds.add(int) + } + + if (seeds.isEmpty()) { + // Use gBlue 500 if there are 0 colors + seeds.add(GOOGLE_BLUE) + } + + return seeds + } + + private fun wrapDegrees(degrees: Int): Int { + return when { + degrees < 0 -> { + (degrees % 360) + 360 + } + degrees >= 360 -> { + degrees % 360 + } + else -> { + degrees + } + } + } + + private fun hueDiff(a: Float, b: Float): Float { + return 180f - ((a - b).absoluteValue - 180f).absoluteValue + } + + private fun humanReadable(colors: List<Int>): String { + return colors.joinToString { "#" + Integer.toHexString(it) } + } + + private fun score(cam: Cam, proportion: Double): Double { + val proportionScore = 0.7 * 100.0 * proportion + val chromaScore = if (cam.chroma < ACCENT1_CHROMA) 0.1 * (cam.chroma - ACCENT1_CHROMA) + else 0.3 * (cam.chroma - ACCENT1_CHROMA) + return chromaScore + proportionScore + } + + private fun huePopulations( + camByColor: Map<Int, Cam>, + populationByColor: Map<Int, Double> + ): List<Double> { + val huePopulation = List(size = 360, init = { 0.0 }).toMutableList() + + for (entry in populationByColor.entries) { + val population = populationByColor[entry.key]!! + val cam = camByColor[entry.key]!! + val hue = cam.hue.roundToInt() % 360 + huePopulation[hue] = huePopulation[hue] + population + } + + return huePopulation + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java b/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java new file mode 100644 index 000000000000..498b7dd8658c --- /dev/null +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/Shades.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.monet; + + +import androidx.annotation.ColorInt; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; + + +/** + * Generate sets of colors that are shades of the same color + */ +@VisibleForTesting +public class Shades { + /** + * Combining the ability to convert between relative luminance and perceptual luminance with + * contrast leads to a design system that can be based on a linear value to determine contrast, + * rather than a ratio. + * + * This codebase implements a design system that has that property, and as a result, we can + * guarantee that any shades 5 steps from each other have a contrast ratio of at least 4.5. + * 4.5 is the requirement for smaller text contrast in WCAG 2.1 and earlier. + * + * However, lstar 50 does _not_ have a contrast ratio >= 4.5 with lstar 100. + * lstar 49.6 is the smallest lstar that will lead to a contrast ratio >= 4.5 with lstar 100, + * and it also contrasts >= 4.5 with lstar 100. + */ + public static final float MIDDLE_LSTAR = 49.6f; + + /** + * Generate shades of a color. Ordered in lightness _descending_. + * <p> + * The first shade will be at 95% lightness, the next at 90, 80, etc. through 0. + * + * @param hue hue in CAM16 color space + * @param chroma chroma in CAM16 color space + * @return shades of a color, as argb integers. Ordered by lightness descending. + */ + public static @ColorInt int[] of(float hue, float chroma) { + int[] shades = new int[12]; + shades[0] = ColorUtils.CAMToColor(hue, chroma, 99); + shades[1] = ColorUtils.CAMToColor(hue, chroma, 95); + for (int i = 2; i < 12; i++) { + float lStar = (i == 6) ? MIDDLE_LSTAR : 100 - 10 * (i - 1); + shades[i] = ColorUtils.CAMToColor(hue, chroma, lStar); + } + return shades; + } +} diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index 871b1c4eb3f6..624ee9f51b2a 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -146,4 +146,8 @@ <item name="android:shadowColor">@color/keyguard_shadow_color</item> <item name="android:shadowRadius">?attr/shadowRadius</item> </style> + + <style name="TextAppearance.Keyguard.BottomArea.Button"> + <item name="android:shadowRadius">0</item> + </style> </resources> diff --git a/packages/SystemUI/res/drawable/logout_button_background.xml b/packages/SystemUI/res/drawable/logout_button_background.xml index eafd663f19d9..34434be7aefb 100644 --- a/packages/SystemUI/res/drawable/logout_button_background.xml +++ b/packages/SystemUI/res/drawable/logout_button_background.xml @@ -17,7 +17,8 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:shape="rectangle"> - <solid android:color="@color/logout_button_bg_color"/> + <solid android:color="?androidprv:attr/colorAccentPrimary"/> <corners android:radius="@dimen/logout_button_corner_radius"/> </shape> diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml index a21a63c3e7c3..9cf09ff328c4 100644 --- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml +++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml @@ -15,19 +15,25 @@ ~ limitations under the License --> <!-- This is a view that shows a user switcher in Keyguard. --> -<com.android.systemui.statusbar.phone.UserAvatarView +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res-auto" android:id="@+id/keyguard_qs_user_switch_view" - android:layout_width="@dimen/kg_framed_avatar_size" - android:layout_height="@dimen/kg_framed_avatar_size" - android:layout_centerHorizontal="true" - android:layout_gravity="top|end" - android:layout_marginEnd="16dp" - systemui:avatarPadding="0dp" - systemui:badgeDiameter="18dp" - systemui:badgeMargin="1dp" - systemui:frameColor="@color/kg_user_avatar_frame" - systemui:framePadding="0dp" - systemui:frameWidth="0dp"> -</com.android.systemui.statusbar.phone.UserAvatarView> + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="end"> + <com.android.systemui.statusbar.phone.UserAvatarView + android:id="@+id/kg_multi_user_avatar" + android:layout_width="@dimen/kg_framed_avatar_size" + android:layout_height="@dimen/kg_framed_avatar_size" + android:layout_centerHorizontal="true" + android:layout_gravity="top|end" + android:layout_marginEnd="16dp" + systemui:avatarPadding="0dp" + systemui:badgeDiameter="18dp" + systemui:badgeMargin="1dp" + systemui:frameColor="@color/kg_user_avatar_frame" + systemui:framePadding="0dp" + systemui:frameWidth="0dp"> + </com.android.systemui.statusbar.phone.UserAvatarView> +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/sidefps_view.xml b/packages/SystemUI/res/layout/sidefps_view.xml index 397763531eb9..921f78830981 100644 --- a/packages/SystemUI/res/layout/sidefps_view.xml +++ b/packages/SystemUI/res/layout/sidefps_view.xml @@ -14,11 +14,13 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.biometrics.SidefpsView +<com.airbnb.lottie.LottieAnimationView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:systemui="http://schemas.android.com/apk/res-auto" - android:id="@+id/sidefps_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:contentDescription="@string/accessibility_fingerprint_label"> -</com.android.systemui.biometrics.SidefpsView> + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/sidefps_animation" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:lottie_autoPlay="true" + app:lottie_loop="true" + app:lottie_rawRes="@raw/sfps_pulse" + android:contentDescription="@string/accessibility_fingerprint_label"/> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 435575104f0b..4b6e58fbd1e5 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -70,6 +70,19 @@ android:padding="@dimen/lock_icon_padding" android:layout_gravity="center" android:scaleType="centerCrop"/> + + <!-- Fingerprint --> + <!-- AOD dashed fingerprint icon with moving dashes --> + <com.airbnb.lottie.LottieAnimationView + android:id="@+id/lock_udfps_aod_fp" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/lock_icon_padding" + android:layout_gravity="center" + android:scaleType="centerCrop" + systemui:lottie_autoPlay="false" + systemui:lottie_loop="true" + systemui:lottie_rawRes="@raw/udfps_aod_fp"/> </com.android.keyguard.LockIconView> <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer diff --git a/packages/SystemUI/res/layout/text_toast.xml b/packages/SystemUI/res/layout/text_toast.xml index 49b182addd05..a3fe8efa48bf 100644 --- a/packages/SystemUI/res/layout/text_toast.xml +++ b/packages/SystemUI/res/layout/text_toast.xml @@ -45,6 +45,5 @@ android:maxLines="2" android:paddingTop="12dp" android:paddingBottom="12dp" - android:lineHeight="20sp" android:textAppearance="@*android:style/TextAppearance.Toast"/> </LinearLayout> diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml index 687830d5c7b3..0fcbfa161ddf 100644 --- a/packages/SystemUI/res/layout/udfps_view.xml +++ b/packages/SystemUI/res/layout/udfps_view.xml @@ -20,7 +20,7 @@ android:id="@+id/udfps_view" android:layout_width="match_parent" android:layout_height="match_parent" - systemui:sensorTouchAreaCoefficient="0.75" + systemui:sensorTouchAreaCoefficient="1.0" android:contentDescription="@string/accessibility_fingerprint_label"> <ViewStub diff --git a/packages/SystemUI/res/raw/sfps_pulse.json b/packages/SystemUI/res/raw/sfps_pulse.json new file mode 100644 index 000000000000..c4903a2857a1 --- /dev/null +++ b/packages/SystemUI/res/raw/sfps_pulse.json @@ -0,0 +1 @@ +{"v":"5.7.6","fr":60,"ip":0,"op":300,"w":42,"h":80,"nm":"Fingerprint Pulse Motion","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":0,"s":[28,40,0],"to":[0.751,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":30,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":60,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":90,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":120,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":150,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":180,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":210,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":240,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":270,"s":[32.503,40,0],"to":[0,0,0],"ti":[0.751,0,0]},{"t":300,"s":[28,40,0]}],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.566,0],[-3.929,-5.503],[-2.751,-6.68],[3.929,0],[-2.751,6.68],[-3.929,5.503]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.218,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue600","cl":"blue600","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[41.878,40,0],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[55,55],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 2","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[28.253,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[20]},{"t":30,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":-30,"s":[55,55]},{"t":30,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-30,"op":30,"st":-30,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[20]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[55,55]},{"t":60,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[20]},{"t":90,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":30,"s":[55,55]},{"t":90,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":90,"st":30,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[20]},{"t":120,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":60,"s":[55,55]},{"t":120,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":120,"st":60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":130,"s":[20]},{"t":150,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":90,"s":[55,55]},{"t":150,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":90,"op":150,"st":90,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[20]},{"t":180,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":120,"s":[55,55]},{"t":180,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":120,"op":180,"st":120,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[20]},{"t":210,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":150,"s":[55,55]},{"t":210,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":150,"op":210,"st":150,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":220,"s":[20]},{"t":240,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":180,"s":[55,55]},{"t":240,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":240,"st":180,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[20]},{"t":270,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":210,"s":[55,55]},{"t":270,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":210,"op":270,"st":210,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":280,"s":[20]},{"t":300,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":240,"s":[55,55]},{"t":300,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":300,"st":240,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":310,"s":[20]},{"t":330,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":270,"s":[55,55]},{"t":330,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":270,"op":330,"st":270,"bm":0}],"markers":[]}
\ No newline at end of file diff --git a/packages/SystemUI/res/raw/sfps_pulse_landscape.json b/packages/SystemUI/res/raw/sfps_pulse_landscape.json new file mode 100644 index 000000000000..8c91762d7286 --- /dev/null +++ b/packages/SystemUI/res/raw/sfps_pulse_landscape.json @@ -0,0 +1 @@ +{"v":"5.7.6","fr":60,"ip":0,"op":300,"w":80,"h":42,"nm":"Fingerprint Pulse Motion Portrait","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":0,"s":[40,14,0],"to":[0,-0.751,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":30,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":60,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":90,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":120,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":150,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":180,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":210,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":240,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":270,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,-0.751,0]},{"t":300,"s":[40,14,0]}],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.566,0],[-3.929,-5.503],[-2.751,-6.68],[3.929,0],[-2.751,6.68],[-3.929,5.503]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.218,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue600","cl":"blue600","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,0.122,0],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[55,55],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 2","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[28.253,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[20]},{"t":30,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":-30,"s":[55,55]},{"t":30,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-30,"op":30,"st":-30,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[20]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[55,55]},{"t":60,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[20]},{"t":90,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":30,"s":[55,55]},{"t":90,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":90,"st":30,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[20]},{"t":120,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":60,"s":[55,55]},{"t":120,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":120,"st":60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":130,"s":[20]},{"t":150,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":90,"s":[55,55]},{"t":150,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":90,"op":150,"st":90,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[20]},{"t":180,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":120,"s":[55,55]},{"t":180,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":120,"op":180,"st":120,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[20]},{"t":210,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":150,"s":[55,55]},{"t":210,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":150,"op":210,"st":150,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":220,"s":[20]},{"t":240,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":180,"s":[55,55]},{"t":240,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":240,"st":180,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[20]},{"t":270,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":210,"s":[55,55]},{"t":270,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":210,"op":270,"st":210,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":280,"s":[20]},{"t":300,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":240,"s":[55,55]},{"t":300,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":300,"st":240,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":310,"s":[20]},{"t":330,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":270,"s":[55,55]},{"t":330,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":270,"op":330,"st":270,"bm":0}],"markers":[]}
\ No newline at end of file diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml index 19ec8cee18a8..f057603e2cd0 100644 --- a/packages/SystemUI/res/values-h800dp/dimens.xml +++ b/packages/SystemUI/res/values-h800dp/dimens.xml @@ -16,7 +16,7 @@ <resources> <!-- Minimum margin between clock and top of screen or ambient indication --> - <dimen name="keyguard_clock_top_margin">76dp</dimen> + <dimen name="keyguard_clock_top_margin">38dp</dimen> <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) --> <dimen name="large_clock_text_size">200dp</dimen> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 2c19212581b7..08778bf14c66 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -192,9 +192,6 @@ <color name="udfps_enroll_progress">#ff669DF6</color> <!-- blue 400 --> <color name="udfps_enroll_progress_help">#ffEE675C</color> <!-- red 400 --> - <!-- Logout button --> - <color name="logout_button_bg_color">#ccffffff</color> - <!-- Color for the Assistant invocation lights --> <color name="default_invocation_lights_color">#ffffffff</color> <!-- white --> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 37cc42ee4b73..5e495f0cd1a6 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -197,11 +197,6 @@ low powered state yet. --> <bool name="doze_long_press_uses_prox">true</bool> - <!-- Doze: whether the brightness sensor uses the proximity sensor. - If both this parameter and doze_selectively_register_prox are true, registration for the - brightness sensor won't occur when the display state is ON. --> - <bool name="doze_brightness_uses_prox">true</bool> - <!-- Doze: should notifications be used as a pulse signal? --> <bool name="doze_pulse_on_notifications">true</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 26ee5ea7f914..79581e183c4d 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -750,9 +750,7 @@ <!-- The margin between the status view and the notifications on Keyguard.--> <dimen name="keyguard_status_view_bottom_margin">20dp</dimen> <!-- Minimum margin between clock and status bar --> - <dimen name="keyguard_clock_top_margin">36dp</dimen> - <!-- The margin between top of clock and bottom of lock icon. --> - <dimen name="keyguard_clock_lock_margin">16dp</dimen> + <dimen name="keyguard_clock_top_margin">18dp</dimen> <!-- The amount to shift the clocks during a small/large transition --> <dimen name="keyguard_clock_switch_y_shift">10dp</dimen> <!-- When large clock is showing, offset the smartspace by this amount --> @@ -1157,9 +1155,9 @@ <dimen name="default_burn_in_prevention_offset">15dp</dimen> <!-- The maximum offset for the under-display fingerprint sensor (UDFPS) icon in either - direction that elements aer moved to prevent burn-in on AOD--> - <dimen name="udfps_burn_in_offset_x">2dp</dimen> - <dimen name="udfps_burn_in_offset_y">8dp</dimen> + direction that elements are moved to prevent burn-in on AOD--> + <dimen name="udfps_burn_in_offset_x">7px</dimen> + <dimen name="udfps_burn_in_offset_y">28px</dimen> <dimen name="corner_size">8dp</dimen> <dimen name="top_padding">0dp</dimen> @@ -1250,10 +1248,7 @@ <integer name="wired_charging_keyguard_text_animation_distance">-30</integer> <!-- Logout button --> - <dimen name="logout_button_layout_height">32dp</dimen> - <dimen name="logout_button_padding_horizontal">16dp</dimen> - <dimen name="logout_button_margin_bottom">12dp</dimen> - <dimen name="logout_button_corner_radius">4dp</dimen> + <dimen name="logout_button_corner_radius">50dp</dimen> <!-- Blur radius on status bar window and power menu --> <dimen name="min_window_blur_radius">1px</dimen> diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index c2b87a55f366..d442cc51cfd7 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -22,7 +22,7 @@ <bool name="flag_notification_pipeline2_rendering">false</bool> <bool name="flag_notif_updates">true</bool> - <bool name="flag_monet">false</bool> + <bool name="flag_monet">true</bool> <!-- AOD/Lockscreen alternate layout --> <bool name="flag_keyguard_layout">true</bool> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt index e5933e6a9aea..9010d5154156 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt @@ -36,6 +36,13 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( * [View.setTranslationY] */ private val translationApplier: TranslationApplier = object : TranslationApplier {}, + /** + * Allows to set custom implementation for getting + * view location. Could be useful if logical view bounds + * are different than actual bounds (e.g. view container may + * have larger width than width of the items in the container) + */ + private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {} ) : UnfoldTransitionProgressProvider.TransitionProgressListener { private val screenSize = Point() @@ -43,6 +50,8 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( private val animatedViews: MutableList<AnimatedView> = arrayListOf() + private var lastAnimationProgress: Float = 0f + /** * Updates display properties in order to calculate the initial position for the views * Must be called before [registerViewForAnimation] @@ -58,6 +67,19 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( } /** + * If target view positions have changed (e.g. because of layout changes) call this method + * to re-query view positions and update the translations + */ + fun updateViewPositions() { + animatedViews.forEach { animatedView -> + animatedView.view.get()?.let { + animatedView.updateAnimatedView(it) + } + } + onTransitionProgress(lastAnimationProgress) + } + + /** * Registers a view to be animated, the view should be measured and layouted * After finishing the animation it is necessary to clear * the views using [clearRegisteredViews] @@ -85,45 +107,30 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( ) } } + lastAnimationProgress = progress } - private fun createAnimatedView(view: View): AnimatedView { - val viewCenter = getViewCenter(view) + private fun createAnimatedView(view: View): AnimatedView = + AnimatedView(view = WeakReference(view)).updateAnimatedView(view) + + private fun AnimatedView.updateAnimatedView(view: View): AnimatedView { + val viewCenter = Point() + viewCenterProvider.getViewCenter(view, viewCenter) + val viewCenterX = viewCenter.x val viewCenterY = viewCenter.y - val translationX: Float - val translationY: Float - if (isVerticalFold) { val distanceFromScreenCenterToViewCenter = screenSize.x / 2 - viewCenterX - translationX = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE - translationY = 0f + startTranslationX = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE + startTranslationY = 0f } else { val distanceFromScreenCenterToViewCenter = screenSize.y / 2 - viewCenterY - translationX = 0f - translationY = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE + startTranslationX = 0f + startTranslationY = distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE } - return AnimatedView( - view = WeakReference(view), - startTranslationX = translationX, - startTranslationY = translationY - ) - } - - private fun getViewCenter(view: View): Point { - val viewLocation = IntArray(2) - view.getLocationOnScreen(viewLocation) - - val viewX = viewLocation[0] - val viewY = viewLocation[1] - - val outPoint = Point() - outPoint.x = viewX + view.width / 2 - outPoint.y = viewY + view.height / 2 - - return outPoint + return this } /** @@ -139,10 +146,29 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( } } + /** + * Interface that allows to use custom logic to get the center of the view + */ + interface ViewCenterProvider { + /** + * Called when we need to get the center of the view + */ + fun getViewCenter(view: View, outPoint: Point) { + val viewLocation = IntArray(2) + view.getLocationOnScreen(viewLocation) + + val viewX = viewLocation[0] + val viewY = viewLocation[1] + + outPoint.x = viewX + view.width / 2 + outPoint.y = viewY + view.height / 2 + } + } + private class AnimatedView( val view: WeakReference<View>, - val startTranslationX: Float, - val startTranslationY: Float + var startTranslationX: Float = 0f, + var startTranslationY: Float = 0f ) } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt index 49cd279fb4be..b6be6edc7a10 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt @@ -22,7 +22,6 @@ import android.hardware.SensorManager import android.hardware.devicestate.DeviceStateManager import android.os.Handler import com.android.systemui.unfold.updates.screen.ScreenStatusProvider -import com.android.systemui.unfold.config.ANIMATION_MODE_HINGE_ANGLE import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider @@ -30,7 +29,6 @@ import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgress import com.android.systemui.unfold.updates.DeviceFoldStateProvider import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider -import com.android.systemui.unfold.updates.hinge.RotationSensorHingeAngleProvider import java.lang.IllegalStateException import java.util.concurrent.Executor @@ -50,14 +48,8 @@ fun createUnfoldTransitionProgressProvider( } val hingeAngleProvider = - if (config.mode == ANIMATION_MODE_HINGE_ANGLE) { - // TODO: after removing temporary "config.mode" we should just - // switch between fixed timing and hinge sensor based on this flag - if (config.isHingeAngleEnabled) { - HingeSensorAngleProvider(sensorManager) - } else { - RotationSensorHingeAngleProvider(sensorManager) - } + if (config.isHingeAngleEnabled) { + HingeSensorAngleProvider(sensorManager) } else { EmptyHingeAngleProvider() } @@ -70,7 +62,7 @@ fun createUnfoldTransitionProgressProvider( mainExecutor ) - return if (config.mode == ANIMATION_MODE_HINGE_ANGLE) { + return if (config.isHingeAngleEnabled) { PhysicsBasedUnfoldTransitionProgressProvider( mainHandler, foldStateProvider diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt index e7c6998a847e..3f027e30b473 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt @@ -23,17 +23,16 @@ internal class ResourceUnfoldTransitionConfig( ) : UnfoldTransitionConfig { override val isEnabled: Boolean - get() = readIsEnabled() && mode != ANIMATION_MODE_DISABLED + get() = readIsEnabledResource() && isPropertyEnabled override val isHingeAngleEnabled: Boolean get() = readIsHingeAngleEnabled() - @AnimationMode - override val mode: Int + private val isPropertyEnabled: Boolean get() = SystemProperties.getInt(UNFOLD_TRANSITION_MODE_PROPERTY_NAME, - ANIMATION_MODE_FIXED_TIMING) + UNFOLD_TRANSITION_PROPERTY_ENABLED) == UNFOLD_TRANSITION_PROPERTY_ENABLED - private fun readIsEnabled(): Boolean = context.resources + private fun readIsEnabledResource(): Boolean = context.resources .getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled) private fun readIsHingeAngleEnabled(): Boolean = context.resources @@ -44,4 +43,5 @@ internal class ResourceUnfoldTransitionConfig( * Temporary persistent property to control unfold transition mode * See [com.android.unfold.config.AnimationMode] */ -private const val UNFOLD_TRANSITION_MODE_PROPERTY_NAME = "persist.unfold.transition_mode" +private const val UNFOLD_TRANSITION_MODE_PROPERTY_NAME = "persist.unfold.transition_enabled" +private const val UNFOLD_TRANSITION_PROPERTY_ENABLED = 1 diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt index a5697578e05a..5b187b3486c6 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt @@ -15,25 +15,7 @@ */ package com.android.systemui.unfold.config -import android.annotation.IntDef - interface UnfoldTransitionConfig { val isEnabled: Boolean val isHingeAngleEnabled: Boolean - - @AnimationMode - val mode: Int } - -@IntDef(prefix = ["ANIMATION_MODE_"], value = [ - ANIMATION_MODE_DISABLED, - ANIMATION_MODE_FIXED_TIMING, - ANIMATION_MODE_HINGE_ANGLE -]) - -@Retention(AnnotationRetention.SOURCE) -annotation class AnimationMode - -const val ANIMATION_MODE_DISABLED = 0 -const val ANIMATION_MODE_FIXED_TIMING = 1 -const val ANIMATION_MODE_HINGE_ANGLE = 2 diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/RotationSensorHingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/RotationSensorHingeAngleProvider.kt deleted file mode 100644 index 8b6eecfdfde4..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/RotationSensorHingeAngleProvider.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.android.systemui.unfold.updates.hinge - -import android.hardware.Sensor -import android.hardware.SensorEvent -import android.hardware.SensorEventListener -import android.hardware.SensorManager -import androidx.core.util.Consumer -import com.android.systemui.shared.recents.utilities.Utilities - -/** - * Temporary hinge angle provider that uses rotation sensor instead. - * It requires to have the device in a certain position to work correctly - * (flat to the ground) - */ -internal class RotationSensorHingeAngleProvider( - private val sensorManager: SensorManager -) : HingeAngleProvider { - - private val sensorListener = HingeAngleSensorListener() - private val listeners: MutableList<Consumer<Float>> = arrayListOf() - - override fun start() { - val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR) - sensorManager.registerListener(sensorListener, sensor, SensorManager.SENSOR_DELAY_FASTEST) - } - - override fun stop() { - sensorManager.unregisterListener(sensorListener) - } - - override fun removeCallback(listener: Consumer<Float>) { - listeners.remove(listener) - } - - override fun addCallback(listener: Consumer<Float>) { - listeners.add(listener) - } - - private fun onHingeAngle(angle: Float) { - listeners.forEach { it.accept(angle) } - } - - private inner class HingeAngleSensorListener : SensorEventListener { - - override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { - } - - override fun onSensorChanged(event: SensorEvent) { - // Jumbojack sends incorrect sensor reading 1.0f event in the beginning, let's ignore it - if (event.values[3] == 1.0f) return - - val angleRadians = event.values.convertToAngle() - val hingeAngleDegrees = Math.toDegrees(angleRadians).toFloat() - val angle = Utilities.clamp(hingeAngleDegrees, FULLY_CLOSED_DEGREES, FULLY_OPEN_DEGREES) - onHingeAngle(angle) - } - - private val rotationMatrix = FloatArray(9) - private val resultOrientation = FloatArray(9) - - private fun FloatArray.convertToAngle(): Double { - SensorManager.getRotationMatrixFromVector(rotationMatrix, this) - SensorManager.getOrientation(rotationMatrix, resultOrientation) - return resultOrientation[2] + Math.PI - } - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java index 92f89d6b90fd..efcf40a66258 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -160,9 +160,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver); mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); mBatteryController.removeCallback(mBatteryCallback); - if (!mView.isAttachedToWindow()) { - mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener); - } + mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener); } /** Animate the clock appearance */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 11c4b6a27968..260b39378485 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -72,13 +72,16 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS * Clock for both small and large sizes */ private AnimatableClockController mClockViewController; - private FrameLayout mClockFrame; + private FrameLayout mClockFrame; // top aligned clock private AnimatableClockController mLargeClockViewController; - private FrameLayout mLargeClockFrame; + private FrameLayout mLargeClockFrame; // centered clock private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final KeyguardBypassController mBypassController; + private int mLargeClockTopMargin = 0; + private int mKeyguardClockTopMargin = 0; + /** * Listener for changes to the color palette. * @@ -177,6 +180,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } mColorExtractor.addOnColorsChangedListener(mColorsListener); mView.updateColors(getGradientColors()); + mKeyguardClockTopMargin = + mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); if (mOnlyClock) { View ksa = mView.findViewById(R.id.keyguard_status_area); @@ -241,6 +246,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS */ public void onDensityOrFontScaleChanged() { mView.onDensityOrFontScaleChanged(); + mKeyguardClockTopMargin = + mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); updateClockLayout(); } @@ -249,9 +256,12 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS if (mSmartspaceController.isEnabled()) { RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); - lp.topMargin = getContext().getResources().getDimensionPixelSize( + mLargeClockTopMargin = getContext().getResources().getDimensionPixelSize( R.dimen.keyguard_large_clock_top_margin); + lp.topMargin = mLargeClockTopMargin; mLargeClockFrame.setLayoutParams(lp); + } else { + mLargeClockTopMargin = 0; } } @@ -363,6 +373,28 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } } + /** + * Get y-bottom position of the currently visible clock on the keyguard. + * We can't directly getBottom() because clock changes positions in AOD for burn-in + */ + int getClockBottom(int statusBarHeaderHeight) { + if (mLargeClockFrame.getVisibility() == View.VISIBLE) { + View clock = mLargeClockFrame.findViewById( + com.android.systemui.R.id.animatable_clock_view_large); + int frameHeight = mLargeClockFrame.getHeight(); + int clockHeight = clock.getHeight(); + return frameHeight / 2 + clockHeight / 2; + } else { + return mClockFrame.findViewById( + com.android.systemui.R.id.animatable_clock_view).getHeight() + + statusBarHeaderHeight + mKeyguardClockTopMargin; + } + } + + boolean isClockTopAligned() { + return mLargeClockFrame.getVisibility() != View.VISIBLE; + } + private void updateAodIcons() { NotificationIconContainer nic = (NotificationIconContainer) mView.findViewById( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index a35aedf6f503..1862fc7f6603 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -102,7 +102,7 @@ public class KeyguardPatternView extends KeyguardInputView ConstraintSet cs = new ConstraintSet(); cs.clone(mContainer); - cs.setGuidelinePercent(R.id.pin_pad_top_guideline, posture == DEVICE_POSTURE_HALF_OPENED + cs.setGuidelinePercent(R.id.pattern_top_guideline, posture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f); cs.applyTo(mContainer); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 260590685674..64d214d1eee7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -54,6 +54,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; +import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import java.util.ArrayList; @@ -472,6 +473,11 @@ public class KeyguardSecurityContainer extends FrameLayout { mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT); + int keyguardState = mIsSecurityViewLeftAligned + ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_LEFT + : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_RIGHT; + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, keyguardState); + updateSecurityViewLocation(animate); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 0df2d65d2acb..9d649e78c363 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -151,9 +151,17 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { + int bouncerSide = SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__DEFAULT; + if (canUseOneHandedBouncer()) { + bouncerSide = isOneHandedKeyguardLeftAligned() + ? SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__LEFT + : SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__RIGHT; + } + if (success) { SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, - SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS); + SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS, + bouncerSide); mLockPatternUtils.reportSuccessfulPasswordAttempt(userId); // Force a garbage collection in an attempt to erase any lockscreen password left in // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard @@ -168,7 +176,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard }); } else { SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, - SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE); + SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE, + bouncerSide); reportFailedUnlockAttempt(userId, timeoutMs); } mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER) @@ -314,6 +323,14 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard @Override public void onResume(int reason) { if (mCurrentSecurityMode != SecurityMode.None) { + int state = SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN; + if (canUseOneHandedBouncer()) { + state = mView.isOneHandedModeLeftAligned() + ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_LEFT + : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_RIGHT; + } + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, state); + getCurrentSecurityController().onResume(reason); } mView.onResume( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 8bf8e0926095..d23b1c88b345 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -187,6 +187,20 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } /** + * Get y-bottom position of the currently visible clock. + */ + public int getClockBottom(int statusBarHeaderHeight) { + return mKeyguardClockSwitchController.getClockBottom(statusBarHeaderHeight); + } + + /** + * @return true if the currently displayed clock is top aligned (as opposed to center aligned) + */ + public boolean isClockTopAligned() { + return mKeyguardClockSwitchController.isClockTopAligned(); + } + + /** * Set whether the view accessibility importance mode. */ public void setStatusAccessibilityImportance(int mode) { diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index dd3bb8990599..371564a98aad 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -45,7 +45,7 @@ public class LockIconView extends FrameLayout implements Dumpable { private int mRadius; private ImageView mLockIcon; - private ImageView mUnlockBgView; + private ImageView mBgView; private int mLockIconColor; @@ -58,19 +58,19 @@ public class LockIconView extends FrameLayout implements Dumpable { public void onFinishInflate() { super.onFinishInflate(); mLockIcon = findViewById(R.id.lock_icon); - mUnlockBgView = findViewById(R.id.lock_icon_bg); + mBgView = findViewById(R.id.lock_icon_bg); } void updateColorAndBackgroundVisibility(boolean useBackground) { - if (useBackground) { + if (useBackground && mLockIcon.getDrawable() != null) { mLockIconColor = Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorPrimary); - mUnlockBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg)); - mUnlockBgView.setVisibility(View.VISIBLE); + mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg)); + mBgView.setVisibility(View.VISIBLE); } else { mLockIconColor = Utils.getColorAttrDefaultColor(getContext(), R.attr.wallpaperTextColorAccent); - mUnlockBgView.setVisibility(View.GONE); + mBgView.setVisibility(View.GONE); } mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor)); @@ -78,6 +78,11 @@ public class LockIconView extends FrameLayout implements Dumpable { void setImageDrawable(Drawable drawable) { mLockIcon.setImageDrawable(drawable); + if (drawable == null) { + mBgView.setVisibility(View.INVISIBLE); + } else { + mBgView.setVisibility(View.VISIBLE); + } } /** diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 2a4022ca57b6..28e19acce506 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -19,6 +19,8 @@ package com.android.keyguard; import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static com.android.systemui.classifier.Classifier.LOCK_ICON; +import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; +import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset; import android.content.Context; import android.content.res.Configuration; @@ -27,11 +29,13 @@ import android.graphics.Rect; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.hardware.biometrics.BiometricSourceType; +import android.hardware.biometrics.SensorLocationInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.media.AudioAttributes; import android.os.Process; import android.os.Vibrator; import android.util.DisplayMetrics; +import android.util.MathUtils; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; @@ -59,6 +63,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; +import com.airbnb.lottie.LottieAnimationView; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Objects; @@ -94,6 +100,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull private final DelayableExecutor mExecutor; private boolean mUdfpsEnrolled; + @NonNull private LottieAnimationView mAodFp; + @NonNull private final AnimatedVectorDrawable mFpToUnlockIcon; @NonNull private final AnimatedVectorDrawable mLockToUnlockIcon; @NonNull private final Drawable mLockIcon; @@ -112,6 +120,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private boolean mIsKeyguardShowing; private boolean mUserUnlockedWithBiometric; private Runnable mCancelDelayedUpdateVisibilityRunnable; + private Runnable mOnGestureDetectedRunnable; private boolean mUdfpsSupported; private float mHeightPixels; @@ -121,6 +130,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private boolean mShowUnlockIcon; private boolean mShowLockIcon; + // for udfps when strong auth is required or unlocked on AOD + private boolean mShowAODFpIcon; + private final int mMaxBurnInOffsetX; + private final int mMaxBurnInOffsetY; + private float mInterpolatedDarkAmount; + private boolean mDownDetected; private boolean mDetectedLongPress; private final Rect mSensorTouchLocation = new Rect(); @@ -155,6 +170,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mAuthRippleController = authRippleController; final Context context = view.getContext(); + mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp); + mMaxBurnInOffsetX = context.getResources() + .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); + mMaxBurnInOffsetY = context.getResources() + .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); + mUnlockIcon = mView.getContext().getResources().getDrawable( R.drawable.ic_unlock, mView.getContext().getTheme()); @@ -173,19 +194,19 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Override protected void onInit() { - mAuthController.addCallback(mAuthControllerCallback); - mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; - mView.setAccessibilityDelegate(mAccessibilityDelegate); } @Override protected void onViewAttached() { + updateIsUdfpsEnrolled(); updateConfiguration(); updateKeyguardShowing(); mUserUnlockedWithBiometric = false; + mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); mIsDozing = mStatusBarStateController.isDozing(); + mInterpolatedDarkAmount = mStatusBarStateController.getDozeAmount(); mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning(); mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen(); mStatusBarState = mStatusBarStateController.getState(); @@ -193,15 +214,18 @@ public class LockIconViewController extends ViewController<LockIconView> impleme updateColors(); mConfigurationController.addCallback(mConfigurationListener); + mAuthController.addCallback(mAuthControllerCallback); mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); mStatusBarStateController.addCallback(mStatusBarStateListener); mKeyguardStateController.addCallback(mKeyguardStateCallback); mDownDetected = false; + updateBurnInOffsets(); updateVisibility(); } @Override protected void onViewDetached() { + mAuthController.removeCallback(mAuthControllerCallback); mConfigurationController.removeCallback(mConfigurationListener); mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); mStatusBarStateController.removeCallback(mStatusBarStateListener); @@ -231,7 +255,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mCancelDelayedUpdateVisibilityRunnable = null; } - if (!mIsKeyguardShowing) { + if (!mIsKeyguardShowing && !mIsDozing) { mView.setVisibility(View.INVISIBLE); return; } @@ -242,6 +266,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen() && (!mUdfpsEnrolled || !mRunningFPS); mShowUnlockIcon = mCanDismissLockScreen && isLockScreen(); + mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS; final CharSequence prevContentDescription = mView.getContentDescription(); if (mShowLockIcon) { @@ -264,10 +289,22 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } mView.setVisibility(View.VISIBLE); mView.setContentDescription(mUnlockedLabel); + } else if (mShowAODFpIcon) { + mView.setImageDrawable(null); + mView.setContentDescription(null); + mAodFp.setVisibility(View.VISIBLE); + mAodFp.setContentDescription(mCanDismissLockScreen ? mUnlockedLabel : mLockedLabel); + mView.setVisibility(View.VISIBLE); } else { mView.setVisibility(View.INVISIBLE); mView.setContentDescription(null); } + + if (!mShowAODFpIcon) { + mAodFp.setVisibility(View.INVISIBLE); + mAodFp.setContentDescription(null); + } + if (!Objects.equals(prevContentDescription, mView.getContentDescription()) && mView.getContentDescription() != null) { mView.announceForAccessibility(mView.getContentDescription()); @@ -330,8 +367,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private void updateLockIconLocation() { if (mUdfpsSupported) { FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0); - mView.setCenterLocation(new PointF(props.sensorLocationX, props.sensorLocationY), - props.sensorRadius); + final SensorLocationInternal location = props.getLocation(); + mView.setCenterLocation(new PointF(location.sensorLocationX, location.sensorLocationY), + location.sensorRadius); } else { mView.setCenterLocation( new PointF(mWidthPixels / 2, @@ -344,10 +382,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("mUdfpsSupported: " + mUdfpsSupported); pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled); pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing); pw.println(" mShowUnlockIcon: " + mShowUnlockIcon); pw.println(" mShowLockIcon: " + mShowLockIcon); + pw.println(" mShowAODFpIcon: " + mShowAODFpIcon); pw.println(" mIsDozing: " + mIsDozing); pw.println(" mIsBouncerShowing: " + mIsBouncerShowing); pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric); @@ -355,17 +395,57 @@ public class LockIconViewController extends ViewController<LockIconView> impleme pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen); pw.println(" mStatusBarState: " + StatusBarState.toShortString(mStatusBarState)); pw.println(" mQsExpanded: " + mQsExpanded); + pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount); if (mView != null) { mView.dump(fd, pw, args); } } + /** Every minute, update the aod icon's burn in offset */ + public void dozeTimeTick() { + updateBurnInOffsets(); + } + + private void updateBurnInOffsets() { + float offsetX = MathUtils.lerp(0f, + getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */) + - mMaxBurnInOffsetX, mInterpolatedDarkAmount); + float offsetY = MathUtils.lerp(0f, + getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */) + - mMaxBurnInOffsetY, mInterpolatedDarkAmount); + float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount); + + mAodFp.setTranslationX(offsetX); + mAodFp.setTranslationY(offsetY); + mAodFp.setProgress(progress); + mAodFp.setAlpha(255 * mInterpolatedDarkAmount); + } + + private void updateIsUdfpsEnrolled() { + boolean wasUdfpsSupported = mUdfpsSupported; + boolean wasUdfpsEnrolled = mUdfpsEnrolled; + + mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; + mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); + if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) { + updateVisibility(); + } + } + private StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { @Override + public void onDozeAmountChanged(float linear, float eased) { + mInterpolatedDarkAmount = eased; + updateBurnInOffsets(); + } + + @Override public void onDozingChanged(boolean isDozing) { mIsDozing = isDozing; + updateBurnInOffsets(); + updateIsUdfpsEnrolled(); updateVisibility(); } @@ -439,7 +519,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mKeyguardUpdateMonitor.getUserUnlockedWithBiometric( KeyguardUpdateMonitor.getCurrentUser()); } - mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); + updateIsUdfpsEnrolled(); updateVisibility(); } @@ -485,8 +565,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme // intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or // MotionEvent.ACTION_UP (see #onTouchEvent) - mDownDetected = true; - if (mVibrator != null) { + if (mVibrator != null && !mDownDetected) { mVibrator.vibrate( Process.myUid(), getContext().getOpPackageName(), @@ -494,6 +573,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme "lockIcon-onDown", VIBRATION_SONIFICATION_ATTRIBUTES); } + + mDownDetected = true; return true; } @@ -551,6 +632,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mAuthRippleController.showRipple(FINGERPRINT); } updateVisibility(); + if (mOnGestureDetectedRunnable != null) { + mOnGestureDetectedRunnable.run(); + } mKeyguardViewController.showBouncer(/* scrim */ true); return true; } @@ -561,16 +645,18 @@ public class LockIconViewController extends ViewController<LockIconView> impleme * in a 'clickable' state * @return whether to intercept the touch event */ - public boolean onTouchEvent(MotionEvent event) { + public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) { if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY()) - && mView.getVisibility() == View.VISIBLE) { + && (mView.getVisibility() == View.VISIBLE + || mAodFp.getVisibility() == View.VISIBLE)) { + mOnGestureDetectedRunnable = onGestureDetectedRunnable; mGestureDetector.onTouchEvent(event); } // we continue to intercept all following touches until we see MotionEvent.ACTION_CANCEL UP // or MotionEvent.ACTION_UP. this is to avoid passing the touch to NPV // after the lock icon disappears on device entry - if (mDownDetected && mDetectedLongPress) { + if (mDownDetected) { if (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP) { mDownDetected = false; @@ -594,7 +680,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { @Override public void onAllAuthenticatorsRegistered() { - mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; + updateIsUdfpsEnrolled(); updateConfiguration(); } }; diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java index 3a0357d2a284..4331f52eb1a2 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java @@ -16,7 +16,8 @@ package com.android.keyguard.dagger; -import com.android.systemui.statusbar.phone.UserAvatarView; +import android.widget.FrameLayout; + import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; import dagger.BindsInstance; @@ -31,8 +32,7 @@ public interface KeyguardQsUserSwitchComponent { /** Simple factory for {@link KeyguardUserSwitcherComponent}. */ @Subcomponent.Factory interface Factory { - KeyguardQsUserSwitchComponent build( - @BindsInstance UserAvatarView userAvatarView); + KeyguardQsUserSwitchComponent build(@BindsInstance FrameLayout userAvatarContainer); } /** Builds a {@link KeyguardQsUserSwitchController}. */ diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java index 186917fc8807..d325b92cf89f 100644 --- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java +++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java @@ -16,18 +16,13 @@ package com.android.systemui; -import static android.os.PowerManager.WAKE_REASON_UNKNOWN; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.biometrics.BiometricSourceType; import android.os.Build; -import android.os.PowerManager; -import android.os.SystemClock; -import com.android.internal.util.LatencyTracker; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; @@ -48,20 +43,15 @@ public class LatencyTester extends SystemUI { private static final String ACTION_FACE_WAKE = "com.android.systemui.latency.ACTION_FACE_WAKE"; - private static final String - ACTION_TURN_ON_SCREEN = - "com.android.systemui.latency.ACTION_TURN_ON_SCREEN"; private final BiometricUnlockController mBiometricUnlockController; - private final PowerManager mPowerManager; private final BroadcastDispatcher mBroadcastDispatcher; @Inject public LatencyTester(Context context, BiometricUnlockController biometricUnlockController, - PowerManager powerManager, BroadcastDispatcher broadcastDispatcher) { + BroadcastDispatcher broadcastDispatcher) { super(context); mBiometricUnlockController = biometricUnlockController; - mPowerManager = powerManager; mBroadcastDispatcher = broadcastDispatcher; } @@ -74,7 +64,6 @@ public class LatencyTester extends SystemUI { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_FINGERPRINT_WAKE); filter.addAction(ACTION_FACE_WAKE); - filter.addAction(ACTION_TURN_ON_SCREEN); mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -83,22 +72,11 @@ public class LatencyTester extends SystemUI { fakeWakeAndUnlock(BiometricSourceType.FINGERPRINT); } else if (ACTION_FACE_WAKE.equals(action)) { fakeWakeAndUnlock(BiometricSourceType.FACE); - } else if (ACTION_TURN_ON_SCREEN.equals(action)) { - fakeTurnOnScreen(); } } }, filter); } - private void fakeTurnOnScreen() { - if (LatencyTracker.isEnabled(mContext)) { - LatencyTracker.getInstance(mContext).onActionStart( - LatencyTracker.ACTION_TURN_ON_SCREEN); - } - mPowerManager.wakeUp( - SystemClock.uptimeMillis(), WAKE_REASON_UNKNOWN, "android.policy:LATENCY_TESTS"); - } - private void fakeWakeAndUnlock(BiometricSourceType type) { mBiometricUnlockController.onBiometricAcquired(type); mBiometricUnlockController.onBiometricAuthenticated( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 0790af94f287..71445a7c2cfe 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -114,7 +114,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, @VisibleForTesting IBiometricSysuiReceiver mReceiver; @VisibleForTesting - @NonNull final BiometricOrientationEventListener mOrientationListener; + @NonNull final BiometricDisplayListener mOrientationListener; @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps; @Nullable private List<FingerprintSensorPropertiesInternal> mFpProps; @Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps; @@ -459,13 +459,15 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, mSidefpsControllerFactory = sidefpsControllerFactory; mWindowManager = windowManager; mUdfpsEnrolledForUser = new SparseBooleanArray(); - mOrientationListener = new BiometricOrientationEventListener(context, + mOrientationListener = new BiometricDisplayListener( + context, + displayManager, + handler, + BiometricDisplayListener.SensorType.Generic.INSTANCE, () -> { onOrientationChanged(); return Unit.INSTANCE; - }, - displayManager, - handler); + }); mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 9c818fff5018..ba64195ea78b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -16,6 +16,7 @@ package com.android.systemui.biometrics +import android.animation.ValueAnimator import android.content.Context import android.content.res.Configuration import android.graphics.PointF @@ -26,6 +27,8 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.settingslib.Utils import com.android.systemui.R +import com.android.systemui.animation.Interpolators +import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.NotificationShadeWindowController @@ -36,10 +39,14 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.ViewController import java.io.PrintWriter import javax.inject.Inject import javax.inject.Provider +import com.android.systemui.plugins.statusbar.StatusBarStateController + +private const val WAKE_AND_UNLOCK_FADE_DURATION = 180L /*** * Controls the ripple effect that shows when authentication is successful. @@ -52,20 +59,30 @@ class AuthRippleController @Inject constructor( private val authController: AuthController, private val configurationController: ConfigurationController, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val keyguardStateController: KeyguardStateController, + private val wakefulnessLifecycle: WakefulnessLifecycle, private val commandRegistry: CommandRegistry, private val notificationShadeWindowController: NotificationShadeWindowController, private val bypassController: KeyguardBypassController, private val biometricUnlockController: BiometricUnlockController, private val udfpsControllerProvider: Provider<UdfpsController>, + private val statusBarStateController: StatusBarStateController, rippleView: AuthRippleView? -) : ViewController<AuthRippleView>(rippleView) { +) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback, + WakefulnessLifecycle.Observer { + + @VisibleForTesting + internal var startLightRevealScrimOnKeyguardFadingAway = false var fingerprintSensorLocation: PointF? = null private var faceSensorLocation: PointF? = null private var circleReveal: LightRevealEffect? = null private var udfpsController: UdfpsController? = null + private var dwellScale = 2f private var expandedDwellScale = 2.5f + private var aodDwellScale = 1.9f + private var aodExpandedDwellScale = 2.3f private var udfpsRadius: Float = -1f override fun onInit() { @@ -82,6 +99,8 @@ class AuthRippleController @Inject constructor( udfpsController?.addCallback(udfpsControllerCallback) configurationController.addCallback(configurationChangedListener) keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + keyguardStateController.addCallback(this) + wakefulnessLifecycle.addObserver(this) commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } } @@ -91,6 +110,8 @@ class AuthRippleController @Inject constructor( authController.removeCallback(authControllerCallback) keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) configurationController.removeCallback(configurationChangedListener) + keyguardStateController.removeCallback(this) + wakefulnessLifecycle.removeObserver(this) commandRegistry.unregisterCommand("auth-ripple") notificationShadeWindowController.setForcePluginOpen(false, this) @@ -118,30 +139,48 @@ class AuthRippleController @Inject constructor( private fun showUnlockedRipple() { notificationShadeWindowController.setForcePluginOpen(true, this) - val biometricUnlockMode = biometricUnlockController.mode - val useCircleReveal = circleReveal != null && - (biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK || - biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING || - biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM) + val useCircleReveal = circleReveal != null && biometricUnlockController.isWakeAndUnlock val lightRevealScrim = statusBar.lightRevealScrim if (useCircleReveal) { lightRevealScrim?.revealEffect = circleReveal!! + startLightRevealScrimOnKeyguardFadingAway = true } mView.startUnlockedRipple( /* end runnable */ Runnable { notificationShadeWindowController.setForcePluginOpen(false, this) - }, - /* circleReveal */ - if (useCircleReveal) { - lightRevealScrim - } else { - null } ) } + override fun onKeyguardFadingAwayChanged() { + if (keyguardStateController.isKeyguardFadingAway) { + val lightRevealScrim = statusBar.lightRevealScrim + if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) { + val revealAnimator = ValueAnimator.ofFloat(.1f, 1f).apply { + interpolator = Interpolators.LINEAR_OUT_SLOW_IN + duration = RIPPLE_ANIMATION_DURATION + startDelay = keyguardStateController.keyguardFadingAwayDelay + addUpdateListener { animator -> + if (lightRevealScrim.revealEffect != circleReveal) { + // if the something else took over the reveal, let's do nothing. + return@addUpdateListener + } + lightRevealScrim.revealAmount = animator.animatedValue as Float + } + } + revealAnimator.start() + startLightRevealScrimOnKeyguardFadingAway = false + } + } + } + + override fun onStartedGoingToSleep() { + // reset the light reveal start in case we were pending an unlock + startLightRevealScrimOnKeyguardFadingAway = false + } + fun updateSensorLocation() { fingerprintSensorLocation = authController.fingerprintSensorLocation faceSensorLocation = authController.faceAuthSensorLocation @@ -163,6 +202,22 @@ class AuthRippleController @Inject constructor( Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor) } + private fun showDwellRipple() { + if (statusBarStateController.isDozing) { + mView.startDwellRipple( + /* startRadius */ udfpsRadius, + /* endRadius */ udfpsRadius * aodDwellScale, + /* expandedRadius */ udfpsRadius * aodExpandedDwellScale, + /* isDozing */ true) + } else { + mView.startDwellRipple( + /* startRadius */ udfpsRadius, + /* endRadius */ udfpsRadius * dwellScale, + /* expandedRadius */ udfpsRadius * expandedDwellScale, + /* isDozing */ false) + } + } + private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { override fun onBiometricAuthenticated( @@ -204,10 +259,7 @@ class AuthRippleController @Inject constructor( } mView.setSensorLocation(fingerprintSensorLocation!!) - mView.startDwellRipple( - /* startRadius */ udfpsRadius, - /* endRadius */ udfpsRadius * dwellScale, - /* expandedRadius */ udfpsRadius * expandedDwellScale) + showDwellRipple() } override fun onFingerUp() { @@ -223,7 +275,7 @@ class AuthRippleController @Inject constructor( private fun updateUdfpsDependentParams() { authController.udfpsProps?.let { if (it.size > 0) { - udfpsRadius = it[0].sensorRadius.toFloat() + udfpsRadius = it[0].location.sensorRadius.toFloat() udfpsController = udfpsControllerProvider.get() if (mView.isAttachedToWindow) { @@ -234,14 +286,18 @@ class AuthRippleController @Inject constructor( } inner class AuthRippleCommand : Command { - fun printDwellInfo(pw: PrintWriter) { - pw.println("dwell ripple: " + + fun printLockScreenDwellInfo(pw: PrintWriter) { + pw.println("lock screen dwell ripple: " + "\n\tsensorLocation=$fingerprintSensorLocation" + "\n\tdwellScale=$dwellScale" + - "\n\tdwellAlpha=${mView.dwellAlpha}, " + - "duration=${mView.dwellAlphaDuration}" + - "\n\tdwellExpand=$expandedDwellScale" + - "\n\t(crash systemui to reset to default)") + "\n\tdwellExpand=$expandedDwellScale") + } + + fun printAodDwellInfo(pw: PrintWriter) { + pw.println("aod dwell ripple: " + + "\n\tsensorLocation=$fingerprintSensorLocation" + + "\n\tdwellScale=$aodDwellScale" + + "\n\tdwellExpand=$aodExpandedDwellScale") } override fun execute(pw: PrintWriter, args: List<String>) { @@ -249,40 +305,12 @@ class AuthRippleController @Inject constructor( invalidCommand(pw) } else { when (args[0]) { - "dwellScale" -> { - if (args.size > 1 && args[1].toFloatOrNull() != null) { - dwellScale = args[1].toFloat() - printDwellInfo(pw) - } else { - pw.println("expected float argument <dwellScale>") - } - } - "dwellAlpha" -> { - if (args.size > 2 && args[1].toFloatOrNull() != null && - args[2].toLongOrNull() != null) { - mView.dwellAlpha = args[1].toFloat() - if (args[2].toFloat() > 200L) { - pw.println("alpha animation duration must be less than 200ms.") - } - mView.dwellAlphaDuration = kotlin.math.min(args[2].toLong(), 200L) - printDwellInfo(pw) + "dwell" -> { + showDwellRipple() + if (statusBarStateController.isDozing) { + printAodDwellInfo(pw) } else { - pw.println("expected two float arguments:" + - " <dwellAlpha> <dwellAlphaDuration>") - } - } - "dwellExpand" -> { - if (args.size > 1 && args[1].toFloatOrNull() != null) { - val expandedScale = args[1].toFloat() - if (expandedScale <= dwellScale) { - pw.println("invalid expandedScale. must be greater than " + - "dwellScale=$dwellScale, but given $expandedScale") - } else { - expandedDwellScale = expandedScale - } - printDwellInfo(pw) - } else { - pw.println("expected float argument <expandedScale>") + printLockScreenDwellInfo(pw) } } "fingerprint" -> { @@ -313,9 +341,7 @@ class AuthRippleController @Inject constructor( override fun help(pw: PrintWriter) { pw.println("Usage: adb shell cmd statusbar auth-ripple <command>") pw.println("Available commands:") - pw.println(" dwellScale <200ms_scale: float>") - pw.println(" dwellAlpha <alpha: float> <duration : long>") - pw.println(" dwellExpand <expanded_scale: float>") + pw.println(" dwell") pw.println(" fingerprint") pw.println(" face") pw.println(" custom <x-location: int> <y-location: int>") @@ -326,4 +352,8 @@ class AuthRippleController @Inject constructor( help(pw) } } + + companion object { + const val RIPPLE_ANIMATION_DURATION: Long = 1533 + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 8e1303713171..c6d26ffb9957 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -26,13 +26,10 @@ import android.graphics.PointF import android.util.AttributeSet import android.view.View import android.view.animation.PathInterpolator -import com.android.internal.R.attr.interpolator import com.android.internal.graphics.ColorUtils import com.android.systemui.animation.Interpolators -import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.charging.RippleShader -private const val RIPPLE_ANIMATION_DURATION: Long = 1533 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f /** @@ -45,12 +42,18 @@ private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f */ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { private val retractInterpolator = PathInterpolator(.05f, .93f, .1f, 1f) - private val dwellPulseDuration = 200L - var dwellAlphaDuration = dwellPulseDuration + + private val dwellPulseDuration = 50L + private val dwellAlphaDuration = dwellPulseDuration + private val dwellAlpha: Float = 1f private val dwellExpandDuration = 1200L - dwellPulseDuration - private val retractDuration = 400L - var dwellAlpha: Float = .5f + private val aodDwellPulseDuration = 50L + private var aodDwellAlphaDuration = aodDwellPulseDuration + private var aodDwellAlpha: Float = .8f + private var aodDwellExpandDuration = 1200L - aodDwellPulseDuration + + private val retractDuration = 400L private var alphaInDuration: Long = 0 private var unlockedRippleInProgress: Boolean = false private val rippleShader = RippleShader() @@ -142,7 +145,12 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at * Ripple that moves animates from an outer ripple ring of * startRadius => endRadius => expandedRadius */ - fun startDwellRipple(startRadius: Float, endRadius: Float, expandedRadius: Float) { + fun startDwellRipple( + startRadius: Float, + endRadius: Float, + expandedRadius: Float, + isDozing: Boolean + ) { if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) { return } @@ -153,12 +161,13 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at val endInitialDwellProgress = endRadius / radius / 4f val endExpandDwellProgress = expandedRadius / radius / 4f - val pulseOutEndAlpha = (255 * dwellAlpha).toInt() - val expandDwellEndAlpha = kotlin.math.min((255 * (dwellAlpha + .25f)).toInt(), 255) + val alpha = if (isDozing) aodDwellAlpha else dwellAlpha + val pulseOutEndAlpha = (255 * alpha).toInt() + val expandDwellEndAlpha = kotlin.math.min((255 * (alpha + .25f)).toInt(), 255) val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(startDwellProgress, endInitialDwellProgress).apply { interpolator = Interpolators.LINEAR_OUT_SLOW_IN - duration = dwellPulseDuration + duration = if (isDozing) aodDwellPulseDuration else dwellPulseDuration addUpdateListener { animator -> val now = animator.currentPlayTime rippleShader.progress = animator.animatedValue as Float @@ -170,7 +179,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at val dwellPulseOutAlphaAnimator = ValueAnimator.ofInt(0, pulseOutEndAlpha).apply { interpolator = Interpolators.LINEAR - duration = dwellAlphaDuration + duration = if (isDozing) aodDwellAlphaDuration else dwellAlphaDuration addUpdateListener { animator -> rippleShader.color = ColorUtils.setAlphaComponent( rippleShader.color, @@ -184,7 +193,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at val expandDwellRippleAnimator = ValueAnimator.ofFloat(endInitialDwellProgress, endExpandDwellProgress).apply { interpolator = Interpolators.LINEAR_OUT_SLOW_IN - duration = dwellExpandDuration + duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration addUpdateListener { animator -> val now = animator.currentPlayTime rippleShader.progress = animator.animatedValue as Float @@ -197,7 +206,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at val expandDwellAlphaAnimator = ValueAnimator.ofInt(pulseOutEndAlpha, expandDwellEndAlpha) .apply { interpolator = Interpolators.LINEAR - duration = dwellExpandDuration + duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration addUpdateListener { animator -> rippleShader.color = ColorUtils.setAlphaComponent( rippleShader.color, @@ -238,7 +247,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at /** * Ripple that bursts outwards from the position of the sensor to the edges of the screen */ - fun startUnlockedRipple(onAnimationEnd: Runnable?, lightReveal: LightRevealScrim?) { + fun startUnlockedRipple(onAnimationEnd: Runnable?) { if (unlockedRippleInProgress) { return // Ignore if ripple effect is already playing } @@ -254,7 +263,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at val rippleAnimator = ValueAnimator.ofFloat(rippleStart, 1f).apply { interpolator = Interpolators.LINEAR_OUT_SLOW_IN - duration = RIPPLE_ANIMATION_DURATION + duration = AuthRippleController.RIPPLE_ANIMATION_DURATION addUpdateListener { animator -> val now = animator.currentPlayTime rippleShader.progress = animator.animatedValue as Float @@ -264,14 +273,6 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at } } - val revealAnimator = ValueAnimator.ofFloat(.1f, 1f).apply { - interpolator = rippleAnimator.interpolator - duration = rippleAnimator.duration - addUpdateListener { animator -> - lightReveal?.revealAmount = animator.animatedValue as Float - } - } - val alphaInAnimator = ValueAnimator.ofInt(0, 255).apply { duration = alphaDuration addUpdateListener { animator -> @@ -286,7 +287,6 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at val animatorSet = AnimatorSet().apply { playTogether( rippleAnimator, - revealAnimator, alphaInAnimator ) addListener(object : AnimatorListenerAdapter() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt new file mode 100644 index 000000000000..b7404dfeb1cc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.content.Context +import android.hardware.display.DisplayManager +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import android.os.Handler +import android.view.Surface +import com.android.systemui.biometrics.BiometricDisplayListener.SensorType.Generic + +/** + * A listener for keeping overlays for biometric sensors aligned with the physical device + * device's screen. The [onChanged] will be dispatched on the [handler] + * whenever a relevant change to the device's configuration (orientation, fold, display change, + * etc.) may require the UI to change for the given [sensorType]. + */ +class BiometricDisplayListener( + private val context: Context, + private val displayManager: DisplayManager, + private val handler: Handler, + private val sensorType: SensorType = SensorType.Generic, + private val onChanged: () -> Unit +) : DisplayManager.DisplayListener { + + private var lastRotation = context.display?.rotation ?: Surface.ROTATION_0 + + override fun onDisplayAdded(displayId: Int) {} + override fun onDisplayRemoved(displayId: Int) {} + override fun onDisplayChanged(displayId: Int) { + val rotationChanged = didRotationChange() + + when (sensorType) { + is SensorType.SideFingerprint -> onChanged() + else -> { + if (rotationChanged) { + onChanged() + } + } + } + } + + private fun didRotationChange(): Boolean { + val rotation = context.display?.rotation ?: return false + val last = lastRotation + lastRotation = rotation + return last != rotation + } + + /** Listen for changes. */ + fun enable() { + displayManager.registerDisplayListener(this, handler) + } + + /** Stop listening for changes. */ + fun disable() { + displayManager.unregisterDisplayListener(this) + } + + /** + * Type of sensor to determine what kind of display changes require layouts. + * + * The [Generic] type should be used in cases where the modality can vary, such as + * biometric prompt (and this object will likely change as multi-mode auth is added). + */ + sealed class SensorType { + object Generic : SensorType() + data class UnderDisplayFingerprint( + val properties: FingerprintSensorPropertiesInternal + ) : SensorType() + data class SideFingerprint( + val properties: FingerprintSensorPropertiesInternal + ) : SensorType() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt deleted file mode 100644 index 98a03a1c444b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics - -import android.content.Context -import android.hardware.display.DisplayManager -import android.os.Handler -import android.view.OrientationEventListener -import android.view.Surface - -/** - * An [OrientationEventListener] that invokes the [onOrientationChanged] callback whenever - * the orientation of the device has changed in order to keep overlays for biometric sensors - * aligned with the device's screen. - */ -class BiometricOrientationEventListener( - private val context: Context, - private val onOrientationChanged: () -> Unit, - private val displayManager: DisplayManager, - private val handler: Handler -) : DisplayManager.DisplayListener { - - private var lastRotation = context.display?.rotation ?: Surface.ROTATION_0 - - override fun onDisplayAdded(displayId: Int) {} - override fun onDisplayRemoved(displayId: Int) {} - override fun onDisplayChanged(displayId: Int) { - val rotation = context.display?.rotation ?: return - if (lastRotation != rotation) { - lastRotation = rotation - - onOrientationChanged() - } - } - - fun enable() { - displayManager.registerDisplayListener(this, handler) - } - - fun disable() { - displayManager.unregisterDisplayListener(this) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS index 947466f3baaf..adb10f01b5e1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/biometrics/OWNERS @@ -1,3 +1,4 @@ set noparent include /services/core/java/com/android/server/biometrics/OWNERS +beverlyt@google.com diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java deleted file mode 100644 index 8f6e2498a00b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics; - -import static com.android.internal.util.Preconditions.checkArgument; -import static com.android.internal.util.Preconditions.checkNotNull; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.PixelFormat; -import android.hardware.display.DisplayManager; -import android.hardware.fingerprint.FingerprintManager; -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.hardware.fingerprint.ISidefpsController; -import android.os.Handler; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.Surface; -import android.view.WindowManager; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.R; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.util.concurrency.DelayableExecutor; - -import javax.inject.Inject; - -import kotlin.Unit; - -/** - * Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events. - */ -@SysUISingleton -public class SidefpsController { - private static final String TAG = "SidefpsController"; - @NonNull private final Context mContext; - @NonNull private final LayoutInflater mInflater; - private final FingerprintManager mFingerprintManager; - private final WindowManager mWindowManager; - private final DelayableExecutor mFgExecutor; - @VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener; - - // TODO: update mDisplayHeight and mDisplayWidth for multi-display devices - private final int mDisplayHeight; - private final int mDisplayWidth; - - private boolean mIsVisible = false; - @Nullable private SidefpsView mView; - - static final int SFPS_AFFORDANCE_WIDTH = 50; // in default portrait mode - - @NonNull - private final ISidefpsController mSidefpsControllerImpl = new ISidefpsController.Stub() { - @Override - public void show() { - mFgExecutor.execute(() -> { - SidefpsController.this.show(); - mIsVisible = true; - }); - } - - @Override - public void hide() { - mFgExecutor.execute(() -> { - SidefpsController.this.hide(); - mIsVisible = false; - }); - } - }; - - @VisibleForTesting - final FingerprintSensorPropertiesInternal mSensorProps; - private final WindowManager.LayoutParams mCoreLayoutParams; - - @Inject - public SidefpsController(@NonNull Context context, - @NonNull LayoutInflater inflater, - @Nullable FingerprintManager fingerprintManager, - @NonNull WindowManager windowManager, - @Main DelayableExecutor fgExecutor, - @NonNull DisplayManager displayManager, - @Main Handler handler) { - mContext = context; - mInflater = inflater; - mFingerprintManager = checkNotNull(fingerprintManager); - mWindowManager = windowManager; - mFgExecutor = fgExecutor; - mOrientationListener = new BiometricOrientationEventListener( - context, - () -> { - onOrientationChanged(); - return Unit.INSTANCE; - }, - displayManager, - handler); - - mSensorProps = findFirstSidefps(); - checkArgument(mSensorProps != null); - - mCoreLayoutParams = new WindowManager.LayoutParams( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, - getCoreLayoutParamFlags(), - PixelFormat.TRANSLUCENT); - mCoreLayoutParams.setTitle(TAG); - // Overrides default, avoiding status bars during layout - mCoreLayoutParams.setFitInsetsTypes(0); - mCoreLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; - mCoreLayoutParams.layoutInDisplayCutoutMode = - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; - - DisplayMetrics displayMetrics = new DisplayMetrics(); - windowManager.getDefaultDisplay().getMetrics(displayMetrics); - mDisplayHeight = displayMetrics.heightPixels; - mDisplayWidth = displayMetrics.widthPixels; - - mFingerprintManager.setSidefpsController(mSidefpsControllerImpl); - } - - private void show() { - mView = (SidefpsView) mInflater.inflate(R.layout.sidefps_view, null, false); - mView.setSensorProperties(mSensorProps); - mWindowManager.addView(mView, computeLayoutParams()); - - mOrientationListener.enable(); - } - - private void hide() { - if (mView != null) { - mWindowManager.removeView(mView); - mView.setOnTouchListener(null); - mView.setOnHoverListener(null); - mView = null; - } else { - Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden"); - } - - mOrientationListener.disable(); - } - - private void onOrientationChanged() { - // If mView is null or if view is hidden, then return. - if (mView == null || !mIsVisible) { - return; - } - - // If the overlay needs to be displayed with a new configuration, destroy the current - // overlay, and re-create and show the overlay with the updated LayoutParams. - hide(); - show(); - } - - @Nullable - private FingerprintSensorPropertiesInternal findFirstSidefps() { - for (FingerprintSensorPropertiesInternal props : - mFingerprintManager.getSensorPropertiesInternal()) { - if (props.isAnySidefpsType()) { - // TODO(b/188690214): L155-L173 can be removed once sensorLocationX, - // sensorLocationY, and sensorRadius are defined in sensorProps by the HAL - int sensorLocationX = 25; - int sensorLocationY = 610; - int sensorRadius = 112; - - FingerprintSensorPropertiesInternal tempProps = - new FingerprintSensorPropertiesInternal( - props.sensorId, - props.sensorStrength, - props.maxEnrollmentsPerUser, - props.componentInfo, - props.sensorType, - props.resetLockoutRequiresHardwareAuthToken, - sensorLocationX, - sensorLocationY, - sensorRadius - ); - props = tempProps; - return props; - } - } - return null; - } - - private int getCoreLayoutParamFlags() { - return WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; - } - - /** - * Computes layout params depending on orientation & folding configuration of device - */ - private WindowManager.LayoutParams computeLayoutParams() { - mCoreLayoutParams.flags = getCoreLayoutParamFlags(); - // Y value of top of affordance in portrait mode, X value of left of affordance in landscape - int sfpsLocationY = mSensorProps.sensorLocationY - mSensorProps.sensorRadius; - int sfpsAffordanceHeight = mSensorProps.sensorRadius * 2; - - // Calculate coordinates of drawable area for the fps affordance, accounting for orientation - switch (mContext.getDisplay().getRotation()) { - case Surface.ROTATION_90: - mCoreLayoutParams.x = sfpsLocationY; - mCoreLayoutParams.y = 0; - mCoreLayoutParams.height = SFPS_AFFORDANCE_WIDTH; - mCoreLayoutParams.width = sfpsAffordanceHeight; - break; - case Surface.ROTATION_270: - mCoreLayoutParams.x = mDisplayHeight - sfpsLocationY - sfpsAffordanceHeight; - mCoreLayoutParams.y = mDisplayWidth - SFPS_AFFORDANCE_WIDTH; - mCoreLayoutParams.height = SFPS_AFFORDANCE_WIDTH; - mCoreLayoutParams.width = sfpsAffordanceHeight; - break; - default: // Portrait - mCoreLayoutParams.x = mDisplayWidth - SFPS_AFFORDANCE_WIDTH; - mCoreLayoutParams.y = sfpsLocationY; - mCoreLayoutParams.height = sfpsAffordanceHeight; - mCoreLayoutParams.width = SFPS_AFFORDANCE_WIDTH; - } - return mCoreLayoutParams; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt new file mode 100644 index 000000000000..41c7ebe6fa4b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.biometrics + +import android.content.Context +import android.graphics.PixelFormat +import android.graphics.Rect +import android.hardware.biometrics.BiometricOverlayConstants +import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD +import android.hardware.display.DisplayManager +import android.hardware.fingerprint.FingerprintManager +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import android.hardware.fingerprint.ISidefpsController +import android.os.Handler +import android.util.Log +import android.view.Display +import android.view.Gravity +import android.view.LayoutInflater +import android.view.Surface +import android.view.View +import android.view.WindowManager +import androidx.annotation.RawRes +import com.airbnb.lottie.LottieAnimationView +import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.util.concurrency.DelayableExecutor +import javax.inject.Inject + +private const val TAG = "SidefpsController" + +/** + * Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events. + */ +@SysUISingleton +class SidefpsController @Inject constructor( + private val context: Context, + private val layoutInflater: LayoutInflater, + fingerprintManager: FingerprintManager?, + private val windowManager: WindowManager, + @Main mainExecutor: DelayableExecutor, + displayManager: DisplayManager, + @Main handler: Handler +) { + @VisibleForTesting + val sensorProps: FingerprintSensorPropertiesInternal = fingerprintManager + ?.sensorPropertiesInternal + ?.firstOrNull { it.isAnySidefpsType } + ?: throw IllegalStateException("no side fingerprint sensor") + + @VisibleForTesting + val orientationListener = BiometricDisplayListener( + context, + displayManager, + handler, + BiometricDisplayListener.SensorType.SideFingerprint(sensorProps) + ) { onOrientationChanged() } + + private var overlayView: View? = null + set(value) { + field?.let { oldView -> + windowManager.removeView(oldView) + orientationListener.disable() + } + field = value + field?.let { newView -> + windowManager.addView(newView, overlayViewParams) + orientationListener.enable() + } + } + + private val overlayViewParams = WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + PixelFormat.TRANSLUCENT + ).apply { + title = TAG + fitInsetsTypes = 0 // overrides default, avoiding status bars during layout + gravity = Gravity.TOP or Gravity.LEFT + layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY + } + + init { + fingerprintManager?.setSidefpsController(object : ISidefpsController.Stub() { + override fun show( + sensorId: Int, + @BiometricOverlayConstants.ShowReason reason: Int + ) = if (reason.isReasonToShow()) doShow() else hide(sensorId) + + private fun doShow() = mainExecutor.execute { + if (overlayView == null) { + overlayView = createOverlayForDisplay() + } else { + Log.v(TAG, "overlay already shown") + } + } + + override fun hide(sensorId: Int) = mainExecutor.execute { overlayView = null } + }) + } + + private fun onOrientationChanged() { + if (overlayView != null) { + overlayView = createOverlayForDisplay() + } + } + + private fun createOverlayForDisplay(): View { + val view = layoutInflater.inflate(R.layout.sidefps_view, null, false) + val display = context.display!! + + val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView + lottie.setAnimation(display.asSideFpsAnimation()) + view.rotation = display.asSideFpsAnimationRotation() + + updateOverlayParams(display, lottie.composition?.bounds ?: Rect()) + lottie.addLottieOnCompositionLoadedListener { + if (overlayView == view) { + updateOverlayParams(display, it.bounds) + windowManager.updateViewLayout(overlayView, overlayViewParams) + } + } + + return view + } + + private fun updateOverlayParams(display: Display, bounds: Rect) { + val isPortrait = display.isPortrait() + val size = windowManager.maximumWindowMetrics.bounds + val displayWidth = if (isPortrait) size.width() else size.height() + val displayHeight = if (isPortrait) size.height() else size.width() + val offsets = sensorProps.getLocation(display.uniqueId).let { location -> + if (location == null) { + Log.w(TAG, "No location specified for display: ${display.uniqueId}") + } + location ?: sensorProps.location + } + + // ignore sensorLocationX and sensorRadius since it's assumed to be on the side + // of the device and centered at sensorLocationY + val (x, y) = when (display.rotation) { + Surface.ROTATION_90 -> + Pair(offsets.sensorLocationY, 0) + Surface.ROTATION_270 -> + Pair(displayHeight - offsets.sensorLocationY - bounds.width(), displayWidth) + Surface.ROTATION_180 -> + Pair(0, displayHeight - offsets.sensorLocationY - bounds.height()) + else -> + Pair(displayWidth, offsets.sensorLocationY) + } + overlayViewParams.x = x + overlayViewParams.y = y + } +} + +@BiometricOverlayConstants.ShowReason +private fun Int.isReasonToShow(): Boolean = when (this) { + REASON_AUTH_KEYGUARD -> false + else -> true +} + +@RawRes +private fun Display.asSideFpsAnimation(): Int = when (rotation) { + Surface.ROTATION_0 -> R.raw.sfps_pulse + Surface.ROTATION_180 -> R.raw.sfps_pulse + else -> R.raw.sfps_pulse_landscape +} + +private fun Display.asSideFpsAnimationRotation(): Float = when (rotation) { + Surface.ROTATION_180 -> 180f + Surface.ROTATION_270 -> 180f + else -> 0f +} + +private fun Display.isPortrait(): Boolean = + rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsView.java deleted file mode 100644 index 4fc59d60ff62..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsView.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics; - -import static com.android.systemui.biometrics.SidefpsController.SFPS_AFFORDANCE_WIDTH; - -import android.annotation.NonNull; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.RectF; -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.util.AttributeSet; -import android.view.Surface; -import android.widget.FrameLayout; - -/** - * A view containing a normal drawable view for sidefps events. - */ -public class SidefpsView extends FrameLayout { - private static final String TAG = "SidefpsView"; - private static final int POINTER_SIZE_PX = 50; - private static final int ROUND_RADIUS = 15; - - @NonNull private final RectF mSensorRect; - @NonNull private final Paint mSensorRectPaint; - @NonNull private final Paint mPointerText; - @NonNull private final Context mContext; - - // Used to obtain the sensor location. - @NonNull private FingerprintSensorPropertiesInternal mSensorProps; - @Surface.Rotation private int mOrientation; - - public SidefpsView(Context context, AttributeSet attrs) { - super(context, attrs); - super.setWillNotDraw(false); - mContext = context; - mPointerText = new Paint(0 /* flags */); - mPointerText.setAntiAlias(true); - mPointerText.setColor(Color.WHITE); - mPointerText.setTextSize(POINTER_SIZE_PX); - - mSensorRect = new RectF(); - mSensorRectPaint = new Paint(0 /* flags */); - mSensorRectPaint.setAntiAlias(true); - mSensorRectPaint.setColor(Color.BLUE); // TODO: Fix Color - mSensorRectPaint.setStyle(Paint.Style.FILL); - } - - void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) { - mSensorProps = properties; - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - canvas.drawRoundRect(mSensorRect, ROUND_RADIUS, ROUND_RADIUS, mSensorRectPaint); - int x, y; - if (mOrientation == Surface.ROTATION_90 || mOrientation == Surface.ROTATION_270) { - x = mSensorProps.sensorRadius + 10; - y = SFPS_AFFORDANCE_WIDTH / 2 + 15; - } else { - x = SFPS_AFFORDANCE_WIDTH / 2 - 10; - y = mSensorProps.sensorRadius + 30; - } - canvas.drawText( - ">", - x, - y, - mPointerText - ); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - mOrientation = mContext.getDisplay().getRotation(); - if (mOrientation == Surface.ROTATION_90 || mOrientation == Surface.ROTATION_270) { - right = mSensorProps.sensorRadius * 2; - bottom = SFPS_AFFORDANCE_WIDTH; - } else { - right = SFPS_AFFORDANCE_WIDTH; - bottom = mSensorProps.sensorRadius * 2; - } - - mSensorRect.set( - 0, - 0, - right, - bottom); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 62b9fd44d800..56418680e74d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -16,7 +16,7 @@ package com.android.systemui.biometrics; -import static android.hardware.fingerprint.IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD; +import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkNotNull; @@ -32,6 +32,8 @@ import android.content.IntentFilter; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.RectF; +import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.biometrics.SensorLocationInternal; import android.hardware.display.DisplayManager; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -39,7 +41,6 @@ import android.hardware.fingerprint.IUdfpsOverlayController; import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; import android.media.AudioAttributes; import android.os.Handler; -import android.os.Looper; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -63,7 +64,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.dump.DumpManager; -import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -71,6 +71,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -93,7 +94,7 @@ import kotlin.Unit; * controls/manages all UDFPS sensors. In other words, a single controller is registered with * {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such * as {@link FingerprintManager#onPointerDown(int, int, int, float, float)} or - * {@link IUdfpsOverlayController#showUdfpsOverlay(int)}should all have + * {@link IUdfpsOverlayController#showUdfpsOverlay(int)} should all have * {@code sensorId} parameters. */ @SuppressWarnings("deprecation") @@ -117,9 +118,7 @@ public class UdfpsController implements DozeReceiver { @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager; @NonNull private final DumpManager mDumpManager; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @NonNull private final KeyguardViewMediator mKeyguardViewMediator; @Nullable private final Vibrator mVibrator; - @NonNull private final Handler mMainHandler; @NonNull private final FalsingManager mFalsingManager; @NonNull private final PowerManager mPowerManager; @NonNull private final AccessibilityManager mAccessibilityManager; @@ -128,7 +127,9 @@ public class UdfpsController implements DozeReceiver { @NonNull private final KeyguardBypassController mKeyguardBypassController; @NonNull private final ConfigurationController mConfigurationController; @NonNull private final SystemClock mSystemClock; - @VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener; + @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener; + @NonNull private final UnlockedScreenOffAnimationController + mUnlockedScreenOffAnimationController; // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple // sensors, this, in addition to a lot of the code here, will be updated. @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps; @@ -241,8 +242,8 @@ public class UdfpsController implements DozeReceiver { @NonNull IUdfpsOverlayControllerCallback callback) { mFgExecutor.execute(() -> { final UdfpsEnrollHelper enrollHelper; - if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR - || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) { + if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR + || reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING) { enrollHelper = new UdfpsEnrollHelper(mContext, reason); } else { enrollHelper = null; @@ -320,7 +321,7 @@ public class UdfpsController implements DozeReceiver { @Override public void onReceive(Context context, Intent intent) { if (mServerRequest != null - && mServerRequest.mRequestReason != REASON_AUTH_FPM_KEYGUARD + && mServerRequest.mRequestReason != REASON_AUTH_KEYGUARD && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, mRequestReason: " + mServerRequest.mRequestReason); @@ -517,7 +518,6 @@ public class UdfpsController implements DozeReceiver { @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager, @NonNull DumpManager dumpManager, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, - @NonNull KeyguardViewMediator keyguardViewMediator, @NonNull FalsingManager falsingManager, @NonNull PowerManager powerManager, @NonNull AccessibilityManager accessibilityManager, @@ -531,11 +531,11 @@ public class UdfpsController implements DozeReceiver { @NonNull DisplayManager displayManager, @Main Handler mainHandler, @NonNull ConfigurationController configurationController, - @NonNull SystemClock systemClock) { + @NonNull SystemClock systemClock, + @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { mContext = context; mExecution = execution; // TODO (b/185124905): inject main handler and vibrator once done prototyping - mMainHandler = new Handler(Looper.getMainLooper()); mVibrator = vibrator; mInflater = inflater; // The fingerprint manager is queried for UDFPS before this class is constructed, so the @@ -549,7 +549,6 @@ public class UdfpsController implements DozeReceiver { mKeyguardViewManager = statusBarKeyguardViewManager; mDumpManager = dumpManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mKeyguardViewMediator = keyguardViewMediator; mFalsingManager = falsingManager; mPowerManager = powerManager; mAccessibilityManager = accessibilityManager; @@ -557,21 +556,23 @@ public class UdfpsController implements DozeReceiver { mHbmProvider = hbmProvider.orElse(null); screenLifecycle.addObserver(mScreenObserver); mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON; - mOrientationListener = new BiometricOrientationEventListener( - context, - () -> { - onOrientationChanged(); - return Unit.INSTANCE; - }, - displayManager, - mainHandler); mKeyguardBypassController = keyguardBypassController; mConfigurationController = configurationController; mSystemClock = systemClock; + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mSensorProps = findFirstUdfps(); // At least one UDFPS sensor exists checkArgument(mSensorProps != null); + mOrientationListener = new BiometricDisplayListener( + context, + displayManager, + mainHandler, + new BiometricDisplayListener.SensorType.UnderDisplayFingerprint(mSensorProps), + () -> { + onOrientationChanged(); + return Unit.INSTANCE; + }); mCoreLayoutParams = new WindowManager.LayoutParams( WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, @@ -641,10 +642,11 @@ public class UdfpsController implements DozeReceiver { // on lockscreen and for the udfps light reveal animation on keyguard. // Keyguard is only shown in portrait mode for now, so this will need to // be updated if that ever changes. - return new RectF(mSensorProps.sensorLocationX - mSensorProps.sensorRadius, - mSensorProps.sensorLocationY - mSensorProps.sensorRadius, - mSensorProps.sensorLocationX + mSensorProps.sensorRadius, - mSensorProps.sensorLocationY + mSensorProps.sensorRadius); + final SensorLocationInternal location = mSensorProps.getLocation(); + return new RectF(location.sensorLocationX - location.sensorRadius, + location.sensorLocationY - location.sensorRadius, + location.sensorLocationX + location.sensorRadius, + location.sensorLocationY + location.sensorRadius); } private void updateOverlay() { @@ -657,6 +659,20 @@ public class UdfpsController implements DozeReceiver { } } + private boolean shouldRotate(@Nullable UdfpsAnimationViewController animation) { + if (!(animation instanceof UdfpsKeyguardViewController)) { + // always rotate view if we're not on the keyguard + return true; + } + + // on the keyguard, make sure we don't rotate if we're going to sleep or not occluded + if (mKeyguardUpdateMonitor.isGoingToSleep() || !mKeyguardStateController.isOccluded()) { + return false; + } + + return true; + } + private WindowManager.LayoutParams computeLayoutParams( @Nullable UdfpsAnimationViewController animation) { final int paddingX = animation != null ? animation.getPaddingX() : 0; @@ -668,10 +684,11 @@ public class UdfpsController implements DozeReceiver { } // Default dimensions assume portrait mode. - mCoreLayoutParams.x = mSensorProps.sensorLocationX - mSensorProps.sensorRadius - paddingX; - mCoreLayoutParams.y = mSensorProps.sensorLocationY - mSensorProps.sensorRadius - paddingY; - mCoreLayoutParams.height = 2 * mSensorProps.sensorRadius + 2 * paddingX; - mCoreLayoutParams.width = 2 * mSensorProps.sensorRadius + 2 * paddingY; + final SensorLocationInternal location = mSensorProps.getLocation(); + mCoreLayoutParams.x = location.sensorLocationX - location.sensorRadius - paddingX; + mCoreLayoutParams.y = location.sensorLocationY - location.sensorRadius - paddingY; + mCoreLayoutParams.height = 2 * location.sensorRadius + 2 * paddingX; + mCoreLayoutParams.width = 2 * location.sensorRadius + 2 * paddingY; Point p = new Point(); // Gets the size based on the current rotation of the display. @@ -680,24 +697,28 @@ public class UdfpsController implements DozeReceiver { // Transform dimensions if the device is in landscape mode switch (mContext.getDisplay().getRotation()) { case Surface.ROTATION_90: - if (animation instanceof UdfpsKeyguardViewController - && mKeyguardUpdateMonitor.isGoingToSleep()) { + if (!shouldRotate(animation)) { + Log.v(TAG, "skip rotating udfps location ROTATION_90"); break; + } else { + Log.v(TAG, "rotate udfps location ROTATION_90"); } - mCoreLayoutParams.x = mSensorProps.sensorLocationY - mSensorProps.sensorRadius + mCoreLayoutParams.x = location.sensorLocationY - location.sensorRadius - paddingX; - mCoreLayoutParams.y = p.y - mSensorProps.sensorLocationX - mSensorProps.sensorRadius + mCoreLayoutParams.y = p.y - location.sensorLocationX - location.sensorRadius - paddingY; break; case Surface.ROTATION_270: - if (animation instanceof UdfpsKeyguardViewController - && mKeyguardUpdateMonitor.isGoingToSleep()) { + if (!shouldRotate(animation)) { + Log.v(TAG, "skip rotating udfps location ROTATION_270"); break; + } else { + Log.v(TAG, "rotate udfps location ROTATION_270"); } - mCoreLayoutParams.x = p.x - mSensorProps.sensorLocationY - mSensorProps.sensorRadius + mCoreLayoutParams.x = p.x - location.sensorLocationY - location.sensorRadius - paddingX; - mCoreLayoutParams.y = mSensorProps.sensorLocationX - mSensorProps.sensorRadius + mCoreLayoutParams.y = location.sensorLocationX - location.sensorRadius - paddingY; break; @@ -747,9 +768,9 @@ public class UdfpsController implements DozeReceiver { // This view overlaps the sensor area, so prevent it from being selectable // during a11y. - if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR - || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING - || reason == IUdfpsOverlayController.REASON_AUTH_BP) { + if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR + || reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING + || reason == BiometricOverlayConstants.REASON_AUTH_BP) { mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); } @@ -767,8 +788,8 @@ public class UdfpsController implements DozeReceiver { private UdfpsAnimationViewController inflateUdfpsAnimation(int reason) { switch (reason) { - case IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR: - case IUdfpsOverlayController.REASON_ENROLL_ENROLLING: + case BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR: + case BiometricOverlayConstants.REASON_ENROLL_ENROLLING: UdfpsEnrollView enrollView = (UdfpsEnrollView) mInflater.inflate( R.layout.udfps_enroll_view, null); mView.addView(enrollView); @@ -780,7 +801,7 @@ public class UdfpsController implements DozeReceiver { mStatusBarOptional, mDumpManager ); - case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD: + case BiometricOverlayConstants.REASON_AUTH_KEYGUARD: UdfpsKeyguardView keyguardView = (UdfpsKeyguardView) mInflater.inflate(R.layout.udfps_keyguard_view, null); mView.addView(keyguardView); @@ -790,16 +811,15 @@ public class UdfpsController implements DozeReceiver { mStatusBarOptional, mKeyguardViewManager, mKeyguardUpdateMonitor, - mFgExecutor, mDumpManager, - mKeyguardViewMediator, mLockscreenShadeTransitionController, mConfigurationController, mSystemClock, mKeyguardStateController, + mUnlockedScreenOffAnimationController, this ); - case IUdfpsOverlayController.REASON_AUTH_BP: + case BiometricOverlayConstants.REASON_AUTH_BP: // note: empty controller, currently shows no visual affordance UdfpsBpView bpView = (UdfpsBpView) mInflater.inflate(R.layout.udfps_bp_view, null); mView.addView(bpView); @@ -809,7 +829,7 @@ public class UdfpsController implements DozeReceiver { mStatusBarOptional, mDumpManager ); - case IUdfpsOverlayController.REASON_AUTH_FPM_OTHER: + case BiometricOverlayConstants.REASON_AUTH_OTHER: UdfpsFpmOtherView authOtherView = (UdfpsFpmOtherView) mInflater.inflate(R.layout.udfps_fpm_other_view, null); mView.addView(authOtherView); @@ -861,6 +881,10 @@ public class UdfpsController implements DozeReceiver { return; } + if (!mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { + return; + } + mAodInterruptRunnable = () -> { mIsAodInterruptActive = true; // Since the sensor that triggers the AOD interrupt doesn't provide diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java index 7ccfb865cd5a..6cc8acf07c50 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Insets; import android.graphics.Rect; +import android.hardware.biometrics.SensorLocationInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.util.Log; import android.view.Surface; @@ -93,7 +94,7 @@ public class UdfpsDialogMeasureAdapter { // Go through each of the children and do the custom measurement. int totalHeight = 0; final int numChildren = mView.getChildCount(); - final int sensorDiameter = mSensorProps.sensorRadius * 2; + final int sensorDiameter = mSensorProps.getLocation().sensorRadius * 2; for (int i = 0; i < numChildren; i++) { final View child = mView.getChildAt(i); if (child.getId() == R.id.biometric_icon_frame) { @@ -160,7 +161,7 @@ public class UdfpsDialogMeasureAdapter { final int horizontalSpacerWidth = calculateHorizontalSpacerWidthForLandscape( mSensorProps, displayWidth, dialogMargin, horizontalInset); - final int sensorDiameter = mSensorProps.sensorRadius * 2; + final int sensorDiameter = mSensorProps.getLocation().sensorRadius * 2; final int remeasuredWidth = sensorDiameter + 2 * horizontalSpacerWidth; int remeasuredHeight = 0; @@ -257,10 +258,10 @@ public class UdfpsDialogMeasureAdapter { @NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayHeightPx, int textIndicatorHeightPx, int buttonBarHeightPx, int dialogMarginPx, int navbarBottomInsetPx) { - + final SensorLocationInternal location = sensorProperties.getLocation(); final int sensorDistanceFromBottom = displayHeightPx - - sensorProperties.sensorLocationY - - sensorProperties.sensorRadius; + - location.sensorLocationY + - location.sensorRadius; final int spacerHeight = sensorDistanceFromBottom - textIndicatorHeightPx @@ -318,10 +319,10 @@ public class UdfpsDialogMeasureAdapter { static int calculateHorizontalSpacerWidthForLandscape( @NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayWidthPx, int dialogMarginPx, int navbarHorizontalInsetPx) { - + final SensorLocationInternal location = sensorProperties.getLocation(); final int sensorDistanceFromEdge = displayWidthPx - - sensorProperties.sensorLocationY - - sensorProperties.sensorRadius; + - location.sensorLocationY + - location.sensorRadius; final int horizontalPadding = sensorDistanceFromEdge - dialogMarginPx diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java index 19148e383005..c6d2192b5b05 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.PointF; -import android.hardware.fingerprint.IUdfpsOverlayController; +import android.hardware.biometrics.BiometricOverlayConstants; import android.os.Build; import android.os.UserHandle; import android.provider.Settings; @@ -119,7 +119,7 @@ public class UdfpsEnrollHelper { } boolean shouldShowProgressBar() { - return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING; + return mEnrollReason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING; } void onEnrollmentProgress(int remaining) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java index d9edef48b3d4..c83006d8092a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java @@ -63,7 +63,7 @@ public class UdfpsEnrollView extends UdfpsAnimationView { void updateSensorLocation(@NonNull FingerprintSensorPropertiesInternal sensorProps) { View fingerprintAccessibilityView = findViewById(R.id.udfps_enroll_accessibility_view); - final int sensorHeight = sensorProps.sensorRadius * 2; + final int sensorHeight = sensorProps.getLocation().sensorRadius * 2; final int sensorWidth = sensorHeight; ViewGroup.LayoutParams params = fingerprintAccessibilityView.getLayoutParams(); params.width = sensorWidth; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 8ce4c3b8577d..db93b26d99e5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -26,36 +26,34 @@ import android.view.MotionEvent; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; import com.android.systemui.dump.DumpManager; -import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.time.SystemClock; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Optional; - /** * Class that coordinates non-HBM animations during keyguard authentication. */ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> { @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @NonNull private final DelayableExecutor mExecutor; - @NonNull private final KeyguardViewMediator mKeyguardViewMediator; @NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController; @NonNull private final ConfigurationController mConfigurationController; @NonNull private final SystemClock mSystemClock; @NonNull private final KeyguardStateController mKeyguardStateController; @NonNull private final UdfpsController mUdfpsController; + @NonNull private final UnlockedScreenOffAnimationController + mUnlockedScreenOffAnimationController; private boolean mShowingUdfpsBouncer; private boolean mUdfpsRequested; @@ -82,24 +80,22 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud @NonNull Optional<StatusBar> statusBarOptional, @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, - @NonNull DelayableExecutor mainDelayableExecutor, @NonNull DumpManager dumpManager, - @NonNull KeyguardViewMediator keyguardViewMediator, @NonNull LockscreenShadeTransitionController transitionController, @NonNull ConfigurationController configurationController, @NonNull SystemClock systemClock, @NonNull KeyguardStateController keyguardStateController, + @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, @NonNull UdfpsController udfpsController) { super(view, statusBarStateController, statusBarOptional, dumpManager); mKeyguardViewManager = statusBarKeyguardViewManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mExecutor = mainDelayableExecutor; - mKeyguardViewMediator = keyguardViewMediator; mLockScreenShadeTransitionController = transitionController; mConfigurationController = configurationController; mSystemClock = systemClock; mKeyguardStateController = keyguardStateController; mUdfpsController = udfpsController; + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; } @Override @@ -138,6 +134,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor); mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(this); + mUnlockedScreenOffAnimationController.addCallback(mUnlockedScreenOffCallback); } @Override @@ -156,6 +153,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) { mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null); } + mUnlockedScreenOffAnimationController.removeCallback(mUnlockedScreenOffCallback); } @Override @@ -428,4 +426,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud updatePauseAuth(); } }; + + private final UnlockedScreenOffAnimationController.Callback mUnlockedScreenOffCallback = + (linear, eased) -> mStateListener.onDozeAmountChanged(linear, eased); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java index 15f77ffc08fd..6d31ef0e7701 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java @@ -25,6 +25,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.PointF; import android.graphics.RectF; +import android.hardware.biometrics.SensorLocationInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Build; import android.os.UserHandle; @@ -141,11 +142,12 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin : mAnimationViewController.getPaddingX(); int paddingY = mAnimationViewController == null ? 0 : mAnimationViewController.getPaddingY(); + final SensorLocationInternal location = mSensorProps.getLocation(); mSensorRect.set( paddingX, paddingY, - 2 * mSensorProps.sensorRadius + paddingX, - 2 * mSensorProps.sensorRadius + paddingY); + 2 * location.sensorRadius + paddingX, + 2 * location.sensorRadius + paddingY); if (mAnimationViewController != null) { mAnimationViewController.onSensorRectUpdated(new RectF(mSensorRect)); diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt index 4104e3184efd..46a03e809b06 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt @@ -16,6 +16,10 @@ package com.android.systemui.controls.ui +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter import android.os.Bundle import android.view.View import android.view.ViewGroup @@ -23,18 +27,25 @@ import android.view.WindowInsets import android.view.WindowInsets.Type import com.android.systemui.R +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.management.ControlsAnimations import com.android.systemui.util.LifecycleActivity import javax.inject.Inject /** - * Displays Device Controls inside an activity + * Displays Device Controls inside an activity. This activity is available to be displayed over the + * lockscreen if the user has allowed it via + * [android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS]. This activity will be + * destroyed on SCREEN_OFF events, due to issues with occluded activities over lockscreen as well as + * user expectations for the activity to not continue running. */ class ControlsActivity @Inject constructor( - private val uiController: ControlsUiController + private val uiController: ControlsUiController, + private val broadcastDispatcher: BroadcastDispatcher ) : LifecycleActivity() { private lateinit var parent: ViewGroup + private lateinit var broadcastReceiver: BroadcastReceiver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -62,6 +73,8 @@ class ControlsActivity @Inject constructor( WindowInsets.CONSUMED } } + + initBroadcastReceiver() } override fun onResume() { @@ -83,4 +96,25 @@ class ControlsActivity @Inject constructor( uiController.hide() } + + override fun onDestroy() { + super.onDestroy() + + broadcastDispatcher.unregisterReceiver(broadcastReceiver) + } + + private fun initBroadcastReceiver() { + broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val action = intent.getAction() + if (Intent.ACTION_SCREEN_OFF.equals(action)) { + finish() + } + } + } + + val filter = IntentFilter() + filter.addAction(Intent.ACTION_SCREEN_OFF) + broadcastDispatcher.registerReceiver(broadcastReceiver, filter) + } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 567d0cb3e6cb..72da7f4f893a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -256,13 +256,13 @@ class ControlsUiControllerImpl @Inject constructor ( // Force animations when transitioning from a dialog to an activity intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true) - if (keyguardStateController.isUnlocked()) { + if (keyguardStateController.isShowing()) { + activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */) + } else { activityContext.startActivity( intent, ActivityOptions.makeSceneTransitionAnimation(activityContext as Activity).toBundle() ) - } else { - activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */) } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index 41964652ac49..845d7dc26ee5 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -214,6 +214,14 @@ public class DozeLog implements Dumpable { } /** + * Appends display state delayed by UDFPS event to the logs + * @param delayedDisplayState the display screen state that was delayed + */ + public void traceDisplayStateDelayedByUdfps(int delayedDisplayState) { + mLogger.logDisplayStateDelayedByUdfps(delayedDisplayState); + } + + /** * Appends display state changed event to the logs * @param displayState new DozeMachine state */ @@ -267,6 +275,13 @@ public class DozeLog implements Dumpable { } /** + * Appends sensor event dropped event to logs + */ + public void traceSensorEventDropped(int sensorEvent, String reason) { + mLogger.logSensorEventDropped(sensorEvent, reason); + } + + /** * Appends pulse dropped event to logs * @param reason why the pulse was dropped */ diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index 9bc74be9b9c3..dc186182432f 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -160,6 +160,14 @@ class DozeLogger @Inject constructor( }) } + fun logDisplayStateDelayedByUdfps(delayedDisplayState: Int) { + buffer.log(TAG, INFO, { + str1 = Display.stateToString(delayedDisplayState) + }, { + "Delaying display state change to: $str1 due to UDFPS activity" + }) + } + fun logDisplayStateChanged(displayState: Int) { buffer.log(TAG, INFO, { str1 = Display.stateToString(displayState) @@ -197,6 +205,15 @@ class DozeLogger @Inject constructor( }) } + fun logSensorEventDropped(sensorEvent: Int, reason: String) { + buffer.log(TAG, INFO, { + int1 = sensorEvent + str1 = reason + }, { + "SensorEvent [$int1] dropped, reason=$str1" + }) + } + fun logPulseDropped(reason: String) { buffer.log(TAG, INFO, { str1 = reason diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 98d2739836a9..da7b389fbd36 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -29,7 +29,6 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; -import android.view.Display; import com.android.systemui.dock.DockManager; import com.android.systemui.doze.dagger.BrightnessSensor; @@ -111,7 +110,15 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { switch (newState) { case INITIALIZED: + resetBrightnessToDefault(); + break; + case DOZE_AOD: + case DOZE_REQUEST_PULSE: + case DOZE_AOD_DOCKED: + setLightSensorEnabled(true); + break; case DOZE: + setLightSensorEnabled(false); resetBrightnessToDefault(); break; case FINISH: @@ -124,22 +131,6 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi } } - @Override - public void onScreenState(int state) { - boolean isDockedScreenOn = state == Display.STATE_ON && mDockManager.isDocked(); - if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND - || (isDockedScreenOn && shouldRegisterLightSensorWhenScreenOnDocked())) { - setLightSensorEnabled(true); - } else { - setLightSensorEnabled(false); - } - } - - private boolean shouldRegisterLightSensorWhenScreenOnDocked() { - return !mDozeParameters.brightnessUsesProx() - || !mDozeParameters.getSelectivelyRegisterSensorsUsingProx(); - } - private void onDestroy() { setLightSensorEnabled(false); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java index 8c50a16b566f..8f1486b0c7cb 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java @@ -26,6 +26,11 @@ import android.os.Handler; import android.util.Log; import android.view.Display; +import androidx.annotation.Nullable; + +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.biometrics.AuthController; +import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.doze.dagger.WrappedService; @@ -34,6 +39,7 @@ import com.android.systemui.util.wakelock.SettableWakeLock; import com.android.systemui.util.wakelock.WakeLock; import javax.inject.Inject; +import javax.inject.Provider; /** * Controls the screen when dozing. @@ -56,23 +62,61 @@ public class DozeScreenState implements DozeMachine.Part { */ public static final int ENTER_DOZE_HIDE_WALLPAPER_DELAY = 2500; + /** + * Add an extra delay to the transition to DOZE when udfps is current activated before + * the display state transitions from ON => DOZE. + */ + public static final int UDFPS_DISPLAY_STATE_DELAY = 1200; + private final DozeMachine.Service mDozeService; private final Handler mHandler; private final Runnable mApplyPendingScreenState = this::applyPendingScreenState; private final DozeParameters mParameters; private final DozeHost mDozeHost; + private final AuthController mAuthController; + private final Provider<UdfpsController> mUdfpsControllerProvider; + @Nullable private UdfpsController mUdfpsController; + private final DozeLog mDozeLog; private int mPendingScreenState = Display.STATE_UNKNOWN; private SettableWakeLock mWakeLock; @Inject - public DozeScreenState(@WrappedService DozeMachine.Service service, @Main Handler handler, - DozeHost host, DozeParameters parameters, WakeLock wakeLock) { + public DozeScreenState( + @WrappedService DozeMachine.Service service, + @Main Handler handler, + DozeHost host, + DozeParameters parameters, + WakeLock wakeLock, + AuthController authController, + Provider<UdfpsController> udfpsControllerProvider, + DozeLog dozeLog) { mDozeService = service; mHandler = handler; mParameters = parameters; mDozeHost = host; mWakeLock = new SettableWakeLock(wakeLock, TAG); + mAuthController = authController; + mUdfpsControllerProvider = udfpsControllerProvider; + mDozeLog = dozeLog; + + updateUdfpsController(); + if (mUdfpsController == null) { + mAuthController.addCallback(new AuthController.Callback() { + @Override + public void onAllAuthenticatorsRegistered() { + updateUdfpsController(); + } + }); + } + } + + private void updateUdfpsController() { + if (mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) { + mUdfpsController = mUdfpsControllerProvider.get(); + } else { + mUdfpsController = null; + } } @Override @@ -110,21 +154,28 @@ public class DozeScreenState implements DozeMachine.Part { mPendingScreenState = screenState; // Delay screen state transitions even longer while animations are running. - boolean shouldDelayTransition = newState == DOZE_AOD + boolean shouldDelayTransitionEnteringDoze = newState == DOZE_AOD && mParameters.shouldControlScreenOff() && !turningOn; - if (shouldDelayTransition) { + // Delay screen state transition longer if UDFPS is actively authenticating a fp + boolean shouldDelayTransitionForUDFPS = newState == DOZE_AOD + && mUdfpsController != null && mUdfpsController.isFingerDown(); + + if (shouldDelayTransitionEnteringDoze || shouldDelayTransitionForUDFPS) { mWakeLock.setAcquired(true); } if (!messagePending) { if (DEBUG) { Log.d(TAG, "Display state changed to " + screenState + " delayed by " - + (shouldDelayTransition ? ENTER_DOZE_DELAY : 1)); + + (shouldDelayTransitionEnteringDoze ? ENTER_DOZE_DELAY : 1)); } - if (shouldDelayTransition) { + if (shouldDelayTransitionEnteringDoze) { mHandler.postDelayed(mApplyPendingScreenState, ENTER_DOZE_DELAY); + } else if (shouldDelayTransitionForUDFPS) { + mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState); + mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY); } else { mHandler.post(mApplyPendingScreenState); } @@ -139,6 +190,12 @@ public class DozeScreenState implements DozeMachine.Part { } private void applyPendingScreenState() { + if (mUdfpsController != null && mUdfpsController.isFingerDown()) { + mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState); + mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY); + return; + } + applyScreenState(mPendingScreenState); mPendingScreenState = Display.STATE_UNKNOWN; } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 455f3c0d6ac2..756adca724e9 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -41,6 +41,7 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.doze.DozeMachine.State; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.Assert; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.sensors.AsyncSensorManager; @@ -92,6 +93,7 @@ public class DozeTriggers implements DozeMachine.Part { private final BroadcastDispatcher mBroadcastDispatcher; private final AuthController mAuthController; private final DelayableExecutor mMainExecutor; + private final KeyguardStateController mKeyguardStateController; private final UiEventLogger mUiEventLogger; private long mNotificationPulseTime; @@ -179,7 +181,8 @@ public class DozeTriggers implements DozeMachine.Part { DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher, SecureSettings secureSettings, AuthController authController, @Main DelayableExecutor mainExecutor, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + KeyguardStateController keyguardStateController) { mContext = context; mDozeHost = dozeHost; mConfig = config; @@ -198,6 +201,7 @@ public class DozeTriggers implements DozeMachine.Part { mAuthController = authController; mMainExecutor = mainExecutor; mUiEventLogger = uiEventLogger; + mKeyguardStateController = keyguardStateController; } @Override @@ -294,6 +298,7 @@ public class DozeTriggers implements DozeMachine.Part { proximityCheckThenCall((result) -> { if (result != null && result) { // In pocket, drop event. + mDozeLog.traceSensorEventDropped(pulseReason, "prox reporting near"); return; } if (isDoubleTap || isTap) { @@ -302,6 +307,10 @@ public class DozeTriggers implements DozeMachine.Part { } gentleWakeUp(pulseReason); } else if (isPickup) { + if (shouldDropPickupEvent()) { + mDozeLog.traceSensorEventDropped(pulseReason, "keyguard occluded"); + return; + } gentleWakeUp(pulseReason); } else if (isUdfpsLongPress) { final State state = mMachine.getState(); @@ -320,7 +329,7 @@ public class DozeTriggers implements DozeMachine.Part { }, true /* alreadyPerformedProxCheck */, pulseReason); } - if (isPickup) { + if (isPickup && !shouldDropPickupEvent()) { final long timeSinceNotification = SystemClock.elapsedRealtime() - mNotificationPulseTime; final boolean withinVibrationThreshold = @@ -329,6 +338,10 @@ public class DozeTriggers implements DozeMachine.Part { } } + private boolean shouldDropPickupEvent() { + return mKeyguardStateController.isOccluded(); + } + private void gentleWakeUp(int reason) { // Log screen wake up reason (lift/pickup, tap, double-tap) Optional.ofNullable(DozingUpdateUiEvent.fromReason(reason)) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 281771860cb7..1561eb6368a8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -818,6 +818,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private final KeyguardStateController mKeyguardStateController; private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy; + private boolean mWallpaperSupportsAmbientMode; /** * Injected constructor. See {@link KeyguardModule}. @@ -2089,13 +2090,14 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, int flags = 0; if (mKeyguardViewControllerLazy.get().shouldDisableWindowAnimationsForUnlock() - || (mWakeAndUnlocking && !mPulsing) - || isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) { + || mWakeAndUnlocking && !mWallpaperSupportsAmbientMode) { flags |= WindowManagerPolicyConstants .KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; } if (mKeyguardViewControllerLazy.get().isGoingToNotificationShade() - || (mWakeAndUnlocking && mPulsing)) { + || mWakeAndUnlocking && mWallpaperSupportsAmbientMode) { + // When the wallpaper supports ambient mode, the scrim isn't fully opaque during + // wake and unlock and we should fade in the app on top of the wallpaper flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; } if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) { @@ -2565,9 +2567,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private void handleNotifyScreenTurnedOn() { Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurnedOn"); - if (LatencyTracker.isEnabled(mContext)) { - LatencyTracker.getInstance(mContext).onActionEnd(LatencyTracker.ACTION_TURN_ON_SCREEN); - } synchronized (this) { if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOn"); mKeyguardViewControllerLazy.get().onScreenTurnedOn(); @@ -2784,6 +2783,15 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, mPulsing = pulsing; } + /** + * Set if the wallpaper supports ambient mode. This is used to trigger the right animation. + * In case it does support it, we have to fade in the incoming app, otherwise we'll reveal it + * with the light reveal scrim. + */ + public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) { + mWallpaperSupportsAmbientMode = supportsAmbientMode; + } + private static class StartKeyguardExitAnimParams { @WindowManager.TransitionOldType int mTransit; diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java new file mode 100644 index 000000000000..c8afd72d1dde --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CollapsedSbFragmentLog.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** + * A {@link LogBuffer} for + * {@link com.android.systemui.statusbar.phone.CollapsedStatusBarFragment}-related messages. + */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface CollapsedSbFragmentLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 19193f9eceb2..84c5a571c857 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -100,6 +100,17 @@ public class LogModule { return factory.create("PrivacyLog", 100); } + /** + * Provides a logging buffer for + * {@link com.android.systemui.statusbar.phone.CollapsedStatusBarFragment}. + */ + @Provides + @SysUISingleton + @CollapsedSbFragmentLog + public static LogBuffer provideCollapsedSbFragmentLogBuffer(LogBufferFactory factory) { + return factory.create("CollapsedSbFragmentLog", 20); + } + /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 2de40262f42d..c02cc8dda4c4 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -102,10 +102,7 @@ import java.util.function.Consumer; public class NavigationBarView extends FrameLayout implements NavigationModeController.ModeChangedListener { final static boolean DEBUG = false; - final static String TAG = "StatusBar/NavBarView"; - - // slippery nav bar when everything is disabled, e.g. during setup - final static boolean SLIPPERY_WHEN_DISABLED = true; + final static String TAG = "NavBarView"; final static boolean ALTERNATE_CAR_MODE_UI = false; private final RegionSamplingHelper mRegionSamplingHelper; @@ -385,6 +382,12 @@ public class NavigationBarView extends FrameLayout implements } } + @Override + protected boolean onSetAlpha(int alpha) { + Log.e(TAG, "onSetAlpha", new Throwable()); + return super.onSetAlpha(alpha); + } + public void setAutoHideController(AutoHideController autoHideController) { mAutoHideController = autoHideController; } @@ -1319,6 +1322,7 @@ public class NavigationBarView extends FrameLayout implements dumpButton(pw, "back", getBackButton()); dumpButton(pw, "home", getHomeButton()); + dumpButton(pw, "handle", getHomeHandle()); dumpButton(pw, "rcnt", getRecentsButton()); dumpButton(pw, "rota", getRotateSuggestionButton()); dumpButton(pw, "a11y", getAccessibilityButton()); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 3167070e2c3e..fc377c374f53 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -30,6 +30,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_I import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import android.app.StatusBarManager; import android.app.StatusBarManager.WindowVisibleState; @@ -46,6 +47,7 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.CommandQueue; import javax.inject.Inject; @@ -100,7 +102,8 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mDisplayId = displayId; mCommandQueue.addCallback(this); mOverviewProxyService.addCallback(this); - mNavigationModeController.addListener(this); + mEdgeBackGestureHandler.onNavigationModeChanged( + mNavigationModeController.addListener(this)); mNavigationBarA11yHelper.registerA11yEventListener(mNavA11yEventListener); // Set initial state for any listeners updateSysuiFlags(); @@ -126,6 +129,8 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, .setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isWindowVisible()) .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY, allowSystemGestureIgnoringBarVisibility()) + .setFlag(SYSUI_STATE_SCREEN_PINNING, + ActivityManagerWrapper.getInstance().isScreenPinningActive()) .commitUpdate(mDisplayId); } diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java index a0cf44c8bfdc..16b971f876bc 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java @@ -102,6 +102,7 @@ public abstract class PluginsModule { } @Provides + @Singleton static PluginManager providesPluginManager( Context context, PluginActionManager.Factory instanceManagerFactory, diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt index a626681c0b01..d0aa710ecea6 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt @@ -29,6 +29,7 @@ import android.util.Log import androidx.annotation.MainThread import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread +import com.android.internal.logging.UiEventLogger import com.android.systemui.appops.AppOpsController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -67,6 +68,7 @@ class PrivacyDialogController( private val privacyLogger: PrivacyLogger, private val keyguardStateController: KeyguardStateController, private val appOpsController: AppOpsController, + private val uiEventLogger: UiEventLogger, @VisibleForTesting private val dialogProvider: DialogProvider ) { @@ -81,7 +83,8 @@ class PrivacyDialogController( @Main uiExecutor: Executor, privacyLogger: PrivacyLogger, keyguardStateController: KeyguardStateController, - appOpsController: AppOpsController + appOpsController: AppOpsController, + uiEventLogger: UiEventLogger ) : this( permissionManager, packageManager, @@ -93,6 +96,7 @@ class PrivacyDialogController( privacyLogger, keyguardStateController, appOpsController, + uiEventLogger, defaultDialogProvider ) @@ -105,6 +109,7 @@ class PrivacyDialogController( private val onDialogDismissed = object : PrivacyDialog.OnDialogDismissed { override fun onDialogDismissed() { privacyLogger.logPrivacyDialogDismissed() + uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED) dialog = null } } @@ -114,6 +119,8 @@ class PrivacyDialogController( val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS) intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName) intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId)) + uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS, + userId, packageName) privacyLogger.logStartSettingsActivityFromDialog(packageName, userId) if (!keyguardStateController.isUnlocked) { // If we are locked, hide the dialog so the user can unlock @@ -155,7 +162,7 @@ class PrivacyDialogController( val items = usage.mapNotNull { val type = filterType(permGroupToPrivacyType(it.permGroupName)) val userInfo = userInfos.firstOrNull { ui -> ui.id == UserHandle.getUserId(it.uid) } - userInfo?.let { ui -> + if (userInfo != null || it.isPhoneCall) { type?.let { t -> // Only try to get the app name if we actually need it val appName = if (it.isPhoneCall) { @@ -171,10 +178,14 @@ class PrivacyDialogController( it.attribution, it.lastAccess, it.isActive, - ui.isManagedProfile, + // If there's no user info, we're in a phoneCall in secondary user + userInfo?.isManagedProfile ?: false, it.isPhoneCall ) } + } else { + // No matching user or phone call + null } } uiExecutor.execute { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt new file mode 100644 index 000000000000..3ecc5a5e5b00 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.privacy + +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger + +enum class PrivacyDialogEvent(private val _id: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "Privacy dialog is clicked by user to go to the app settings page.") + PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS(904), + + @UiEvent(doc = "Privacy dialog is dismissed by user.") + PRIVACY_DIALOG_DISMISSED(905); + + override fun getId() = _id +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt index fcfa72a82acb..f6c89a9c66a2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt @@ -36,7 +36,7 @@ class FooterActionsControllerBuilder @Inject constructor( private val activityStarter: ActivityStarter, private val userManager: UserManager, private val userInfoController: UserInfoController, - private val multiUserSwitchController: MultiUserSwitchController, + private val multiUserSwitchControllerFactory: MultiUserSwitchController.Factory, private val deviceProvisionedController: DeviceProvisionedController, private val falsingManager: FalsingManager, private val metricsLogger: MetricsLogger, @@ -60,8 +60,8 @@ class FooterActionsControllerBuilder @Inject constructor( fun build(): FooterActionsController { return FooterActionsController(view, qsPanelController, activityStarter, userManager, - userInfoController, multiUserSwitchController, deviceProvisionedController, - falsingManager, metricsLogger, tunerService, globalActionsDialog, uiEventLogger, - showPMLiteButton, buttonsVisibleState) + userInfoController, multiUserSwitchControllerFactory.create(view), + deviceProvisionedController, falsingManager, metricsLogger, tunerService, + globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 6e09f22fba63..90254270f3c6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -42,6 +42,7 @@ import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.brightness.BrightnessController; import com.android.systemui.settings.brightness.BrightnessMirrorHandler; import com.android.systemui.settings.brightness.BrightnessSlider; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.tuner.TunerService; @@ -60,6 +61,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { private final QSCustomizerController mQsCustomizerController; private final QSTileRevealController.Factory mQsTileRevealControllerFactory; private final FalsingManager mFalsingManager; + private final CommandQueue mCommandQueue; private final BrightnessController mBrightnessController; private final BrightnessSlider mBrightnessSlider; private final BrightnessMirrorHandler mBrightnessMirrorHandler; @@ -97,7 +99,8 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { QSTileRevealController.Factory qsTileRevealControllerFactory, DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory, - BrightnessSlider.Factory brightnessSliderFactory, FalsingManager falsingManager) { + BrightnessSlider.Factory brightnessSliderFactory, FalsingManager falsingManager, + CommandQueue commandQueue) { super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager); mQsSecurityFooter = qsSecurityFooter; @@ -105,6 +108,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mQsCustomizerController = qsCustomizerController; mQsTileRevealControllerFactory = qsTileRevealControllerFactory; mFalsingManager = falsingManager; + mCommandQueue = commandQueue; mQsSecurityFooter.setHostEnvironment(qstileHost); mBrightnessSlider = brightnessSliderFactory.create(getContext(), mView); @@ -274,6 +278,14 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { /** */ public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) { + // TODO(b/199296365) + // Workaround for opening detail from QQS, when there might not be enough space to + // display e.g. in case of multiuser detail from split shade. Currently showing detail works + // only for QS (mView below) and that's why expanding panel (thus showing QS instead of QQS) + // makes it displayed correctly. + if (!isExpanded()) { + mCommandQueue.animateExpandSettingsPanel(null); + } mView.showDetailAdapter(true, detailAdapter, new int[]{x, y}); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 9ceeb754f9b7..4e9b0f1e369d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -96,6 +96,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D private final UiEventLogger mUiEventLogger; private final InstanceIdSequence mInstanceIdSequence; private final CustomTileStatePersister mCustomTileStatePersister; + private final FeatureFlags mFeatureFlags; private final List<Callback> mCallbacks = new ArrayList<>(); private AutoTileManager mAutoTiles; @@ -123,7 +124,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D UiEventLogger uiEventLogger, UserTracker userTracker, SecureSettings secureSettings, - CustomTileStatePersister customTileStatePersister + CustomTileStatePersister customTileStatePersister, + FeatureFlags featureFlags ) { mIconController = iconController; mContext = context; @@ -145,6 +147,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mUserTracker = userTracker; mSecureSettings = secureSettings; mCustomTileStatePersister = customTileStatePersister; + mFeatureFlags = featureFlags; mainHandler.post(() -> { // This is technically a hack to avoid circular dependency of @@ -266,7 +269,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) { newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode); } - final List<String> tileSpecs = loadTileSpecs(mContext, newValue); + final List<String> tileSpecs = loadTileSpecs(mContext, newValue, mFeatureFlags); int currentUser = mUserTracker.getUserId(); if (currentUser != mCurrentUser) { mUserContext = mUserTracker.getUserContext(); @@ -335,7 +338,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D if (newTiles.isEmpty() && !tileSpecs.isEmpty()) { // If we didn't manage to create any tiles, set it to empty (default) Log.d(TAG, "No valid tiles on tuning changed. Setting to default."); - changeTiles(currentSpecs, loadTileSpecs(mContext, "")); + changeTiles(currentSpecs, loadTileSpecs(mContext, "", mFeatureFlags)); } else { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onTilesChanged(); @@ -403,7 +406,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D private void changeTileSpecs(Predicate<List<String>> changeFunction) { final String setting = mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser); - final List<String> tileSpecs = loadTileSpecs(mContext, setting); + final List<String> tileSpecs = loadTileSpecs(mContext, setting, mFeatureFlags); if (changeFunction.test(tileSpecs)) { saveTilesToSettings(tileSpecs); } @@ -492,7 +495,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec()); } - protected static List<String> loadTileSpecs(Context context, String tileList) { + protected static List<String> loadTileSpecs( + Context context, String tileList, FeatureFlags featureFlags) { final Resources res = context.getResources(); if (TextUtils.isEmpty(tileList)) { @@ -525,6 +529,21 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } } } + if (featureFlags.isProviderModelSettingEnabled()) { + if (!tiles.contains("internet")) { + if (tiles.contains("wifi")) { + // Replace the WiFi with Internet, and remove the Cell + tiles.set(tiles.indexOf("wifi"), "internet"); + tiles.remove("cell"); + } else if (tiles.contains("cell")) { + // Replace the Cell with Internet + tiles.set(tiles.indexOf("cell"), "internet"); + } + } else { + tiles.remove("wifi"); + tiles.remove("cell"); + } + } return tiles; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index 1c20a860a3cb..993bbd039b60 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -63,6 +63,7 @@ public class TileQueryHelper { private final Executor mBgExecutor; private final Context mContext; private final UserTracker mUserTracker; + private final FeatureFlags mFeatureFlags; private TileStateListener mListener; private boolean mFinished; @@ -72,12 +73,14 @@ public class TileQueryHelper { Context context, UserTracker userTracker, @Main Executor mainExecutor, - @Background Executor bgExecutor + @Background Executor bgExecutor, + FeatureFlags featureFlags ) { mContext = context; mMainExecutor = mainExecutor; mBgExecutor = bgExecutor; mUserTracker = userTracker; + mFeatureFlags = featureFlags; } public void setListener(TileStateListener listener) { @@ -118,6 +121,10 @@ public class TileQueryHelper { } final ArrayList<QSTile> tilesToAdd = new ArrayList<>(); + if (mFeatureFlags.isProviderModelSettingEnabled()) { + possibleTiles.remove("cell"); + possibleTiles.remove("wifi"); + } for (String spec : possibleTiles) { // Only add current and stock tiles that can be created from QSFactoryImpl. diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java index 386769cd399e..b11420afa529 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java @@ -39,7 +39,6 @@ import com.android.systemui.qs.QSPanel; import com.android.systemui.qs.QuickQSPanel; import com.android.systemui.qs.QuickStatusBarHeader; import com.android.systemui.qs.customize.QSCustomizer; -import com.android.systemui.statusbar.phone.MultiUserSwitch; import javax.inject.Named; @@ -82,12 +81,6 @@ public interface QSFragmentModule { /** */ @Provides - static MultiUserSwitch providesMultiUserSWitch(QSFooterView qsFooterView) { - return qsFooterView.findViewById(R.id.multi_user_switch); - } - - /** */ - @Provides static QSPanel provideQSPanel(@RootView View view) { return view.findViewById(R.id.quick_settings_panel); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 70f52ad5432d..50e7e43822ea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -58,9 +58,11 @@ import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.DeviceInfoUtils; +import com.android.settingslib.SignalIcon; import com.android.settingslib.Utils; import com.android.settingslib.graph.SignalDrawable; import com.android.settingslib.mobile.MobileMappings; +import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.SignalStrengthUtil; import com.android.settingslib.wifi.WifiUtils; import com.android.systemui.R; @@ -534,6 +536,14 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback, } int resId = mapIconSets(config).get(iconKey).dataContentDescription; + final MergedCarrierEntry mergedCarrierEntry = + mAccessPointController.getMergedCarrierEntry(); + if (mergedCarrierEntry != null && mergedCarrierEntry.isDefaultNetwork()) { + SignalIcon.MobileIconGroup carrierMergedWifiIconGroup = + TelephonyIcons.CARRIER_MERGED_WIFI; + resId = carrierMergedWifiIconGroup.dataContentDescription; + } + return resId != 0 ? SubscriptionManager.getResourcesForSubId(context, subId).getString(resId) : ""; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt new file mode 100644 index 000000000000..cf34db233b06 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/DisableFlagsLogger.kt @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar + +import android.app.StatusBarManager.DISABLE_BACK +import android.app.StatusBarManager.DISABLE_CLOCK +import android.app.StatusBarManager.DISABLE_EXPAND +import android.app.StatusBarManager.DISABLE_HOME +import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS +import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS +import android.app.StatusBarManager.DISABLE_ONGOING_CALL_CHIP +import android.app.StatusBarManager.DISABLE_RECENT +import android.app.StatusBarManager.DISABLE_SEARCH +import android.app.StatusBarManager.DISABLE_SYSTEM_INFO +import android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS +import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE +import android.app.StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS +import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS +import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** + * A singleton that creates concise but readable strings representing the values of the disable + * flags for debugging. + * + * See [CommandQueue.disable] for information about disable flags. + * + * Note that, for both lists passed in, each flag must have a distinct [DisableFlag.flagIsSetSymbol] + * and distinct [DisableFlag.flagNotSetSymbol] within the list. If this isn't true, the logs could + * be ambiguous so an [IllegalArgumentException] is thrown. + */ +@SysUISingleton +class DisableFlagsLogger constructor( + private val disable1FlagsList: List<DisableFlag>, + private val disable2FlagsList: List<DisableFlag> +) { + + @Inject + constructor() : this(defaultDisable1FlagsList, defaultDisable2FlagsList) + + init { + if (flagsListHasDuplicateSymbols(disable1FlagsList)) { + throw IllegalArgumentException("disable1 flags must have unique symbols") + } + if (flagsListHasDuplicateSymbols(disable2FlagsList)) { + throw IllegalArgumentException("disable2 flags must have unique symbols") + } + } + + private fun flagsListHasDuplicateSymbols(list: List<DisableFlag>): Boolean { + val numDistinctFlagOffStatus = list.map { it.getFlagStatus(0) }.distinct().count() + val numDistinctFlagOnStatus = list + .map { it.getFlagStatus(Int.MAX_VALUE) } + .distinct() + .count() + return numDistinctFlagOffStatus < list.count() || numDistinctFlagOnStatus < list.count() + } + + /** + * Returns a string representing the, old, new, and new-after-modification disable flag states, + * as well as the differences between each of the states. + * + * Example: + * Old: EnaiHbcRso.qINgr | New: EnaihBcRso.qiNGR (hB.iGR) | New after local modification: + * EnaihBcRso.qInGR (.n) + * + * A capital character signifies the flag is set and a lowercase character signifies that the + * flag isn't set. The flag states will be logged in the same order as the passed-in lists. + * + * The difference between states is written between parentheses, and won't be included if there + * is no difference. the new-after-modification state also won't be included if there's no + * difference from the new state. + * + * @param old the disable state that had been previously sent. + * @param new the new disable state that has just been sent. + * @param newAfterLocalModification the new disable states after a class has locally modified + * them. Null if the class does not locally modify. + */ + fun getDisableFlagsString( + old: DisableState, + new: DisableState, + newAfterLocalModification: DisableState? = null + ): String { + val builder = StringBuilder("Received new disable state. ") + builder.append("Old: ") + builder.append(getFlagsString(old)) + builder.append(" | New: ") + if (old != new) { + builder.append(getFlagsStringWithDiff(old, new)) + } else { + builder.append(getFlagsString(old)) + } + + if (newAfterLocalModification != null && new != newAfterLocalModification) { + builder.append(" | New after local modification: ") + builder.append(getFlagsStringWithDiff(new, newAfterLocalModification)) + } + + return builder.toString() + } + + /** + * Returns a string representing [new] state, as well as the difference from [old] to [new] + * (if there is one). + */ + private fun getFlagsStringWithDiff(old: DisableState, new: DisableState): String { + val builder = StringBuilder() + builder.append(getFlagsString(new)) + builder.append(" ") + builder.append(getDiffString(old, new)) + return builder.toString() + } + + /** + * Returns a string representing the difference between [old] and [new], or an empty string if + * there is no difference. + * + * For example, if old was "abc.DE" and new was "aBC.De", the difference returned would be + * "(BC.e)". + */ + private fun getDiffString(old: DisableState, new: DisableState): String { + if (old == new) { + return "" + } + + val builder = StringBuilder("(") + disable1FlagsList.forEach { + val newSymbol = it.getFlagStatus(new.disable1) + if (it.getFlagStatus(old.disable1) != newSymbol) { + builder.append(newSymbol) + } + } + builder.append(".") + disable2FlagsList.forEach { + val newSymbol = it.getFlagStatus(new.disable2) + if (it.getFlagStatus(old.disable2) != newSymbol) { + builder.append(newSymbol) + } + } + builder.append(")") + return builder.toString() + } + + /** Returns a string representing the disable flag states, e.g. "EnaihBcRso.qiNGR". */ + private fun getFlagsString(state: DisableState): String { + val builder = StringBuilder("") + disable1FlagsList.forEach { builder.append(it.getFlagStatus(state.disable1)) } + builder.append(".") + disable2FlagsList.forEach { builder.append(it.getFlagStatus(state.disable2)) } + return builder.toString() + } + + /** A POJO representing each disable flag. */ + class DisableFlag( + private val bitMask: Int, + private val flagIsSetSymbol: Char, + private val flagNotSetSymbol: Char + ) { + + /** + * Returns a character representing whether or not this flag is set in [state]. + * + * A capital character signifies the flag is set and a lowercase character signifies that + * the flag isn't set. + */ + internal fun getFlagStatus(state: Int): Char = + if (0 != state and this.bitMask) this.flagIsSetSymbol + else this.flagNotSetSymbol + } + + /** POJO to hold [disable1] and [disable2]. */ + data class DisableState(val disable1: Int, val disable2: Int) +} + +// LINT.IfChange +private val defaultDisable1FlagsList: List<DisableFlagsLogger.DisableFlag> = listOf( + DisableFlagsLogger.DisableFlag(DISABLE_EXPAND, 'E', 'e'), + DisableFlagsLogger.DisableFlag(DISABLE_NOTIFICATION_ICONS, 'N', 'n'), + DisableFlagsLogger.DisableFlag(DISABLE_NOTIFICATION_ALERTS, 'A', 'a'), + DisableFlagsLogger.DisableFlag(DISABLE_SYSTEM_INFO, 'I', 'i'), + DisableFlagsLogger.DisableFlag(DISABLE_HOME, 'H', 'h'), + DisableFlagsLogger.DisableFlag(DISABLE_BACK, 'B', 'b'), + DisableFlagsLogger.DisableFlag(DISABLE_CLOCK, 'C', 'c'), + DisableFlagsLogger.DisableFlag(DISABLE_RECENT, 'R', 'r'), + DisableFlagsLogger.DisableFlag(DISABLE_SEARCH, 'S', 's'), + DisableFlagsLogger.DisableFlag(DISABLE_ONGOING_CALL_CHIP, 'O', 'o') +) + +private val defaultDisable2FlagsList: List<DisableFlagsLogger.DisableFlag> = listOf( + DisableFlagsLogger.DisableFlag(DISABLE2_QUICK_SETTINGS, 'Q', 'q'), + DisableFlagsLogger.DisableFlag(DISABLE2_SYSTEM_ICONS, 'I', 'i'), + DisableFlagsLogger.DisableFlag(DISABLE2_NOTIFICATION_SHADE, 'N', 'n'), + DisableFlagsLogger.DisableFlag(DISABLE2_GLOBAL_ACTIONS, 'G', 'g'), + DisableFlagsLogger.DisableFlag(DISABLE2_ROTATE_SUGGESTIONS, 'R', 'r') +) +// LINT.ThenChange(frameworks/base/core/java/android/app/StatusBarManager.java)
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 445715e138ab..17bcfe73ccd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -66,6 +66,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.ViewClippingUtil; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; @@ -455,7 +456,8 @@ public class KeyguardIndicationController { new KeyguardIndication.Builder() .setMessage(mContext.getResources().getString( com.android.internal.R.string.global_action_logout)) - .setTextColor(mInitialTextColorState) + .setTextColor(Utils.getColorAttr( + mContext, com.android.internal.R.attr.textColorOnAccent)) .setBackground(mContext.getDrawable( com.android.systemui.R.drawable.logout_button_background)) .setClickListener((view) -> { @@ -798,7 +800,9 @@ public class KeyguardIndicationController { * Show message on the keyguard for how the user can unlock/enter their device. */ public void showActionToUnlock() { - if (mDozing) { + if (mDozing + && !mKeyguardUpdateMonitor.getUserCanSkipBouncer( + KeyguardUpdateMonitor.getCurrentUser())) { return; } @@ -809,7 +813,7 @@ public class KeyguardIndicationController { String message = mContext.getString(R.string.keyguard_retry); mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState); } - } else if (mKeyguardUpdateMonitor.isScreenOn()) { + } else { showTransientIndication(mContext.getString(R.string.keyguard_unlock), false /* isError */, true /* hideOnScreenOff */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 73c4b054fd4e..1530e5238c67 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -874,7 +874,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder mRow.getImageResolver().purgeCache(); } - private class RtlEnabledContext extends ContextWrapper { + private static class RtlEnabledContext extends ContextWrapper { private RtlEnabledContext(Context packageContext) { super(packageContext); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 6e201048abdb..2c76cfeff733 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -116,7 +116,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp /** * Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the - * device while being requested when keyguard is occluded. + * device while being requested when keyguard is occluded or showing. */ public static final int MODE_UNLOCK_COLLAPSING = 5; @@ -425,6 +425,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp if (!wasDeviceInteractive) { mPendingShowBouncer = true; } else { + mShadeController.animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_NONE, + true /* force */, + false /* delayed */, + BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR); mPendingShowBouncer = false; mKeyguardViewController.notifyKeyguardAuthenticated( false /* strongAuth */); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index 96c405866a68..c16cc125aece 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -42,6 +42,7 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.DisableFlagsLogger.DisableState; import com.android.systemui.statusbar.OperatorNameView; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.StatusBarState; @@ -93,6 +94,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private Lazy<Optional<StatusBar>> mStatusBarOptionalLazy; private DarkIconManager mDarkIconManager; private final CommandQueue mCommandQueue; + private final CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger; private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory; private final OngoingCallController mOngoingCallController; private final SystemStatusAnimationScheduler mAnimationScheduler; @@ -131,6 +133,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue NetworkController networkController, StatusBarStateController statusBarStateController, CommandQueue commandQueue, + CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, OperatorNameViewController.Factory operatorNameViewControllerFactory ) { mOngoingCallController = ongoingCallController; @@ -144,6 +147,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mNetworkController = networkController; mStatusBarStateController = statusBarStateController; mCommandQueue = commandQueue; + mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger; mOperatorNameViewControllerFactory = operatorNameViewControllerFactory; } @@ -244,7 +248,14 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue if (displayId != getContext().getDisplayId()) { return; } + + int state1BeforeAdjustment = state1; state1 = adjustDisableFlags(state1); + + mCollapsedStatusBarFragmentLogger.logDisableFlagChange( + new DisableState(state1BeforeAdjustment, state2), + new DisableState(state1, state2)); + final int old1 = mDisabled1; final int diff1 = state1 ^ old1; final int old2 = mDisabled2; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLogger.kt new file mode 100644 index 000000000000..3c2b555eea68 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLogger.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.CollapsedSbFragmentLog +import com.android.systemui.statusbar.DisableFlagsLogger +import javax.inject.Inject + +/** Used by [CollapsedStatusBarFragment] to log messages to a [LogBuffer]. */ +class CollapsedStatusBarFragmentLogger @Inject constructor( + @CollapsedSbFragmentLog private val buffer: LogBuffer, + private val disableFlagsLogger: DisableFlagsLogger, +) { + + /** Logs a string representing the old and new disable flag states to [buffer]. */ + fun logDisableFlagChange( + oldState: DisableFlagsLogger.DisableState, + newState: DisableFlagsLogger.DisableState) { + buffer.log( + TAG, + LogLevel.INFO, + { + int1 = oldState.disable1 + int2 = oldState.disable2 + long1 = newState.disable1.toLong() + long2 = newState.disable2.toLong() + }, + { + disableFlagsLogger.getDisableFlagsString( + DisableFlagsLogger.DisableState(int1, int2), + DisableFlagsLogger.DisableState(long1.toInt(), long2.toInt()) + ) + } + ) + } +} + +private const val TAG = "CollapsedSbFragment"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index ab7f9d3bbd25..a8c62fe2984c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -270,13 +270,6 @@ public class DozeParameters implements } /** - * Whether the brightness sensor uses the proximity sensor. - */ - public boolean brightnessUsesProx() { - return mResources.getBoolean(R.bool.doze_brightness_uses_prox); - } - - /** * Callback to listen for DozeParameter changes. */ public void addCallback(Callback callback) { @@ -300,6 +293,7 @@ public class DozeParameters implements @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.print("getAlwaysOn(): "); pw.println(getAlwaysOn()); pw.print("getDisplayStateSupported(): "); pw.println(getDisplayStateSupported()); pw.print("getPulseDuration(): "); pw.println(getPulseDuration()); pw.print("getPulseInDuration(): "); pw.println(getPulseInDuration()); @@ -312,7 +306,6 @@ public class DozeParameters implements pw.print("getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold()); pw.print("getSelectivelyRegisterSensorsUsingProx(): "); pw.println(getSelectivelyRegisterSensorsUsingProx()); - pw.print("brightnessUsesProx(): "); pw.println(brightnessUsesProx()); } interface Callback { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 8c0dfc5f7ab4..f1d5e0271b96 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -252,8 +252,6 @@ public class KeyguardBouncer { mKeyguardViewController.resetSecurityContainer(); showPromptReason(mBouncerPromptReason); } - SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, - SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index d348954e49bb..e368aad31ac8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -57,6 +57,21 @@ public class KeyguardClockPositionAlgorithm { private int mUserSwitchPreferredY; /** + * Whether or not there is a custom clock face on keyguard. + */ + private boolean mHasCustomClock; + + /** + * Whether or not the NSSL contains any visible notifications. + */ + private boolean mHasVisibleNotifs; + + /** + * Height of notification stack: Sum of height of each notification. + */ + private int mNotificationStackHeight; + + /** * Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher * avatar. */ @@ -113,6 +128,25 @@ public class KeyguardClockPositionAlgorithm { private boolean mIsSplitShade; /** + * Top location of the udfps icon. This includes the worst case (highest) burn-in + * offset that would make the top physically highest on the screen. + * + * Set to -1 if udfps is not enrolled on the device. + */ + private float mUdfpsTop; + + /** + * Bottom y-position of the currently visible clock + */ + private float mClockBottom; + + /** + * If true, try to keep clock aligned to the top of the display. Else, assume the clock + * is center aligned. + */ + private boolean mIsClockTopAligned; + + /** * Refreshes the dimension values. */ public void loadDimens(Resources res) { @@ -120,7 +154,7 @@ public class KeyguardClockPositionAlgorithm { R.dimen.keyguard_status_view_bottom_margin); mContainerTopPadding = - res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) / 2; + res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); mBurnInPreventionOffsetX = res.getDimensionPixelSize( R.dimen.burn_in_prevention_offset_x); mBurnInPreventionOffsetYClock = res.getDimensionPixelSize( @@ -134,7 +168,7 @@ public class KeyguardClockPositionAlgorithm { int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY, float dark, float overStretchAmount, boolean bypassEnabled, int unlockedStackScrollerPadding, float qsExpansion, int cutoutTopInset, - boolean isSplitShade) { + boolean isSplitShade, float udfpsTop, float clockBottom, boolean isClockTopAligned) { mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(mContainerTopPadding, userSwitchHeight); mPanelExpansion = panelExpansion; @@ -148,6 +182,9 @@ public class KeyguardClockPositionAlgorithm { mQsExpansion = qsExpansion; mCutoutTopInset = cutoutTopInset; mIsSplitShade = isSplitShade; + mUdfpsTop = udfpsTop; + mClockBottom = clockBottom; + mIsClockTopAligned = isClockTopAligned; } public void run(Result result) { @@ -202,8 +239,34 @@ public class KeyguardClockPositionAlgorithm { if (clockY - mBurnInPreventionOffsetYClock < mCutoutTopInset) { shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYClock); } - float clockYDark = clockY + burnInPreventionOffsetY() + shift; + int burnInPreventionOffsetY = mBurnInPreventionOffsetYClock; // requested offset + final boolean hasUdfps = mUdfpsTop > -1; + if (hasUdfps && !mIsClockTopAligned) { + // ensure clock doesn't overlap with the udfps icon + if (mUdfpsTop < mClockBottom) { + // sometimes the clock textView extends beyond udfps, so let's just use the + // space above the KeyguardStatusView/clock as our burn-in offset + burnInPreventionOffsetY = (int) (clockY - mCutoutTopInset) / 2; + if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) { + burnInPreventionOffsetY = mBurnInPreventionOffsetYClock; + } + shift = -burnInPreventionOffsetY; + } else { + float upperSpace = clockY - mCutoutTopInset; + float lowerSpace = mUdfpsTop - mClockBottom; + // center the burn-in offset within the upper + lower space + burnInPreventionOffsetY = (int) (lowerSpace + upperSpace) / 2; + if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) { + burnInPreventionOffsetY = mBurnInPreventionOffsetYClock; + } + shift = (lowerSpace - upperSpace) / 2; + } + } + + float clockYDark = clockY + + burnInPreventionOffsetY(burnInPreventionOffsetY) + + shift; return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount); } @@ -235,9 +298,7 @@ public class KeyguardClockPositionAlgorithm { return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount); } - private float burnInPreventionOffsetY() { - int offset = mBurnInPreventionOffsetYClock; - + private float burnInPreventionOffsetY(int offset) { return getBurnInOffset(offset * 2, false /* xAxis */) - offset; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java index f1cde8a9be7a..a5b5f1cbf1e7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java @@ -28,7 +28,10 @@ import android.util.AttributeSet; import android.view.View; import android.widget.TextView; +import androidx.annotation.StyleRes; + import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.keyguard.KeyguardIndication; @@ -39,6 +42,12 @@ import java.util.LinkedList; */ public class KeyguardIndicationTextView extends TextView { private static final long MSG_MIN_DURATION_MILLIS_DEFAULT = 1500; + + @StyleRes + private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea; + @StyleRes + private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button; + private long mNextAnimationTime = 0; private boolean mAnimationsEnabled = true; private LinkedList<CharSequence> mMessages = new LinkedList<>(); @@ -136,6 +145,14 @@ public class KeyguardIndicationTextView extends TextView { public void onAnimationEnd(Animator animator) { KeyguardIndication info = mKeyguardIndicationInfo.poll(); if (info != null) { + // First, update the style. + // If a background is set on the text, we don't want shadow on the text + if (info.getBackground() != null) { + setTextAppearance(sButtonStyleId); + } else { + setTextAppearance(sStyleId); + } + setBackground(info.getBackground()); setTextColor(info.getTextColor()); setOnClickListener(info.getClickListener()); setClickable(info.getClickListener() != null); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index 51eb496c3c2a..abee7a51f91f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -225,19 +225,19 @@ public class LightBarController implements BatteryController.BatteryStateChangeC } } + // If no one is light, all icons become white. + if (numLightStacks == 0) { + mStatusBarIconController.getTransitionsController().setIconsDark( + false, animateChange()); + } + // If all stacks are light, all icons get dark. - if (numLightStacks == numStacks) { + else if (numLightStacks == numStacks) { mStatusBarIconController.setIconsDarkArea(null); mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange()); } - // If no one is light, all icons become white. - else if (numLightStacks == 0) { - mStatusBarIconController.getTransitionsController().setIconsDark( - false, animateChange()); - } - // Not the same for every stack, magic! else { mStatusBarIconController.setIconsDarkArea( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java index f27c7d28df44..3fdf1ceaa2d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java @@ -25,6 +25,7 @@ import android.view.ViewGroup; import com.android.systemui.R; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.DetailAdapter; +import com.android.systemui.qs.FooterActionsView; import com.android.systemui.qs.QSDetailDisplayer; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.statusbar.policy.UserSwitcherController; @@ -33,7 +34,6 @@ import com.android.systemui.util.ViewController; import javax.inject.Inject; /** View Controller for {@link MultiUserSwitch}. */ -@QSScope public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { private final UserManager mUserManager; private final UserSwitcherController mUserSwitcherController; @@ -60,8 +60,30 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { } }; - @Inject - public MultiUserSwitchController(MultiUserSwitch view, UserManager userManager, + @QSScope + public static class Factory { + private final UserManager mUserManager; + private final UserSwitcherController mUserSwitcherController; + private final QSDetailDisplayer mQsDetailDisplayer; + private final FalsingManager mFalsingManager; + + @Inject + public Factory(UserManager userManager, UserSwitcherController userSwitcherController, + QSDetailDisplayer qsDetailDisplayer, FalsingManager falsingManager) { + mUserManager = userManager; + mUserSwitcherController = userSwitcherController; + mQsDetailDisplayer = qsDetailDisplayer; + mFalsingManager = falsingManager; + } + + public MultiUserSwitchController create(FooterActionsView view) { + return new MultiUserSwitchController(view.findViewById(R.id.multi_user_switch), + mUserManager, mUserSwitcherController, mQsDetailDisplayer, + mFalsingManager); + } + } + + private MultiUserSwitchController(MultiUserSwitch view, UserManager userManager, UserSwitcherController userSwitcherController, QSDetailDisplayer qsDetailDisplayer, FalsingManager falsingManager) { super(view); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index cae85ce10358..b21088a7eb3a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -57,6 +57,9 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; +import android.hardware.biometrics.BiometricSourceType; +import android.hardware.biometrics.SensorLocationInternal; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; @@ -568,6 +571,8 @@ public class NotificationPanelViewController extends PanelViewController { */ private float mKeyguardOnlyContentAlpha = 1.0f; + private float mUdfpsMaxYBurnInOffset; + /** * Are we currently in gesture navigation */ @@ -798,13 +803,13 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header); mBigClockContainer = mView.findViewById(R.id.big_clock_container); - UserAvatarView userAvatarView = null; + FrameLayout userAvatarContainer = null; KeyguardUserSwitcherView keyguardUserSwitcherView = null; if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) { if (mKeyguardQsUserSwitchEnabled) { ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub); - userAvatarView = (UserAvatarView) stub.inflate(); + userAvatarContainer = (FrameLayout) stub.inflate(); } else { ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub); keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate(); @@ -820,7 +825,7 @@ public class NotificationPanelViewController extends PanelViewController { updateViewControllers( mView.findViewById(R.id.keyguard_status_view), - userAvatarView, + userAvatarContainer, keyguardUserSwitcherView); mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent); NotificationStackScrollLayout stackScrollLayout = mView.findViewById( @@ -907,10 +912,11 @@ public class NotificationPanelViewController extends PanelViewController { mView.getContext()); mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize( R.dimen.notification_side_paddings); + mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); } private void updateViewControllers(KeyguardStatusView keyguardStatusView, - UserAvatarView userAvatarView, + FrameLayout userAvatarView, KeyguardUserSwitcherView keyguardUserSwitcherView) { // Re-associate the KeyguardStatusViewController KeyguardStatusViewComponent statusViewComponent = @@ -1066,7 +1072,7 @@ public class NotificationPanelViewController extends PanelViewController { !mKeyguardQsUserSwitchEnabled && mKeyguardUserSwitcherEnabled && isUserSwitcherEnabled; - UserAvatarView userAvatarView = (UserAvatarView) reInflateStub( + FrameLayout userAvatarView = (FrameLayout) reInflateStub( R.id.keyguard_qs_user_switch_view /* viewId */, R.id.keyguard_qs_user_switch_stub /* stubId */, R.layout.keyguard_qs_user_switch /* layoutId */, @@ -1265,7 +1271,17 @@ public class NotificationPanelViewController extends PanelViewController { float darkamount = mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() ? 1.0f : mInterpolatedDarkAmount; - mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard, + + float udfpsAodTopLocation = -1f; + if (mUpdateMonitor.isUdfpsEnrolled() && mAuthController.getUdfpsProps().size() > 0) { + FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0); + final SensorLocationInternal location = props.getLocation(); + udfpsAodTopLocation = location.sensorLocationY - location.sensorRadius + - mUdfpsMaxYBurnInOffset; + } + + mClockPositionAlgorithm.setup( + mStatusBarHeaderHeightKeyguard, expandedFraction, mKeyguardStatusViewController.getLockscreenHeight(), userIconHeight, @@ -1274,7 +1290,10 @@ public class NotificationPanelViewController extends PanelViewController { bypassEnabled, getUnlockedStackScrollerPadding(), computeQsExpansionFraction(), mDisplayTopInset, - mShouldUseSplitNotificationShade); + mShouldUseSplitNotificationShade, + udfpsAodTopLocation, + mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard), + mKeyguardStatusViewController.isClockTopAligned()); mClockPositionAlgorithm.run(mClockPositionResult); boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); boolean animateClock = animate || mAnimateNextPositionUpdate; @@ -3546,6 +3565,7 @@ public class NotificationPanelViewController extends PanelViewController { } public void dozeTimeTick() { + mLockIconViewController.dozeTimeTick(); mKeyguardBottomArea.dozeTimeTick(); mKeyguardStatusViewController.dozeTimeTick(); if (mInterpolatedDarkAmount > 0) { @@ -3851,10 +3871,6 @@ public class NotificationPanelViewController extends PanelViewController { mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX()); } - if (mLockIconViewController.onTouchEvent(event)) { - return true; - } - handled |= super.onTouch(v, event); return !mDozing || mPulsing || handled; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index 147ebfb4ab99..03d0bb02e1bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -35,6 +35,7 @@ import android.view.View; import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; +import com.android.keyguard.LockIconViewController; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dock.DockManager; @@ -87,6 +88,7 @@ public class NotificationShadeWindowViewController { private final NotificationShadeDepthController mDepthController; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; + private final LockIconViewController mLockIconViewController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private GestureDetector mGestureDetector; @@ -137,7 +139,8 @@ public class NotificationShadeWindowViewController { NotificationPanelViewController notificationPanelViewController, StatusBarWindowView statusBarWindowView, NotificationStackScrollLayoutController notificationStackScrollLayoutController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager) { + StatusBarKeyguardViewManager statusBarKeyguardViewManager, + LockIconViewController lockIconViewController) { mInjectionInflationController = injectionInflationController; mCoordinator = coordinator; mPulseExpansionHandler = pulseExpansionHandler; @@ -162,6 +165,7 @@ public class NotificationShadeWindowViewController { mStatusBarWindowView = statusBarWindowView; mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mLockIconViewController = lockIconViewController; // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); @@ -234,6 +238,7 @@ public class NotificationShadeWindowViewController { if (!isCancel && mService.shouldIgnoreTouch()) { return false; } + if (isDown) { setTouchActive(true); mTouchCancelled = false; @@ -244,6 +249,7 @@ public class NotificationShadeWindowViewController { if (mTouchCancelled || mExpandAnimationRunning) { return false; } + mFalsingCollector.onTouchEvent(ev); mGestureDetector.onTouchEvent(ev); mStatusBarKeyguardViewManager.onTouch(ev); @@ -259,9 +265,17 @@ public class NotificationShadeWindowViewController { if (isDown) { mNotificationStackScrollLayoutController.closeControlsIfOutsideTouch(ev); } + if (mStatusBarStateController.isDozing()) { mService.mDozeScrimController.extendPulse(); } + mLockIconViewController.onTouchEvent( + ev, + () -> mService.wakeUpIfDozing( + SystemClock.uptimeMillis(), + mView, + "LOCK_ICON_TOUCH")); + // In case we start outside of the view bounds (below the status bar), we need to // dispatch // the touch manually as the view system can't accommodate for touches outside of diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index a09b30f41f49..af556a26e3af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -79,6 +79,8 @@ public class PhoneStatusBarView extends PanelBar { private int mStatusBarHeight; @Nullable private List<StatusBar.ExpansionChangedListener> mExpansionChangedListeners; + @Nullable + private PanelExpansionStateChangedListener mPanelExpansionStateChangedListener; private PanelEnabledProvider mPanelEnabledProvider; @@ -102,6 +104,10 @@ public class PhoneStatusBarView extends PanelBar { mExpansionChangedListeners = listeners; } + void setPanelExpansionStateChangedListener(PanelExpansionStateChangedListener listener) { + mPanelExpansionStateChangedListener = listener; + } + public void setScrimController(ScrimController scrimController) { mScrimController = scrimController; } @@ -289,11 +295,10 @@ public class PhoneStatusBarView extends PanelBar { super.panelExpansionChanged(frac, expanded); updateScrimFraction(); if ((frac == 0 || frac == 1)) { - if (mBar.getNavigationBarView() != null) { - mBar.getNavigationBarView().onStatusBarPanelStateChanged(); - } - if (mBar.getNotificationPanelViewController() != null) { - mBar.getNotificationPanelViewController().updateSystemUiStateFlags(); + if (mPanelExpansionStateChangedListener != null) { + mPanelExpansionStateChangedListener.onPanelExpansionStateChanged(); + } else { + Log.w(TAG, "No PanelExpansionStateChangedListener provided."); } } @@ -412,4 +417,10 @@ public class PhoneStatusBarView extends PanelBar { /** Returns true if the panel is enabled and false otherwise. */ boolean panelEnabled(); } + + /** A listener that will be notified when a panel's expansion state may have changed. */ + public interface PanelExpansionStateChangedListener { + /** Called when a panel's expansion state may have changed. */ + void onPanelExpansionStateChanged(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt new file mode 100644 index 000000000000..28040fd8d8a1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.phone + +import android.graphics.Point +import android.view.View +import android.view.ViewGroup +import com.android.systemui.R +import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.ViewController + +/** Controller for [PhoneStatusBarView]. */ +class PhoneStatusBarViewController( + view: PhoneStatusBarView, + commandQueue: CommandQueue, + statusBarMoveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?, + panelExpansionStateChangedListener: PhoneStatusBarView.PanelExpansionStateChangedListener, +) : ViewController<PhoneStatusBarView>(view) { + + override fun onViewAttached() {} + override fun onViewDetached() {} + + init { + mView.setPanelEnabledProvider { + commandQueue.panelsEnabled() + } + mView.setPanelExpansionStateChangedListener(panelExpansionStateChangedListener) + + statusBarMoveFromCenterAnimationController?.let { animationController -> + val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side) + val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area) + + val viewCenterProvider = StatusBarViewsCenterProvider() + val viewsToAnimate = arrayOf( + statusBarLeftSide, + systemIconArea + ) + + animationController.init(viewsToAnimate, viewCenterProvider) + + mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ -> + val widthChanged = right - left != oldRight - oldLeft + if (widthChanged) { + statusBarMoveFromCenterAnimationController.onStatusBarWidthChanged() + } + } + } + } + + private class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider { + override fun getViewCenter(view: View, outPoint: Point) = + when (view.id) { + R.id.status_bar_left_side -> { + // items aligned to the start, return start center point + getViewEdgeCenter(view, outPoint, isStart = true) + } + R.id.system_icon_area -> { + // items aligned to the end, return end center point + getViewEdgeCenter(view, outPoint, isStart = false) + } + else -> super.getViewCenter(view, outPoint) + } + + /** + * Returns start or end (based on [isStart]) center point of the view + */ + private fun getViewEdgeCenter(view: View, outPoint: Point, isStart: Boolean) { + val isRtl = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL + val isLeftEdge = isRtl xor isStart + + val viewLocation = IntArray(2) + view.getLocationOnScreen(viewLocation) + + val viewX = viewLocation[0] + val viewY = viewLocation[1] + + outPoint.x = viewX + if (isLeftEdge) view.height / 2 else view.width - view.height / 2 + outPoint.y = viewY + view.height / 2 + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 685b0625b9a2..6b96f3c23a2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -454,6 +454,7 @@ public class StatusBar extends SystemUI implements @Nullable protected LockscreenWallpaper mLockscreenWallpaper; private final AutoHideController mAutoHideController; + private final CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger; private final Point mCurrentDisplaySize = new Point(); @@ -536,6 +537,7 @@ public class StatusBar extends SystemUI implements private final FeatureFlags mFeatureFlags; private final UnfoldTransitionConfig mUnfoldTransitionConfig; private final Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimation; + private final Lazy<StatusBarMoveFromCenterAnimationController> mMoveFromCenterAnimation; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final MessageRouter mMessageRouter; private final WallpaperManager mWallpaperManager; @@ -742,6 +744,7 @@ public class StatusBar extends SystemUI implements DozeScrimController dozeScrimController, VolumeComponent volumeComponent, CommandQueue commandQueue, + CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, StatusBarComponent.Factory statusBarComponentFactory, PluginManager pluginManager, Optional<LegacySplitScreen> splitScreenOptional, @@ -768,6 +771,7 @@ public class StatusBar extends SystemUI implements BrightnessSlider.Factory brightnessSliderFactory, UnfoldTransitionConfig unfoldTransitionConfig, Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation, + Lazy<StatusBarMoveFromCenterAnimationController> statusBarUnfoldAnimationController, OngoingCallController ongoingCallController, SystemStatusAnimationScheduler animationScheduler, StatusBarLocationPublisher locationPublisher, @@ -840,6 +844,7 @@ public class StatusBar extends SystemUI implements mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy; mVolumeComponent = volumeComponent; mCommandQueue = commandQueue; + mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger; mStatusBarComponentFactory = statusBarComponentFactory; mPluginManager = pluginManager; mSplitScreenOptional = splitScreenOptional; @@ -860,6 +865,7 @@ public class StatusBar extends SystemUI implements mBrightnessSliderFactory = brightnessSliderFactory; mUnfoldTransitionConfig = unfoldTransitionConfig; mUnfoldLightRevealOverlayAnimation = unfoldLightRevealOverlayAnimation; + mMoveFromCenterAnimation = statusBarUnfoldAnimationController; mOngoingCallController = ongoingCallController; mAnimationScheduler = animationScheduler; mStatusBarLocationPublisher = locationPublisher; @@ -1141,8 +1147,16 @@ public class StatusBar extends SystemUI implements sendInitialExpansionAmount(listener); } + StatusBarMoveFromCenterAnimationController moveFromCenterAnimation = null; + if (mUnfoldTransitionConfig.isEnabled()) { + moveFromCenterAnimation = mMoveFromCenterAnimation.get(); + } mPhoneStatusBarViewController = - new PhoneStatusBarViewController(mStatusBarView, mCommandQueue); + new PhoneStatusBarViewController( + mStatusBarView, + mCommandQueue, + moveFromCenterAnimation, + this::onPanelExpansionStateChanged); mPhoneStatusBarViewController.init(); mBatteryMeterViewController = new BatteryMeterViewController( @@ -1207,6 +1221,7 @@ public class StatusBar extends SystemUI implements mNetworkController, mStatusBarStateController, mCommandQueue, + mCollapsedStatusBarFragmentLogger, mOperatorNameViewControllerFactory ), CollapsedStatusBarFragment.TAG) @@ -1412,6 +1427,15 @@ public class StatusBar extends SystemUI implements } } + private void onPanelExpansionStateChanged() { + if (getNavigationBarView() != null) { + getNavigationBarView().onStatusBarPanelStateChanged(); + } + if (getNotificationPanelViewController() != null) { + getNotificationPanelViewController().updateSystemUiStateFlags(); + } + } + @NonNull @Override public Lifecycle getLifecycle() { @@ -3388,8 +3412,6 @@ public class StatusBar extends SystemUI implements mNotificationPanelViewController.collapseWithDuration(duration); } - - /** * Updates the light reveal effect to reflect the reason we're waking or sleeping (for example, * from the power button). @@ -4280,6 +4302,7 @@ public class StatusBar extends SystemUI implements mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode); mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode); + mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode); } }; @@ -4387,7 +4410,8 @@ public class StatusBar extends SystemUI implements @Override public void onDozeAmountChanged(float linear, float eased) { if (mFeatureFlags.useNewLockscreenAnimations() - && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { + && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal) + && !mBiometricUnlockController.isWakeAndUnlock()) { mLightRevealScrim.setRevealAmount(1f - linear); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java index 6a510c92a3a0..5301b2571534 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java @@ -57,6 +57,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.qs.QSPanelController; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.DisableFlagsLogger; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; @@ -98,6 +99,7 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { private final VibratorHelper mVibratorHelper; private final Optional<Vibrator> mVibratorOptional; private final LightBarController mLightBarController; + private final DisableFlagsLogger mDisableFlagsLogger; private final int mDisplayId; private final boolean mVibrateOnOpening; private final VibrationEffect mCameraLaunchGestureVibrationEffect; @@ -134,6 +136,7 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { VibratorHelper vibratorHelper, Optional<Vibrator> vibratorOptional, LightBarController lightBarController, + DisableFlagsLogger disableFlagsLogger, @DisplayId int displayId) { mStatusBar = statusBar; @@ -159,6 +162,7 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { mVibratorHelper = vibratorHelper; mVibratorOptional = vibratorOptional; mLightBarController = lightBarController; + mDisableFlagsLogger = disableFlagsLogger; mDisplayId = displayId; mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation); @@ -267,7 +271,17 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { if (displayId != mDisplayId) { return; } + + int state2BeforeAdjustment = state2; state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2); + Log.d(StatusBar.TAG, + mDisableFlagsLogger.getDisableFlagsString( + /* old= */ new DisableFlagsLogger.DisableState( + mStatusBar.getDisabled1(), mStatusBar.getDisabled2()), + /* new= */ new DisableFlagsLogger.DisableState( + state1, state2BeforeAdjustment), + /* newStateAfterLocalModification= */ new DisableFlagsLogger.DisableState( + state1, state2))); final int old1 = mStatusBar.getDisabled1(); final int diff1 = state1 ^ old1; @@ -277,43 +291,6 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { final int diff2 = state2 ^ old2; mStatusBar.setDisabled2(state2); - if (StatusBar.DEBUG) { - Log.d(StatusBar.TAG, String.format("disable1: 0x%08x -> 0x%08x (diff1: 0x%08x)", - old1, state1, diff1)); - Log.d(StatusBar.TAG, String.format("disable2: 0x%08x -> 0x%08x (diff2: 0x%08x)", - old2, state2, diff2)); - } - - StringBuilder flagdbg = new StringBuilder(); - flagdbg.append("disable<"); - flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_EXPAND)) ? 'E' : 'e'); - flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_EXPAND)) ? '!' : ' '); - flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) ? 'I' : 'i'); - flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) ? '!' : ' '); - flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS)) ? 'A' : 'a'); - flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_NOTIFICATION_ALERTS)) ? '!' : ' '); - flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_SYSTEM_INFO)) ? 'S' : 's'); - flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_SYSTEM_INFO)) ? '!' : ' '); - flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_BACK)) ? 'B' : 'b'); - flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_BACK)) ? '!' : ' '); - flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_HOME)) ? 'H' : 'h'); - flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_HOME)) ? '!' : ' '); - flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_RECENT)) ? 'R' : 'r'); - flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_RECENT)) ? '!' : ' '); - flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_CLOCK)) ? 'C' : 'c'); - flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_CLOCK)) ? '!' : ' '); - flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_SEARCH)) ? 'S' : 's'); - flagdbg.append(0 != ((diff1 & StatusBarManager.DISABLE_SEARCH)) ? '!' : ' '); - flagdbg.append("> disable2<"); - flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_QUICK_SETTINGS)) ? 'Q' : 'q'); - flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS)) ? '!' : ' '); - flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_SYSTEM_ICONS)) ? 'I' : 'i'); - flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_SYSTEM_ICONS)) ? '!' : ' '); - flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE)) ? 'N' : 'n'); - flagdbg.append(0 != ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE)) ? '!' : ' '); - flagdbg.append('>'); - Log.d(StatusBar.TAG, flagdbg.toString()); - if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) { if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) { mShadeController.animateCollapsePanels(); 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 4316ccfbb620..77254435b688 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -197,6 +197,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean mLastGesturalNav; private boolean mLastIsDocked; private boolean mLastPulsing; + private boolean mLastAnimatedToSleep; private int mLastBiometricMode; private boolean mQsExpanded; private boolean mAnimatedToSleep; @@ -1012,6 +1013,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mLastBiometricMode = mBiometricUnlockController.getMode(); mLastGesturalNav = mGesturalNav; mLastIsDocked = mIsDocked; + mLastAnimatedToSleep = mAnimatedToSleep; mStatusBar.onKeyguardViewManagerStatesUpdated(); } @@ -1055,7 +1057,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb boolean hideWhileDozing = mLastDozing && mLastBiometricMode != MODE_WAKE_AND_UNLOCK_PULSING; boolean keyguardWithGestureNav = (keyguardShowing && !mLastDozing || mLastPulsing && !mLastIsDocked) && mLastGesturalNav; - return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing + return (!mLastAnimatedToSleep && !keyguardShowing && !hideWhileDozing || mLastBouncerShowing || mLastRemoteInputActive || keyguardWithGestureNav || mLastGlobalActionsVisible); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt new file mode 100644 index 000000000000..8af03aa2a3be --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.phone + +import android.view.View +import android.view.WindowManager +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator +import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.ViewCenterProvider +import com.android.systemui.unfold.UnfoldTransitionProgressProvider +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import javax.inject.Inject + +@SysUISingleton +class StatusBarMoveFromCenterAnimationController @Inject constructor( + private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, + private val windowManager: WindowManager +) { + + private lateinit var moveFromCenterAnimator: UnfoldMoveFromCenterAnimator + + fun init(viewsToAnimate: Array<View>, viewCenterProvider: ViewCenterProvider) { + moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager, + viewCenterProvider = viewCenterProvider) + + unfoldTransitionProgressProvider.addCallback(object : TransitionProgressListener { + override fun onTransitionStarted() { + moveFromCenterAnimator.updateDisplayProperties() + + viewsToAnimate.forEach { + moveFromCenterAnimator.registerViewForAnimation(it) + } + } + + override fun onTransitionFinished() { + moveFromCenterAnimator.onTransitionFinished() + moveFromCenterAnimator.clearRegisteredViews() + } + + override fun onTransitionProgress(progress: Float) { + moveFromCenterAnimator.onTransitionProgress(progress) + } + }) + } + + fun onStatusBarWidthChanged() { + moveFromCenterAnimator.updateViewPositions() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index f8120a8a7029..143aaba648da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -8,13 +8,13 @@ import android.content.res.Configuration import android.database.ContentObserver import android.os.Handler import android.provider.Settings -import com.android.systemui.statusbar.StatusBarState import android.view.View import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.statusbar.LightRevealScrim +import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.StatusBarStateControllerImpl import com.android.systemui.statusbar.notification.AnimatableProperty import com.android.systemui.statusbar.notification.PropertyAnimator @@ -61,6 +61,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( private var shouldAnimateInKeyguard = false private var lightRevealAnimationPlaying = false private var aodUiAnimationPlaying = false + private var callbacks = HashSet<Callback>() /** * The result of our decision whether to play the screen off animation in @@ -72,11 +73,17 @@ class UnlockedScreenOffAnimationController @Inject constructor( private val lightRevealAnimator = ValueAnimator.ofFloat(1f, 0f).apply { duration = LIGHT_REVEAL_ANIMATION_DURATION interpolator = Interpolators.LINEAR - addUpdateListener { lightRevealScrim.revealAmount = it.animatedValue as Float } + addUpdateListener { + lightRevealScrim.revealAmount = it.animatedValue as Float + sendUnlockedScreenOffProgressUpdate( + 1f - (it.animatedFraction as Float), + 1f - (it.animatedValue as Float)) + } addListener(object : AnimatorListenerAdapter() { override fun onAnimationCancel(animation: Animator?) { lightRevealScrim.revealAmount = 1f lightRevealAnimationPlaying = false + sendUnlockedScreenOffProgressUpdate(0f, 0f) } override fun onAnimationEnd(animation: Animator?) { @@ -243,7 +250,21 @@ class UnlockedScreenOffAnimationController @Inject constructor( return true } - /** + fun addCallback(callback: Callback) { + callbacks.add(callback) + } + + fun removeCallback(callback: Callback) { + callbacks.remove(callback) + } + + fun sendUnlockedScreenOffProgressUpdate(linear: Float, eased: Float) { + callbacks.forEach { + it.onUnlockedScreenOffProgressUpdate(linear, eased) + } + } + +/** * Whether we're doing the light reveal animation or we're done with that and animating in the * AOD UI. */ @@ -262,4 +283,8 @@ class UnlockedScreenOffAnimationController @Inject constructor( fun isScreenOffLightRevealAnimationPlaying(): Boolean { return lightRevealAnimationPlaying } + + interface Callback { + fun onUnlockedScreenOffProgressUpdate(linear: Float, eased: Float) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index 63ee701425ed..c45068e0171b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -72,6 +72,7 @@ import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BiometricUnlockController; +import com.android.systemui.statusbar.phone.CollapsedStatusBarFragmentLogger; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.DozeScrimController; import com.android.systemui.statusbar.phone.DozeServiceHost; @@ -90,6 +91,7 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; +import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController; import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.StatusBarWindowView; @@ -187,6 +189,7 @@ public interface StatusBarPhoneModule { DozeScrimController dozeScrimController, VolumeComponent volumeComponent, CommandQueue commandQueue, + CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, StatusBarComponent.Factory statusBarComponentFactory, PluginManager pluginManager, Optional<LegacySplitScreen> splitScreenOptional, @@ -213,6 +216,7 @@ public interface StatusBarPhoneModule { BrightnessSlider.Factory brightnessSliderFactory, UnfoldTransitionConfig unfoldTransitionConfig, Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation, + Lazy<StatusBarMoveFromCenterAnimationController> statusBarMoveFromCenterAnimation, OngoingCallController ongoingCallController, SystemStatusAnimationScheduler animationScheduler, StatusBarLocationPublisher locationPublisher, @@ -282,6 +286,7 @@ public interface StatusBarPhoneModule { dozeScrimController, volumeComponent, commandQueue, + collapsedStatusBarFragmentLogger, statusBarComponentFactory, pluginManager, splitScreenOptional, @@ -307,6 +312,7 @@ public interface StatusBarPhoneModule { brightnessSliderFactory, unfoldTransitionConfig, unfoldLightRevealOverlayAnimation, + statusBarMoveFromCenterAnimation, ongoingCallController, animationScheduler, locationPublisher, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 5e70d0dbc418..d838a05135e7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -27,6 +27,7 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.FrameLayout; import com.android.keyguard.KeyguardConstants; import com.android.keyguard.KeyguardVisibilityHelper; @@ -56,7 +57,7 @@ import javax.inject.Provider; * Manages the user switch on the Keyguard that is used for opening the QS user panel. */ @KeyguardUserSwitcherScope -public class KeyguardQsUserSwitchController extends ViewController<UserAvatarView> { +public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> { private static final String TAG = "KeyguardQsUserSwitchController"; private static final boolean DEBUG = KeyguardConstants.DEBUG; @@ -76,6 +77,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; private final KeyguardUserDetailAdapter mUserDetailAdapter; private NotificationPanelViewController mNotificationPanelViewController; + private UserAvatarView mUserAvatarView; UserSwitcherController.UserRecord mCurrentUser; // State info for the user switch and keyguard @@ -111,7 +113,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie @Inject public KeyguardQsUserSwitchController( - UserAvatarView view, + FrameLayout view, Context context, @Main Resources resources, ScreenLifecycle screenLifecycle, @@ -143,6 +145,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie protected void onInit() { super.onInit(); if (DEBUG) Log.d(TAG, "onInit"); + mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar); mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) { @Override public View getView(int position, View convertView, ViewGroup parent) { @@ -150,11 +153,10 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie } }; - mView.setOnClickListener(v -> { + mUserAvatarView.setOnClickListener(v -> { if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return; } - if (isListAnimating()) { return; } @@ -163,7 +165,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie openQsUserPanel(); }); - mView.setAccessibilityDelegate(new View.AccessibilityDelegate() { + mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() { public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(host, info); info.addAction(new AccessibilityNodeInfo.AccessibilityAction( @@ -237,12 +239,12 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie R.string.accessibility_multi_user_switch_switcher); } - if (!TextUtils.equals(mView.getContentDescription(), contentDescription)) { - mView.setContentDescription(contentDescription); + if (!TextUtils.equals(mUserAvatarView.getContentDescription(), contentDescription)) { + mUserAvatarView.setContentDescription(contentDescription); } int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL; - mView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId); + mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId); } Drawable getCurrentUserIcon() { @@ -269,7 +271,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie * Get the height of the keyguard user switcher view when closed. */ public int getUserIconHeight() { - return mView.getHeight(); + return mUserAvatarView.getHeight(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java index 22fd93ed10ed..2d47c8f0b577 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java @@ -71,7 +71,11 @@ public class NextAlarmControllerImpl extends BroadcastReceiver if (mNextAlarm != null) { pw.println(new Date(mNextAlarm.getTriggerTime())); pw.print(" PendingIntentPkg="); - pw.println(mNextAlarm.getShowIntent().getCreatorPackage()); + if (mNextAlarm.getShowIntent() != null) { + pw.println(mNextAlarm.getShowIntent().getCreatorPackage()); + } else { + pw.println("showIntent=null"); + } } else { pw.println("null"); } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 0130cb2f8cd3..a288d999550a 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -37,6 +37,7 @@ import android.content.IntentFilter; import android.content.om.FabricatedOverlay; import android.content.om.OverlayIdentifier; import android.content.pm.UserInfo; +import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.Color; import android.net.Uri; @@ -47,9 +48,11 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; +import android.util.TypedValue; import androidx.annotation.NonNull; +import com.android.internal.graphics.ColorUtils; import com.android.systemui.Dumpable; import com.android.systemui.SystemUI; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -59,6 +62,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.monet.ColorScheme; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; @@ -71,6 +75,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; @@ -107,6 +112,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { private DeviceProvisionedController mDeviceProvisionedController; private WallpaperColors mCurrentColors; private WallpaperManager mWallpaperManager; + private ColorScheme mColorScheme; // If fabricated overlays were already created for the current theme. private boolean mNeedsOverlayCreation; // Dominant color extracted from wallpaper, NOT the color used on the overlay @@ -403,25 +409,47 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { * Return the main theme color from a given {@link WallpaperColors} instance. */ protected int getNeutralColor(@NonNull WallpaperColors wallpaperColors) { - return wallpaperColors.getPrimaryColor().toArgb(); + return ColorScheme.getSeedColor(wallpaperColors); } protected int getAccentColor(@NonNull WallpaperColors wallpaperColors) { - Color accentCandidate = wallpaperColors.getSecondaryColor(); - if (accentCandidate == null) { - accentCandidate = wallpaperColors.getTertiaryColor(); - } - if (accentCandidate == null) { - accentCandidate = wallpaperColors.getPrimaryColor(); - } - return accentCandidate.toArgb(); + return ColorScheme.getSeedColor(wallpaperColors); } /** * Given a color candidate, return an overlay definition. */ protected @Nullable FabricatedOverlay getOverlay(int color, int type) { - return null; + boolean nightMode = (mContext.getResources().getConfiguration().uiMode + & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + + mColorScheme = new ColorScheme(color, nightMode); + List<Integer> colorShades = type == ACCENT + ? mColorScheme.getAllAccentColors() : mColorScheme.getAllNeutralColors(); + String name = type == ACCENT ? "accent" : "neutral"; + int paletteSize = mColorScheme.getAccent1().size(); + FabricatedOverlay.Builder overlay = + new FabricatedOverlay.Builder("com.android.systemui", name, "android"); + for (int i = 0; i < colorShades.size(); i++) { + int luminosity = i % paletteSize; + int paletteIndex = i / paletteSize + 1; + String resourceName; + switch (luminosity) { + case 0: + resourceName = "android:color/system_" + name + paletteIndex + "_10"; + break; + case 1: + resourceName = "android:color/system_" + name + paletteIndex + "_50"; + break; + default: + int l = luminosity - 1; + resourceName = "android:color/system_" + name + paletteIndex + "_" + l + "00"; + } + overlay.setResourceValue(resourceName, TypedValue.TYPE_INT_COLOR_ARGB8, + ColorUtils.setAlphaComponent(colorShades.get(i), 0xFF)); + } + + return overlay.build(); } private void updateThemeOverlays() { @@ -521,7 +549,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { .map(key -> key + " -> " + categoryToPackage.get(key)).collect( Collectors.joining(", "))); } - Runnable overlaysAppliedRunnable = () -> onOverlaysApplied(); + Runnable overlaysAppliedRunnable = this::onOverlaysApplied; if (mNeedsOverlayCreation) { mNeedsOverlayCreation = false; mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] { @@ -544,6 +572,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { pw.println("mSecondaryOverlay=" + mSecondaryOverlay); pw.println("mNeutralOverlay=" + mNeutralOverlay); pw.println("mIsMonetEnabled=" + mIsMonetEnabled); + pw.println("mColorScheme=" + mColorScheme); pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation); pw.println("mAcceptColorEvents=" + mAcceptColorEvents); pw.println("mDeferredThemeEvaluation=" + mDeferredThemeEvaluation); diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java index 2b4b49b82df1..d44fb76f306e 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java @@ -220,6 +220,12 @@ public class WalletActivity extends LifecycleActivity implements } @Override + protected void onStop() { + super.onStop(); + finish(); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.wallet_activity_options_menu, menu); return super.onCreateOptionsMenu(menu); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 06b0bb25e01c..88b596c26c0f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -119,6 +119,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { when(mNotificationIcons.getLayoutParams()).thenReturn( mock(RelativeLayout.LayoutParams.class)); when(mView.getContext()).thenReturn(getContext()); + when(mView.getResources()).thenReturn(mResources); when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView); when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView); @@ -127,7 +128,6 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { when(mLargeClockView.getContext()).thenReturn(getContext()); when(mView.isAttachedToWindow()).thenReturn(true); - when(mResources.getString(anyInt())).thenReturn("h:mm"); when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView); mController = new KeyguardClockSwitchController( mView, @@ -226,7 +226,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { } @Test - public void testDetachRemovesSmartspaceView() { + public void testDetachDisconnectsSmartspace() { when(mSmartspaceController.isEnabled()).thenReturn(true); when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView); mController.init(); @@ -237,7 +237,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture()); listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView); - verify(mView).removeView(mFakeSmartspaceView); + verify(mSmartspaceController).disconnect(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java index b6d1e42a4763..619d48d1e306 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.hardware.biometrics.ComponentInfoInternal; +import android.hardware.biometrics.SensorLocationInternal; import android.hardware.biometrics.SensorProperties; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -257,9 +258,10 @@ public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase { componentInfo, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, true /* resetLockoutRequiresHardwareAuthToken */, - 540 /* sensorLocationX */, - 1600 /* sensorLocationY */, - 100 /* sensorRadius */); + List.of(new SensorLocationInternal("" /* displayId */, + 540 /* sensorLocationX */, + 1600 /* sensorLocationY */, + 100 /* sensorRadius */))); } public class TestableView extends AuthBiometricFaceToFingerprintView { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index 8f5eefcff186..f2f0029708ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -19,16 +19,23 @@ package com.android.systemui.biometrics import android.graphics.PointF import android.hardware.biometrics.BiometricSourceType import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.KeyguardStateController +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -54,11 +61,15 @@ class AuthRippleControllerTest : SysuiTestCase() { @Mock private lateinit var configurationController: ConfigurationController @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var authController: AuthController + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController @Mock private lateinit var bypassController: KeyguardBypassController @Mock private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController> @Mock private lateinit var udfpsController: UdfpsController + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var lightRevealScrim: LightRevealScrim @Before fun setUp() { @@ -71,14 +82,18 @@ class AuthRippleControllerTest : SysuiTestCase() { authController, configurationController, keyguardUpdateMonitor, + keyguardStateController, + wakefulnessLifecycle, commandRegistry, notificationShadeWindowController, bypassController, biometricUnlockController, udfpsControllerProvider, + statusBarStateController, rippleView ) controller.init() + `when`(statusBar.lightRevealScrim).thenReturn(lightRevealScrim) } @Test @@ -100,7 +115,7 @@ class AuthRippleControllerTest : SysuiTestCase() { // THEN update sensor location and show ripple verify(rippleView).setSensorLocation(fpsLocation) - verify(rippleView).startUnlockedRipple(any(), any()) + verify(rippleView).startUnlockedRipple(any()) } @Test @@ -121,7 +136,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startUnlockedRipple(any(), any()) + verify(rippleView, never()).startUnlockedRipple(any()) } @Test @@ -142,7 +157,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startUnlockedRipple(any(), any()) + verify(rippleView, never()).startUnlockedRipple(any()) } @Test @@ -166,7 +181,7 @@ class AuthRippleControllerTest : SysuiTestCase() { // THEN show ripple verify(rippleView).setSensorLocation(faceLocation) - verify(rippleView).startUnlockedRipple(any(), any()) + verify(rippleView).startUnlockedRipple(any()) } @Test @@ -186,7 +201,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startUnlockedRipple(any(), any()) + verify(rippleView, never()).startUnlockedRipple(any()) } @Test @@ -201,7 +216,7 @@ class AuthRippleControllerTest : SysuiTestCase() { 0 /* userId */, BiometricSourceType.FACE /* type */, false /* isStrongBiometric */) - verify(rippleView, never()).startUnlockedRipple(any(), any()) + verify(rippleView, never()).startUnlockedRipple(any()) } @Test @@ -216,7 +231,39 @@ class AuthRippleControllerTest : SysuiTestCase() { 0 /* userId */, BiometricSourceType.FINGERPRINT /* type */, false /* isStrongBiometric */) - verify(rippleView, never()).startUnlockedRipple(any(), any()) + verify(rippleView, never()).startUnlockedRipple(any()) + } + + @Test + fun registersAndDeregisters() { + controller.onViewAttached() + val captor = ArgumentCaptor + .forClass(KeyguardStateController.Callback::class.java) + verify(keyguardStateController).addCallback(captor.capture()) + val captor2 = ArgumentCaptor + .forClass(WakefulnessLifecycle.Observer::class.java) + verify(wakefulnessLifecycle).addObserver(captor2.capture()) + controller.onViewDetached() + verify(keyguardStateController).removeCallback(any()) + verify(wakefulnessLifecycle).removeObserver(any()) + } + + @Test + @RunWithLooper(setAsMainLooper = true) + fun testAnimatorRunWhenWakeAndUnlock() { + val fpsLocation = PointF(5f, 5f) + `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) + controller.onViewAttached() + `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true) + `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) + + controller.showRipple(BiometricSourceType.FINGERPRINT) + assertTrue("reveal didn't start on keyguardFadingAway", + controller.startLightRevealScrimOnKeyguardFadingAway) + `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true) + controller.onKeyguardFadingAwayChanged() + assertFalse("reveal triggers multiple times", + controller.startLightRevealScrimOnKeyguardFadingAway) } @Test 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 977b05ce150c..5fee7fbf8705 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt @@ -16,6 +16,9 @@ package com.android.systemui.biometrics +import android.graphics.Rect +import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN import android.hardware.biometrics.SensorProperties import android.hardware.display.DisplayManager import android.hardware.display.DisplayManagerGlobal @@ -30,8 +33,12 @@ import android.view.Display import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS import android.view.DisplayInfo import android.view.LayoutInflater +import android.view.View +import android.view.WindowInsets import android.view.WindowManager +import android.view.WindowMetrics import androidx.test.filters.SmallTest +import com.airbnb.lottie.LottieAnimationView import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.util.concurrency.FakeExecutor @@ -42,9 +49,13 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.eq +import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.any +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit @@ -66,11 +77,13 @@ class SidefpsControllerTest : SysuiTestCase() { @Mock lateinit var windowManager: WindowManager @Mock - lateinit var sidefpsView: SidefpsView + lateinit var sidefpsView: View @Mock lateinit var displayManager: DisplayManager @Mock lateinit var handler: Handler + @Captor + lateinit var overlayCaptor: ArgumentCaptor<View> private val executor = FakeExecutor(FakeSystemClock()) private lateinit var overlayController: ISidefpsController @@ -79,6 +92,8 @@ class SidefpsControllerTest : SysuiTestCase() { @Before fun setup() { `when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sidefpsView) + `when`(sidefpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation))) + .thenReturn(mock(LottieAnimationView::class.java)) `when`(fingerprintManager.sensorPropertiesInternal).thenReturn( listOf( FingerprintSensorPropertiesInternal( @@ -99,6 +114,9 @@ class SidefpsControllerTest : SysuiTestCase() { DEFAULT_DISPLAY_ADJUSTMENTS ) ) + `when`(windowManager.maximumWindowMetrics).thenReturn( + WindowMetrics(Rect(0, 0, 800, 800), WindowInsets.CONSUMED) + ) sideFpsController = SidefpsController( mContext, layoutInflater, fingerprintManager, windowManager, executor, @@ -112,13 +130,61 @@ class SidefpsControllerTest : SysuiTestCase() { @Test fun testSubscribesToOrientationChangesWhenShowingOverlay() { - overlayController.show() + overlayController.show(SENSOR_ID, REASON_UNKNOWN) executor.runAllReady() verify(displayManager).registerDisplayListener(any(), eq(handler)) - overlayController.hide() + overlayController.hide(SENSOR_ID) executor.runAllReady() verify(displayManager).unregisterDisplayListener(any()) } + + @Test + fun testShowsAndHides() { + overlayController.show(SENSOR_ID, REASON_UNKNOWN) + executor.runAllReady() + + verify(windowManager).addView(overlayCaptor.capture(), any()) + + reset(windowManager) + overlayController.hide(SENSOR_ID) + executor.runAllReady() + + verify(windowManager, never()).addView(any(), any()) + verify(windowManager).removeView(eq(overlayCaptor.value)) + } + + @Test + fun testShowsOnce() { + repeat(5) { + overlayController.show(SENSOR_ID, REASON_UNKNOWN) + executor.runAllReady() + } + + verify(windowManager).addView(any(), any()) + verify(windowManager, never()).removeView(any()) + } + + @Test + fun testHidesOnce() { + overlayController.show(SENSOR_ID, REASON_UNKNOWN) + executor.runAllReady() + + repeat(5) { + overlayController.hide(SENSOR_ID) + executor.runAllReady() + } + + verify(windowManager).addView(any(), any()) + verify(windowManager).removeView(any()) + } + + @Test + fun testIgnoredForKeyguard() { + overlayController.show(SENSOR_ID, REASON_AUTH_KEYGUARD) + executor.runAllReady() + + verify(windowManager, never()).addView(any(), any()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index f5c6f981d101..deabda35aeae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.TypedArray; +import android.hardware.biometrics.BiometricOverlayConstants; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.SensorProperties; import android.hardware.display.DisplayManager; @@ -57,7 +58,6 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; -import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -65,6 +65,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.Execution; @@ -123,8 +124,6 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock - private KeyguardViewMediator mKeyguardViewMediator; - @Mock private IUdfpsOverlayControllerCallback mUdfpsOverlayControllerCallback; @Mock private FalsingManager mFalsingManager; @@ -152,6 +151,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private ConfigurationController mConfigurationController; @Mock private SystemClock mSystemClock; + @Mock + private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private FakeExecutor mFgExecutor; @@ -191,6 +192,7 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mLayoutInflater.inflate(R.layout.udfps_keyguard_view, null)) .thenReturn(mKeyguardView); // for showOverlay REASON_AUTH_FPM_KEYGUARD when(mEnrollView.getContext()).thenReturn(mContext); + when(mKeyguardStateController.isOccluded()).thenReturn(false); final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); final List<ComponentInfoInternal> componentInfo = new ArrayList<>(); @@ -221,7 +223,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mStatusBarKeyguardViewManager, mDumpManager, mKeyguardUpdateMonitor, - mKeyguardViewMediator, mFalsingManager, mPowerManager, mAccessibilityManager, @@ -235,7 +236,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mDisplayManager, mHandler, mConfigurationController, - mSystemClock); + mSystemClock, + mUnlockedScreenOffAnimationController); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); @@ -256,7 +258,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void dozeTimeTick() throws RemoteException { mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); mUdfpsController.dozeTimeTick(); verify(mUdfpsView).dozeTimeTick(); @@ -271,7 +273,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the overlay is showing mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); // WHEN ACTION_DOWN is received @@ -294,7 +296,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the overlay is showing mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); // WHEN ACTION_DOWN is received @@ -317,7 +319,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the overlay is showing mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); // WHEN ACTION_DOWN is received @@ -340,7 +342,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the overlay is showing mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); // WHEN ACTION_DOWN is received @@ -362,7 +364,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the overlay is showing mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); // WHEN ACTION_MOVE is received @@ -384,7 +386,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the overlay is showing mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); // WHEN multiple touches are received @@ -404,7 +406,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void showUdfpsOverlay_addsViewToWindow() throws RemoteException { mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mWindowManager).addView(eq(mUdfpsView), any()); } @@ -412,7 +414,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void hideUdfpsOverlay_removesViewFromWindow() throws RemoteException { mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID); mFgExecutor.runAllReady(); verify(mWindowManager).removeView(eq(mUdfpsView)); @@ -422,7 +424,7 @@ public class UdfpsControllerTest extends SysuiTestCase { public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException { // GIVEN overlay was showing and the udfps bouncer is showing mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true); // WHEN the overlay is hidden @@ -436,7 +438,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception { mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler)); @@ -455,7 +457,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the overlay is showing mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); // WHEN ACTION_DOWN is received verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); @@ -477,11 +479,12 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterrupt() throws RemoteException { - // GIVEN that the overlay is showing and screen is on + // GIVEN that the overlay is showing and screen is on and fp is running mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); // WHEN fingerprint is requested because of AOD interrupt mUdfpsController.onAodInterrupt(0, 0, 2f, 3f); // THEN illumination begins @@ -496,9 +499,10 @@ public class UdfpsControllerTest extends SysuiTestCase { public void cancelAodInterrupt() throws RemoteException { // GIVEN AOD interrupt mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); when(mUdfpsView.isIlluminationRequested()).thenReturn(true); // WHEN it is cancelled @@ -511,9 +515,10 @@ public class UdfpsControllerTest extends SysuiTestCase { public void aodInterruptTimeout() throws RemoteException { // GIVEN AOD interrupt mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); when(mUdfpsView.isIlluminationRequested()).thenReturn(true); // WHEN it times out @@ -527,11 +532,29 @@ public class UdfpsControllerTest extends SysuiTestCase { public void aodInterruptScreenOff() throws RemoteException { // GIVEN screen off mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOff(); mFgExecutor.runAllReady(); // WHEN aod interrupt is received + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); + mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); + + // THEN no illumination because screen is off + verify(mUdfpsView, never()).startIllumination(any()); + } + + @Test + public void aodInterrupt_fingerprintNotRunning() throws RemoteException { + // GIVEN showing overlay + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, + mUdfpsOverlayControllerCallback); + mScreenObserver.onScreenTurnedOn(); + mFgExecutor.runAllReady(); + + // WHEN aod interrupt is received when the fingerprint service isn't running + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); // THEN no illumination because screen is off @@ -546,7 +569,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // GIVEN that the overlay is showing mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); // WHEN ACTION_DOWN is received diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java index 88b4039fd2cd..27755edecba6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java @@ -19,6 +19,7 @@ package com.android.systemui.biometrics; import static org.junit.Assert.assertEquals; import android.hardware.biometrics.ComponentInfoInternal; +import android.hardware.biometrics.SensorLocationInternal; import android.hardware.biometrics.SensorProperties; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -61,8 +62,9 @@ public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase { 0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, componentInfo, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, - true /* resetLockoutRequiresHardwareAuthToken */, sensorLocationX, sensorLocationY, - sensorRadius); + true /* resetLockoutRequiresHardwareAuthToken */, + List.of(new SensorLocationInternal("" /* displayId */, + sensorLocationX, sensorLocationY, sensorRadius))); assertEquals(970, UdfpsDialogMeasureAdapter.calculateBottomSpacerHeightForPortrait( @@ -125,8 +127,9 @@ public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase { 0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, componentInfo, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, - true /* resetLockoutRequiresHardwareAuthToken */, sensorLocationX, sensorLocationY, - sensorRadius); + true /* resetLockoutRequiresHardwareAuthToken */, + List.of(new SensorLocationInternal("" /* displayId */, + sensorLocationX, sensorLocationY, sensorRadius))); assertEquals(1205, UdfpsDialogMeasureAdapter.calculateHorizontalSpacerWidthForLandscape( diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java index be3e535d8570..2821f3d21606 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -89,6 +90,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { @Mock private ConfigurationController mConfigurationController; @Mock + private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; + @Mock private UdfpsController mUdfpsController; private FakeSystemClock mSystemClock = new FakeSystemClock(); @@ -121,13 +124,12 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { Optional.of(mStatusBar), mStatusBarKeyguardViewManager, mKeyguardUpdateMonitor, - mExecutor, mDumpManager, - mKeyguardViewMediator, mLockscreenShadeTransitionController, mConfigurationController, mSystemClock, mKeyguardStateController, + mUnlockedScreenOffAnimationController, mUdfpsController); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java index a32cb9b6baa9..d6226aa53f67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java @@ -43,7 +43,6 @@ public class DozeConfigurationUtil { when(params.singleTapUsesProx()).thenReturn(true); when(params.longPressUsesProx()).thenReturn(true); when(params.getQuickPickupAodDuration()).thenReturn(500); - when(params.brightnessUsesProx()).thenReturn(true); doneHolder[0] = true; return params; diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java index deb7d31d87a3..e0520b406a0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java @@ -18,6 +18,7 @@ package com.android.systemui.doze; import static com.android.systemui.doze.DozeMachine.State.DOZE; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD; +import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING; import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE; @@ -43,7 +44,6 @@ import android.os.PowerManager; import android.os.UserHandle; import android.provider.Settings; import android.testing.AndroidTestingRunner; -import android.view.Display; import androidx.test.filters.SmallTest; @@ -114,8 +114,6 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, Optional.of(mSensor.getSensor()), mDozeHost, null /* handler */, mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager); - - mScreen.onScreenState(Display.STATE_ON); } @Test @@ -127,18 +125,9 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { } @Test - public void testAod_usesLightSensor() { - mScreen.onScreenState(Display.STATE_DOZE); - waitForSensorManager(); - - mSensor.sendSensorEvent(3); - - assertEquals(3, mServiceFake.screenBrightness); - } - - @Test public void testAod_usesDebugValue() throws Exception { - mScreen.onScreenState(Display.STATE_DOZE); + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mScreen.transitionTo(INITIALIZED, DOZE_AOD); waitForSensorManager(); Intent intent = new Intent(DozeScreenBrightness.ACTION_AOD_BRIGHTNESS); @@ -161,34 +150,25 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { } @Test - public void testAodDocked_doNotSelectivelyUseProx_usesLightSensor() { - // GIVEN the device doesn't need to selectively register for prox sensors and - // brightness sensor uses prox - when(mDozeParameters.getSelectivelyRegisterSensorsUsingProx()).thenReturn(false); - when(mDozeParameters.brightnessUsesProx()).thenReturn(true); - + public void doze_doesNotUseLightSensor() { // GIVEN the device is docked and the display state changes to ON - when(mDockManager.isDocked()).thenReturn(true); - mScreen.onScreenState(Display.STATE_ON); + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mScreen.transitionTo(INITIALIZED, DOZE); waitForSensorManager(); // WHEN new sensor event sent mSensor.sendSensorEvent(3); - // THEN brightness is updated - assertEquals(3, mServiceFake.screenBrightness); + // THEN brightness is NOT changed, it's set to the default brightness + assertNotSame(3, mServiceFake.screenBrightness); + assertEquals(DEFAULT_BRIGHTNESS, mServiceFake.screenBrightness); } @Test - public void testAodDocked_brightnessDoesNotUseProx_usesLightSensor() { - // GIVEN the device doesn't need to selectively register for prox sensors but - // the brightness sensor doesn't use prox - when(mDozeParameters.getSelectivelyRegisterSensorsUsingProx()).thenReturn(true); - when(mDozeParameters.brightnessUsesProx()).thenReturn(false); - + public void aod_usesLightSensor() { // GIVEN the device is docked and the display state changes to ON - when(mDockManager.isDocked()).thenReturn(true); - mScreen.onScreenState(Display.STATE_ON); + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mScreen.transitionTo(INITIALIZED, DOZE_AOD); waitForSensorManager(); // WHEN new sensor event sent @@ -198,34 +178,25 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { assertEquals(3, mServiceFake.screenBrightness); } - @Test - public void testAodDocked_noProx_brightnessUsesProx_doNotUseLightSensor() { - final int startBrightness = mServiceFake.screenBrightness; - - // GIVEN the device needs to selectively register for prox sensors and - // the brightness sensor uses prox - when(mDozeParameters.getSelectivelyRegisterSensorsUsingProx()).thenReturn(true); - when(mDozeParameters.brightnessUsesProx()).thenReturn(true); - - // GIVEN the device is docked and the display state is on - when(mDockManager.isDocked()).thenReturn(true); - mScreen.onScreenState(Display.STATE_ON); + public void docked_usesLightSensor() { + // GIVEN the device is docked and the display state changes to ON + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mScreen.transitionTo(INITIALIZED, DOZE_AOD); + mScreen.transitionTo(DOZE_AOD, DOZE_AOD_DOCKED); waitForSensorManager(); // WHEN new sensor event sent mSensor.sendSensorEvent(3); - // THEN brightness is NOT changed - assertNotSame(3, mServiceFake.screenBrightness); - assertEquals(startBrightness, mServiceFake.screenBrightness); + // THEN brightness is updated + assertEquals(3, mServiceFake.screenBrightness); } @Test public void testPausingAod_doesNotResetBrightness() throws Exception { mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); - mScreen.onScreenState(Display.STATE_DOZE); waitForSensorManager(); mSensor.sendSensorEvent(1); @@ -266,18 +237,6 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { } @Test - public void testOnScreenStateSetBeforeTransition_stillRegistersSensor() { - mScreen.transitionTo(UNINITIALIZED, INITIALIZED); - mScreen.onScreenState(Display.STATE_DOZE); - mScreen.transitionTo(INITIALIZED, DOZE_AOD); - waitForSensorManager(); - - mSensor.sendSensorEvent(1); - - assertEquals(1, mServiceFake.screenBrightness); - } - - @Test public void testNullSensor() throws Exception { mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, Optional.empty() /* sensor */, mDozeHost, null /* handler */, @@ -287,15 +246,12 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { mScreen.transitionTo(INITIALIZED, DOZE_AOD); mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING); mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED); - mScreen.onScreenState(Display.STATE_DOZE); - mScreen.onScreenState(Display.STATE_OFF); } @Test public void testNoBrightnessDeliveredAfterFinish() throws Exception { mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); - mScreen.onScreenState(Display.STATE_DOZE); mScreen.transitionTo(DOZE_AOD, FINISH); waitForSensorManager(); @@ -308,7 +264,6 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { public void testNonPositiveBrightness_keepsPreviousBrightnessAndScrim() { mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); - mScreen.onScreenState(Display.STATE_DOZE); waitForSensorManager(); mSensor.sendSensorEvent(1); @@ -322,7 +277,6 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { public void pausingAod_unblanksAfterSensor() { mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); - mScreen.onScreenState(Display.STATE_DOZE); waitForSensorManager(); mSensor.sendSensorEvent(2); @@ -334,7 +288,6 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { reset(mDozeHost); mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD); - mScreen.onScreenState(Display.STATE_DOZE); waitForSensorManager(); mSensor.sendSensorEvent(2); verify(mDozeHost).setAodDimmingScrim(eq(0f)); @@ -344,7 +297,6 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { public void pausingAod_unblanksIfSensorWasAlwaysReady() throws Exception { mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); - mScreen.onScreenState(Display.STATE_DOZE); waitForSensorManager(); mSensor.sendSensorEvent(2); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java index 41d7fd64fe7a..150ab7700e4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java @@ -21,6 +21,7 @@ import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED; import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING; +import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE; import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING; import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE; import static com.android.systemui.doze.DozeMachine.State.FINISH; @@ -34,6 +35,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -45,6 +47,8 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.AuthController; +import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.wakelock.WakeLockFake; import com.android.systemui.utils.os.FakeHandler; @@ -56,6 +60,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import javax.inject.Provider; + @RunWith(AndroidJUnit4.class) @SmallTest public class DozeScreenStateTest extends SysuiTestCase { @@ -68,17 +74,29 @@ public class DozeScreenStateTest extends SysuiTestCase { private DozeParameters mDozeParameters; private WakeLockFake mWakeLock; private DozeScreenState mScreen; + @Mock + private Provider<UdfpsController> mUdfpsControllerProvider; + @Mock + private AuthController mAuthController; + @Mock + private UdfpsController mUdfpsController; + @Mock + private DozeLog mDozeLog; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true); when(mDozeParameters.getAlwaysOn()).thenReturn(true); + when(mUdfpsControllerProvider.get()).thenReturn(mUdfpsController); + when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true); + when(mUdfpsController.isFingerDown()).thenReturn(false); + mServiceFake = new DozeServiceFake(); mHandlerFake = new FakeHandler(Looper.getMainLooper()); mWakeLock = new WakeLockFake(); mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters, - mWakeLock); + mWakeLock, mAuthController, mUdfpsControllerProvider, mDozeLog); } @Test @@ -233,4 +251,56 @@ public class DozeScreenStateTest extends SysuiTestCase { assertEquals(Display.STATE_OFF, mServiceFake.screenState); } + @Test + public void testDelayEnterDozeScreenState_whenUdfpsFingerDown() { + // GIVEN AOD is initialized + when(mDozeParameters.shouldControlScreenOff()).thenReturn(true); + mHandlerFake.setMode(QUEUEING); + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mHandlerFake.dispatchQueuedMessages(); + + mScreen.transitionTo(INITIALIZED, DOZE_AOD); + + // WHEN udfps is activated (fingerDown) + when(mUdfpsController.isFingerDown()).thenReturn(true); + mHandlerFake.dispatchQueuedMessages(); + + // THEN the display screen state doesn't immediately change + assertEquals(Display.STATE_ON, mServiceFake.screenState); + + // WHEN udfpsController finger is no longer down and the queued messages are run + when(mUdfpsController.isFingerDown()).thenReturn(false); + mHandlerFake.dispatchQueuedMessages(); + + // THEN the display screen state will change + assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState); + } + + @Test + public void testDelayExitPulsingScreenState_whenUdfpsFingerDown() { + // GIVEN we're pulsing + when(mDozeParameters.shouldControlScreenOff()).thenReturn(true); + mHandlerFake.setMode(QUEUEING); + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mScreen.transitionTo(INITIALIZED, DOZE_AOD); + mScreen.transitionTo(DOZE_AOD, DOZE_REQUEST_PULSE); + mScreen.transitionTo(DOZE_REQUEST_PULSE, DOZE_PULSING); + mScreen.transitionTo(DOZE_PULSING, DOZE_PULSE_DONE); + mHandlerFake.dispatchQueuedMessages(); + + // WHEN udfps is activated while are transitioning back to DOZE_AOD + mScreen.transitionTo(DOZE_PULSE_DONE, DOZE_AOD); + when(mUdfpsController.isFingerDown()).thenReturn(true); + mHandlerFake.dispatchQueuedMessages(); + + // THEN the display screen state doesn't immediately change + assertEquals(Display.STATE_ON, mServiceFake.screenState); + + // WHEN udfpsController finger is no longer down and the queued messages are run + when(mUdfpsController.isFingerDown()).thenReturn(false); + mHandlerFake.dispatchQueuedMessages(); + + // THEN the display screen state will change + assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState); + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index 10997fab081f..9577c7a2d6fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -45,6 +45,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.FakeThreadFactory; import com.android.systemui.util.sensors.AsyncSensorManager; @@ -83,6 +84,8 @@ public class DozeTriggersTest extends SysuiTestCase { private AuthController mAuthController; @Mock private UiEventLogger mUiEventLogger; + @Mock + private KeyguardStateController mKeyguardStateController; private DozeTriggers mTriggers; private FakeSensorManager mSensors; @@ -114,7 +117,7 @@ public class DozeTriggersTest extends SysuiTestCase { mTriggers = new DozeTriggers(mContext, mHost, config, dozeParameters, asyncSensorManager, wakeLock, mDockManager, mProximitySensor, mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(), - mAuthController, mExecutor, mUiEventLogger); + mAuthController, mExecutor, mUiEventLogger, mKeyguardStateController); mTriggers.setDozeMachine(mMachine); waitForSensorManager(); } @@ -217,6 +220,32 @@ public class DozeTriggersTest extends SysuiTestCase { } @Test + public void testPickupGesture() { + // GIVEN device is in doze (screen blank, but running doze sensors) + when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE); + + // WHEN the pick up gesture is triggered and keyguard isn't occluded + when(mKeyguardStateController.isOccluded()).thenReturn(false); + mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null); + + // THEN wakeup + verify(mMachine).wakeUp(); + } + + @Test + public void testPickupGestureDroppedKeyguardOccluded() { + // GIVEN device is in doze (screen blank, but running doze sensors) + when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE); + + // WHEN the pick up gesture is triggered and keyguard IS occluded + when(mKeyguardStateController.isOccluded()).thenReturn(true); + mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null); + + // THEN never wakeup + verify(mMachine, never()).wakeUp(); + } + + @Test public void testOnSensor_Fingerprint() { // GIVEN dozing state when(mMachine.getState()).thenReturn(DOZE_AOD); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java index d279bbb02cee..af6dee76139e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -18,6 +18,7 @@ package com.android.systemui.keyguard; import static junit.framework.Assert.assertEquals; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -25,6 +26,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; import android.graphics.PointF; +import android.hardware.biometrics.SensorLocationInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Vibrator; import android.testing.AndroidTestingRunner; @@ -49,6 +51,8 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; +import com.airbnb.lottie.LottieAnimationView; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -80,6 +84,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { private @Mock DelayableExecutor mDelayableExecutor; private @Mock Vibrator mVibrator; private @Mock AuthRippleController mAuthRippleController; + private @Mock LottieAnimationView mAodFp; private LockIconViewController mLockIconViewController; @@ -101,6 +106,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { when(mLockIconView.getContext()).thenReturn(mContext); when(mContext.getResources()).thenReturn(mResources); when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); + when(mLockIconView.findViewById(anyInt())).thenReturn(mAodFp); mLockIconViewController = new LockIconViewController( mLockIconView, @@ -121,7 +127,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { @Test public void testUpdateFingerprintLocationOnInit() { - // GIVEN fp sensor location is available pre-init + // GIVEN fp sensor location is available pre-attached final PointF udfpsLocation = new PointF(50, 75); final int radius = 33; final FingerprintSensorPropertiesInternal fpProps = @@ -132,14 +138,15 @@ public class LockIconViewControllerTest extends SysuiTestCase { /* component info */ new ArrayList<>(), /* sensorType */ 3, /* resetLockoutRequiresHwToken */ false, - (int) udfpsLocation.x, (int) udfpsLocation.y, radius); + List.of(new SensorLocationInternal("" /* displayId */, + (int) udfpsLocation.x, (int) udfpsLocation.y, radius))); when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation); when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps)); // WHEN lock icon view controller is initialized and attached mLockIconViewController.init(); captureAttachListener(); - mAttachListener.onViewAttachedToWindow(null); + mAttachListener.onViewAttachedToWindow(mLockIconView); // THEN lock icon view location is updated with the same coordinates as fpProps verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius)); @@ -152,8 +159,10 @@ public class LockIconViewControllerTest extends SysuiTestCase { when(mAuthController.getFingerprintSensorLocation()).thenReturn(null); when(mAuthController.getUdfpsProps()).thenReturn(null); mLockIconViewController.init(); + captureAttachListener(); + mAttachListener.onViewAttachedToWindow(mLockIconView); - // GIVEN fp sensor location is available post-init + // GIVEN fp sensor location is available post-atttached captureAuthControllerCallback(); final PointF udfpsLocation = new PointF(50, 75); final int radius = 33; @@ -165,7 +174,8 @@ public class LockIconViewControllerTest extends SysuiTestCase { /* component info */ new ArrayList<>(), /* sensorType */ 3, /* resetLockoutRequiresHwToken */ false, - (int) udfpsLocation.x, (int) udfpsLocation.y, radius); + List.of(new SensorLocationInternal("" /* displayId */, + (int) udfpsLocation.x, (int) udfpsLocation.y, radius))); when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation); when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java new file mode 100644 index 000000000000..bc86ef98c6fe --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.monet; + +import android.app.WallpaperColors; +import android.graphics.Color; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class ColorSchemeTest extends SysuiTestCase { + @Test + public void testFilterTransparency() { + ColorScheme colorScheme = new ColorScheme(Color.TRANSPARENT, false /* darkTheme */); + Assert.assertEquals(colorScheme.getAllAccentColors(), + new ColorScheme(0xFF1b6ef3, false).getAllAccentColors()); + } + + @Test + public void testDontFilterOpaque() { + ColorScheme colorScheme = new ColorScheme(0xFFFF0000, false /* darkTheme */); + Assert.assertNotEquals(colorScheme.getAllAccentColors(), + new ColorScheme(0xFF1b6ef3, false).getAllAccentColors()); + } + + @Test + public void testUniqueColors() { + WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xffaec00a), + Color.valueOf(0xffaec00a), Color.valueOf(0xffaec00a)); + + List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors); + Assert.assertEquals(rankedSeedColors, List.of(0xffaec00a)); + } + + + @Test + public void testFiltersInvalidColors() { + WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xff5e7ea2), + Color.valueOf(0xff5e7ea2), Color.valueOf(0xff000000)); + + List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors); + Assert.assertEquals(rankedSeedColors, List.of(0xff5e7ea2)); + } + + @Test + public void testInvalidColorBecomesGBlue() { + WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xff000000), null, + null); + + List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors); + Assert.assertEquals(rankedSeedColors, List.of(0xFF1b6ef3)); + } + + @Test + public void testDontFilterRRGGBB() { + ColorScheme colorScheme = new ColorScheme(0xFF0000, false /* darkTheme */); + Assert.assertEquals(colorScheme.getAllAccentColors(), + new ColorScheme(0xFFFF0000, false).getAllAccentColors()); + } + + @Test + public void testNoPopulationSignal() { + WallpaperColors wallpaperColors = new WallpaperColors(Color.valueOf(0xffaec00a), + Color.valueOf(0xffbe0000), Color.valueOf(0xffcc040f)); + + List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors); + Assert.assertEquals(rankedSeedColors, List.of(0xffaec00a, 0xffbe0000, 0xffcc040f)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt index 03248f7e3a70..511848d8a3af 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt @@ -22,11 +22,13 @@ import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.UserInfo +import android.os.Process.SYSTEM_UID import android.os.UserHandle import android.permission.PermGroupUsage import android.permission.PermissionManager import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.appops.AppOpsController import com.android.systemui.plugins.ActivityStarter @@ -53,6 +55,7 @@ import org.mockito.Mockito.`when` import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.mock import org.mockito.Mockito.never +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -96,6 +99,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() { private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback> @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent> + @Mock + private lateinit var uiEventLogger: UiEventLogger private val backgroundExecutor = FakeExecutor(FakeSystemClock()) private val uiExecutor = FakeExecutor(FakeSystemClock()) @@ -136,6 +141,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { privacyLogger, keyguardStateController, appOpsController, + uiEventLogger, dialogProvider ) } @@ -550,6 +556,49 @@ class PrivacyDialogControllerTest : SysuiTestCase() { verify(dialog, never()).dismiss() } + @Test + fun testCallOnSecondaryUser() { + // Calls happen in + val usage = createMockPermGroupUsage(uid = SYSTEM_UID, isPhoneCall = true) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) + `when`(userTracker.userProfiles).thenReturn(listOf( + UserInfo(ENT_USER_ID, "", 0) + )) + + controller.showDialog(context) + exhaustExecutors() + + verify(dialog).show() + } + + @Test + fun testStartActivityLogs() { + val usage = createMockPermGroupUsage() + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) + controller.showDialog(context) + exhaustExecutors() + + dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, USER_ID) + verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS, + USER_ID, TEST_PACKAGE_NAME) + } + + @Test + fun testDismissedDialogLogs() { + val usage = createMockPermGroupUsage() + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) + controller.showDialog(context) + exhaustExecutors() + + verify(dialog).addOnDismissListener(capture(dialogDismissedCaptor)) + + dialogDismissedCaptor.value.onDialogDismissed() + + controller.dismissDialog() + + verify(uiEventLogger, times(1)).log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED) + } + private fun exhaustExecutors() { FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor) } 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 109721f5b096..12c0d5379a20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -43,6 +43,7 @@ import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.MediaHost; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -95,6 +96,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { private KeyguardBypassController mBypassController; @Mock private FalsingManager mFalsingManager; + @Mock + private FeatureFlags mFeatureFlags; public QSFragmentTest() { super(QSFragment.class); @@ -136,7 +139,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { () -> mock(AutoTileManager.class), mock(DumpManager.class), mock(BroadcastDispatcher.class), Optional.of(mock(StatusBar.class)), mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class), - mock(SecureSettings.class), mock(CustomTileStatePersister.class)); + mock(SecureSettings.class), mock(CustomTileStatePersister.class), mFeatureFlags); qs.setHost(host); qs.setListening(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java index 1a87975f0e4d..a21f4887038c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java @@ -42,6 +42,7 @@ import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.brightness.BrightnessController; import com.android.systemui.settings.brightness.BrightnessSlider; import com.android.systemui.settings.brightness.ToggleSlider; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.animation.DisappearParameters; @@ -94,6 +95,8 @@ public class QSPanelControllerTest extends SysuiTestCase { QSTileView mQSTileView; @Mock PagedTileLayout mPagedTileLayout; + @Mock + CommandQueue mCommandQueue; FalsingManagerFake mFalsingManager = new FalsingManagerFake(); private QSPanelController mController; @@ -121,7 +124,7 @@ public class QSPanelControllerTest extends SysuiTestCase { mQSTileHost, mQSCustomizerController, true, mMediaHost, mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger, mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory, - mFalsingManager + mFalsingManager, mCommandQueue ); mController.init(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 6c7f770f9482..6bb2b9dda593 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -121,6 +121,8 @@ public class QSTileHostTest extends SysuiTestCase { private SecureSettings mSecureSettings; @Mock private CustomTileStatePersister mCustomTileStatePersister; + @Mock + private FeatureFlags mFeatureFlags; private Handler mHandler; private TestableLooper mLooper; @@ -138,8 +140,9 @@ public class QSTileHostTest extends SysuiTestCase { mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler, mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager, mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker, - mSecureSettings, mCustomTileStatePersister); + mSecureSettings, mCustomTileStatePersister, mFeatureFlags); setUpTileFactory(); + when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(false); } private void setUpTileFactory() { @@ -167,13 +170,13 @@ public class QSTileHostTest extends SysuiTestCase { @Test public void testLoadTileSpecs_emptySetting() { - List<String> tiles = QSTileHost.loadTileSpecs(mContext, ""); + List<String> tiles = QSTileHost.loadTileSpecs(mContext, "", mFeatureFlags); assertFalse(tiles.isEmpty()); } @Test public void testLoadTileSpecs_nullSetting() { - List<String> tiles = QSTileHost.loadTileSpecs(mContext, null); + List<String> tiles = QSTileHost.loadTileSpecs(mContext, null, mFeatureFlags); assertFalse(tiles.isEmpty()); } @@ -187,6 +190,55 @@ public class QSTileHostTest extends SysuiTestCase { } @Test + public void testRemoveWifiAndCellularWithoutInternet() { + when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true); + mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2"); + + assertEquals("internet", mQSTileHost.mTileSpecs.get(0)); + assertEquals("spec1", mQSTileHost.mTileSpecs.get(1)); + assertEquals("spec2", mQSTileHost.mTileSpecs.get(2)); + } + + @Test + public void testRemoveWifiAndCellularWithInternet() { + when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true); + mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2, internet"); + + assertEquals("spec1", mQSTileHost.mTileSpecs.get(0)); + assertEquals("spec2", mQSTileHost.mTileSpecs.get(1)); + assertEquals("internet", mQSTileHost.mTileSpecs.get(2)); + } + + @Test + public void testRemoveWifiWithoutInternet() { + when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true); + mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, wifi, spec2"); + + assertEquals("spec1", mQSTileHost.mTileSpecs.get(0)); + assertEquals("internet", mQSTileHost.mTileSpecs.get(1)); + assertEquals("spec2", mQSTileHost.mTileSpecs.get(2)); + } + + @Test + public void testRemoveCellWithInternet() { + when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true); + mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, spec2, cell, internet"); + + assertEquals("spec1", mQSTileHost.mTileSpecs.get(0)); + assertEquals("spec2", mQSTileHost.mTileSpecs.get(1)); + assertEquals("internet", mQSTileHost.mTileSpecs.get(2)); + } + + @Test + public void testNoWifiNoCellularNoInternet() { + when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true); + mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2"); + + assertEquals("spec1", mQSTileHost.mTileSpecs.get(0)); + assertEquals("spec2", mQSTileHost.mTileSpecs.get(1)); + } + + @Test public void testSpecWithInvalidDoesNotUseDefault() { mContext.getOrCreateTestableResources() .addOverride(R.string.quick_settings_tiles, "spec1,spec2"); @@ -319,7 +371,7 @@ public class QSTileHostTest extends SysuiTestCase { @Test public void testLoadTileSpec_repeated() { - List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2"); + List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2", mFeatureFlags); assertEquals(2, specs.size()); assertEquals("spec1", specs.get(0)); @@ -330,7 +382,7 @@ public class QSTileHostTest extends SysuiTestCase { public void testLoadTileSpec_repeatedInDefault() { mContext.getOrCreateTestableResources() .addOverride(R.string.quick_settings_tiles_default, "spec1,spec1"); - List<String> specs = QSTileHost.loadTileSpecs(mContext, "default"); + List<String> specs = QSTileHost.loadTileSpecs(mContext, "default", mFeatureFlags); // Remove spurious tiles, like dbg:mem specs.removeIf(spec -> !"spec1".equals(spec)); @@ -341,7 +393,7 @@ public class QSTileHostTest extends SysuiTestCase { public void testLoadTileSpec_repeatedDefaultAndSetting() { mContext.getOrCreateTestableResources() .addOverride(R.string.quick_settings_tiles_default, "spec1"); - List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1"); + List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1", mFeatureFlags); // Remove spurious tiles, like dbg:mem specs.removeIf(spec -> !"spec1".equals(spec)); @@ -379,11 +431,12 @@ public class QSTileHostTest extends SysuiTestCase { Provider<AutoTileManager> autoTiles, DumpManager dumpManager, BroadcastDispatcher broadcastDispatcher, StatusBar statusBar, QSLogger qsLogger, UiEventLogger uiEventLogger, UserTracker userTracker, - SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister) { + SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister, + FeatureFlags featureFlags) { super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager, tunerService, autoTiles, dumpManager, broadcastDispatcher, Optional.of(statusBar), qsLogger, uiEventLogger, userTracker, secureSettings, - customTileStatePersister); + customTileStatePersister, featureFlags); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index 8546a359b459..018806e816d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -109,6 +109,8 @@ public class TileQueryHelperTest extends SysuiTestCase { private PackageManager mPackageManager; @Mock private UserTracker mUserTracker; + @Mock + private FeatureFlags mFeatureFlags; @Captor private ArgumentCaptor<List<TileQueryHelper.TileInfo>> mCaptor; @@ -134,12 +136,12 @@ public class TileQueryHelperTest extends SysuiTestCase { } } ).when(mQSTileHost).createTile(anyString()); - + when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(false); FakeSystemClock clock = new FakeSystemClock(); mMainExecutor = new FakeExecutor(clock); mBgExecutor = new FakeExecutor(clock); mTileQueryHelper = new TileQueryHelper( - mContext, mUserTracker, mMainExecutor, mBgExecutor); + mContext, mUserTracker, mMainExecutor, mBgExecutor, mFeatureFlags); mTileQueryHelper.setListener(mListener); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 2b1840462291..e3045eb2d63f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -43,6 +43,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSFactoryImpl; @@ -98,6 +99,8 @@ public class TileServicesTest extends SysuiTestCase { private UserTracker mUserTracker; @Mock private SecureSettings mSecureSettings; + @Mock + private FeatureFlags mFeatureFlags; @Before public void setUp() throws Exception { @@ -119,7 +122,8 @@ public class TileServicesTest extends SysuiTestCase { mUiEventLogger, mUserTracker, mSecureSettings, - mock(CustomTileStatePersister.class)); + mock(CustomTileStatePersister.class), + mFeatureFlags); mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher, mUserTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt index ebc6f2aa6e9a..6a68b71f639b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt @@ -157,6 +157,21 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() { assertThat(view.translationY).isWithin(0.01f).of(3.75f) } + @Test + fun testUpdateViewPositions_viewOnTheLeftAndMovedToTheRight_viewTranslatedToTheLeft() { + givenScreen(width = 100, height = 100, rotation = ROTATION_0) + val view = createView(x = 20) + animator.registerViewForAnimation(view) + animator.onTransitionStarted() + animator.onTransitionProgress(0.5f) + view.updateMock(x = 80) // view moved from the left side to the right + + animator.updateViewPositions() + + // Negative translationX -> translated to the left + assertThat(view.translationX).isWithin(0.1f).of(-5.25f) + } + private fun createView( x: Int = 0, y: Int = 0, @@ -176,7 +191,30 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() { whenever(view.width).thenReturn(width) whenever(view.height).thenReturn(height) - return view.apply { + view.updateMock(x, y, width, height, translationX, translationY) + + return view + } + + private fun View.updateMock( + x: Int = 0, + y: Int = 0, + width: Int = 10, + height: Int = 10, + translationX: Float = 0f, + translationY: Float = 0f + ) { + doAnswer { + val location = (it.arguments[0] as IntArray) + location[0] = x + location[1] = y + Unit + }.`when`(this).getLocationOnScreen(any()) + + whenever(this.width).thenReturn(width) + whenever(this.height).thenReturn(height) + + this.apply { setTranslationX(translationX) setTranslationY(translationY) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt new file mode 100644 index 000000000000..096efad50615 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DisableFlagsLoggerTest.kt @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows +import org.junit.Test + +@SmallTest +class DisableFlagsLoggerTest : SysuiTestCase() { + private val disable1Flags = listOf( + DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'), + DisableFlagsLogger.DisableFlag(0b010, 'B', 'b'), + DisableFlagsLogger.DisableFlag(0b001, 'C', 'c'), + ) + private val disable2Flags = listOf( + DisableFlagsLogger.DisableFlag(0b10, 'M', 'm'), + DisableFlagsLogger.DisableFlag(0b01, 'N', 'n'), + ) + + private val disableFlagsLogger = DisableFlagsLogger(disable1Flags, disable2Flags) + + @Test + fun getDisableFlagsString_oldAndNewSame_statesLoggedButDiffsNotLogged() { + val state = DisableFlagsLogger.DisableState( + 0b111, // ABC + 0b01 // mN + ) + + val result = disableFlagsLogger.getDisableFlagsString(state, state) + + assertThat(result).contains("Old: ABC.mN") + assertThat(result).contains("New: ABC.mN") + assertThat(result).doesNotContain("(") + assertThat(result).doesNotContain(")") + } + + @Test + fun getDisableFlagsString_oldAndNewDifferent_statesAndDiffLogged() { + val result = disableFlagsLogger.getDisableFlagsString( + DisableFlagsLogger.DisableState( + 0b111, // ABC + 0b01, // mN + ), + DisableFlagsLogger.DisableState( + 0b001, // abC + 0b10 // Mn + ) + ) + + assertThat(result).contains("Old: ABC.mN") + assertThat(result).contains("New: abC.Mn") + assertThat(result).contains("(ab.Mn)") + } + + @Test + fun getDisableFlagsString_onlyDisable2Different_diffLoggedCorrectly() { + val result = disableFlagsLogger.getDisableFlagsString( + DisableFlagsLogger.DisableState( + 0b001, // abC + 0b01, // mN + ), + DisableFlagsLogger.DisableState( + 0b001, // abC + 0b00 // mn + ) + ) + + assertThat(result).contains("(.n)") + } + + @Test + fun getDisableFlagsString_nullLocalModification_localModNotLogged() { + val result = disableFlagsLogger.getDisableFlagsString( + DisableFlagsLogger.DisableState(0, 0), + DisableFlagsLogger.DisableState(1, 1), + newAfterLocalModification = null + ) + + assertThat(result).doesNotContain("local modification") + } + + @Test + fun getDisableFlagsString_newAfterLocalModificationSameAsNew_localModNotLogged() { + val newState = DisableFlagsLogger.DisableState( + 0b001, // abC + 0b10 // mn + ) + + val result = disableFlagsLogger.getDisableFlagsString( + DisableFlagsLogger.DisableState(0, 0), newState, newState + ) + + assertThat(result).doesNotContain("local modification") + } + + @Test + fun getDisableFlagsString_newAfterLocalModificationDifferent_localModAndDiffLogged() { + val result = disableFlagsLogger.getDisableFlagsString( + old = DisableFlagsLogger.DisableState(0, 0), + new = DisableFlagsLogger.DisableState( + 0b000, // abc + 0b00 // mn + ), + newAfterLocalModification = DisableFlagsLogger.DisableState( + 0b100, // Abc + 0b10 // Mn + ) + ) + + assertThat(result).contains("local modification: Abc.Mn (A.M)") + } + + @Test + fun constructor_defaultDisableFlags_noException() { + // Just creating the logger with the default params will trigger the exception if there + // is one. + DisableFlagsLogger() + } + + @Test + fun constructor_disable1_FlagIsSetSymbolNotUnique_exception() { + assertThrows(IllegalArgumentException::class.java) { + DisableFlagsLogger( + disable1FlagsList = listOf( + DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'), + DisableFlagsLogger.DisableFlag(0b010, 'A', 'b'), + ), + listOf() + ) + } + } + + @Test + fun constructor_disable1_FlagNotSetSymbolNotUnique_exception() { + assertThrows(IllegalArgumentException::class.java) { + DisableFlagsLogger( + disable1FlagsList = listOf( + DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'), + DisableFlagsLogger.DisableFlag(0b010, 'B', 'a'), + ), + listOf() + ) + } + } + + @Test + fun constructor_disable2_FlagIsSetSymbolNotUnique_exception() { + assertThrows(IllegalArgumentException::class.java) { + DisableFlagsLogger( + disable1FlagsList = listOf(), + disable2FlagsList = listOf( + DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'), + DisableFlagsLogger.DisableFlag(0b010, 'A', 'b'), + ), + ) + } + } + + @Test + fun constructor_disable2_FlagNotSetSymbolNotUnique_exception() { + assertThrows(IllegalArgumentException::class.java) { + DisableFlagsLogger( + disable1FlagsList = listOf(), + disable2FlagsList = listOf( + DisableFlagsLogger.DisableFlag(0b100, 'A', 'a'), + DisableFlagsLogger.DisableFlag(0b010, 'B', 'a'), + ), + ) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 60f0b68acac3..4276f7ce7b12 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -274,6 +274,26 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { } @Test + public void onBiometricAuthenticated_onLockScreen() { + // GIVEN not dozing + when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true); + + // WHEN we want to unlock collapse + mBiometricUnlockController.startWakeAndUnlock( + BiometricUnlockController.MODE_UNLOCK_COLLAPSING); + + // THEN we collpase the panels and notify authenticated + verify(mShadeController).animateCollapsePanels( + /* flags */ anyInt(), + /* force */ eq(true), + /* delayed */ eq(false), + /* speedUpFactor */ anyFloat() + ); + verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated( + /* strongAuth */ eq(false)); + } + + @Test public void onBiometricAuthenticated_whenFace_noBypass_encrypted_doNothing() { reset(mUpdateMonitor); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLoggerTest.kt new file mode 100644 index 000000000000..f3136c7be967 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentLoggerTest.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.log.LogBufferFactory +import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.statusbar.DisableFlagsLogger +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.Mockito.mock +import java.io.PrintWriter +import java.io.StringWriter + +@SmallTest +class CollapsedStatusBarFragmentLoggerTest : SysuiTestCase() { + + private val buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java)) + .create("buffer", 10) + private val disableFlagsLogger = DisableFlagsLogger( + listOf(DisableFlagsLogger.DisableFlag(0b001, 'A', 'a')), + listOf(DisableFlagsLogger.DisableFlag(0b001, 'B', 'b')) + ) + private val logger = CollapsedStatusBarFragmentLogger(buffer, disableFlagsLogger) + + @Test + fun logToBuffer_bufferHasStates() { + val state = DisableFlagsLogger.DisableState(0, 1) + + logger.logDisableFlagChange(state, state) + + val stringWriter = StringWriter() + buffer.dump(PrintWriter(stringWriter), tailLength = 0) + val actualString = stringWriter.toString() + val expectedLogString = disableFlagsLogger.getDisableFlagsString(state, state) + + assertThat(actualString).contains(expectedLogString) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java index 5f4670045ad6..b08dbee687e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java @@ -39,8 +39,11 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.LogcatEchoTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.DisableFlagsLogger; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; @@ -263,6 +266,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mNetworkController, mStatusBarStateController, mCommandQueue, + new CollapsedStatusBarFragmentLogger( + new LogBuffer("TEST", 1, 1, mock(LogcatEchoTracker.class)), + new DisableFlagsLogger() + ), mOperatorNameViewControllerFactory); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java index f34f21bde803..d098e1a0b8a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java @@ -16,42 +16,80 @@ package com.android.systemui.statusbar.phone; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + import static com.google.common.truth.Truth.assertThat; +import static org.mockito.AdditionalAnswers.returnsFirstArg; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +import android.content.res.Resources; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.doze.util.BurnInHelperKt; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; @SmallTest @RunWith(AndroidTestingRunner.class) public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { private static final int SCREEN_HEIGHT = 2000; - private static final int EMPTY_MARGIN = 0; private static final int EMPTY_HEIGHT = 0; private static final float ZERO_DRAG = 0.f; private static final float OPAQUE = 1.f; private static final float TRANSPARENT = 0.f; + + @Mock + private Resources mResources; + private KeyguardClockPositionAlgorithm mClockPositionAlgorithm; private KeyguardClockPositionAlgorithm.Result mClockPosition; + + private MockitoSession mStaticMockSession; + private int mNotificationStackHeight; + private float mPanelExpansion; + private int mKeyguardStatusBarHeaderHeight; private int mKeyguardStatusHeight; private float mDark; private float mQsExpansion; - private int mCutoutTopInsetPx = 0; + private int mCutoutTopInset = 0; private boolean mIsSplitShade = false; + private float mUdfpsTop = -1; + private float mClockBottom = SCREEN_HEIGHT / 2; + private boolean mClockTopAligned; @Before public void setUp() { + MockitoAnnotations.initMocks(this); + mStaticMockSession = mockitoSession() + .mockStatic(BurnInHelperKt.class) + .startMocking(); + mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(); + when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0); + mClockPositionAlgorithm.loadDimens(mResources); + mClockPosition = new KeyguardClockPositionAlgorithm.Result(); } + @After + public void tearDown() { + mStaticMockSession.finishMocking(); + } + @Test public void clockPositionTopOfScreenOnAOD() { // GIVEN on AOD and clock has 0 height @@ -71,7 +109,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { // GIVEN on AOD and clock has 0 height givenAOD(); mKeyguardStatusHeight = EMPTY_HEIGHT; - mCutoutTopInsetPx = 300; + mCutoutTopInset = 300; // WHEN the clock position algorithm is run positionClock(); // THEN the clock Y position is below the cutout @@ -271,6 +309,155 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT); } + @Test + public void clockPositionMinimizesBurnInMovementToAvoidUdfpsOnAOD() { + // GIVEN a center aligned clock + mClockTopAligned = false; + + // GIVEN the clock + udfps are 100px apart + mClockBottom = SCREEN_HEIGHT - 500; + mUdfpsTop = SCREEN_HEIGHT - 400; + + // GIVEN it's AOD and the burn-in y value is 200 + givenAOD(); + givenMaxBurnInOffset(200); + + // WHEN the clock position algorithm is run with the highest burn in offset + givenHighestBurnInOffset(); + positionClock(); + + // THEN the worst-case clock Y position is shifted only by 100 (not the full 200), + // so that it's at the same location as mUdfpsTop + assertThat(mClockPosition.clockY).isEqualTo(100); + + // WHEN the clock position algorithm is run with the lowest burn in offset + givenLowestBurnInOffset(); + positionClock(); + + // THEN lowest case starts at 0 + assertThat(mClockPosition.clockY).isEqualTo(0); + } + + @Test + public void clockPositionShiftsToAvoidUdfpsOnAOD_usesSpaceAboveClock() { + // GIVEN a center aligned clock + mClockTopAligned = false; + + // GIVEN there's space at the top of the screen on LS (that's available to be used for + // burn-in on AOD) + mKeyguardStatusBarHeaderHeight = 150; + + // GIVEN the bottom of the clock is beyond the top of UDFPS + mClockBottom = SCREEN_HEIGHT - 300; + mUdfpsTop = SCREEN_HEIGHT - 400; + + // GIVEN it's AOD and the burn-in y value is 200 + givenAOD(); + givenMaxBurnInOffset(200); + + // WHEN the clock position algorithm is run with the highest burn in offset + givenHighestBurnInOffset(); + positionClock(); + + // THEN the algo should shift the clock up and use the area above the clock for + // burn-in since the burn in offset > space above clock + assertThat(mClockPosition.clockY).isEqualTo(mKeyguardStatusBarHeaderHeight); + + // WHEN the clock position algorithm is run with the lowest burn in offset + givenLowestBurnInOffset(); + positionClock(); + + // THEN lowest case starts at mCutoutTopInset (0 in this case) + assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset); + } + + @Test + public void clockPositionShiftsToAvoidUdfpsOnAOD_usesMaxBurnInOffset() { + // GIVEN a center aligned clock + mClockTopAligned = false; + + // GIVEN there's 200px space at the top of the screen on LS (that's available to be used for + // burn-in on AOD) but 50px are taken up by the cutout + mKeyguardStatusBarHeaderHeight = 200; + mCutoutTopInset = 50; + + // GIVEN the bottom of the clock is beyond the top of UDFPS + mClockBottom = SCREEN_HEIGHT - 300; + mUdfpsTop = SCREEN_HEIGHT - 400; + + // GIVEN it's AOD and the burn-in y value is only 25px (less than space above clock) + givenAOD(); + int maxYBurnInOffset = 25; + givenMaxBurnInOffset(maxYBurnInOffset); + + // WHEN the clock position algorithm is run with the highest burn in offset + givenHighestBurnInOffset(); + positionClock(); + + // THEN the algo should shift the clock up and use the area above the clock for + // burn-in + assertThat(mClockPosition.clockY).isEqualTo(mKeyguardStatusBarHeaderHeight); + + // WHEN the clock position algorithm is run with the lowest burn in offset + givenLowestBurnInOffset(); + positionClock(); + + // THEN lowest case starts above mKeyguardStatusBarHeaderHeight + assertThat(mClockPosition.clockY).isEqualTo( + mKeyguardStatusBarHeaderHeight - 2 * maxYBurnInOffset); + } + + @Test + public void clockPositionShiftsToMaximizeUdfpsBurnInMovement() { + // GIVEN a center aligned clock + mClockTopAligned = false; + + // GIVEN there's 200px space at the top of the screen on LS (that's available to be used for + // burn-in on AOD) but 50px are taken up by the cutout + mKeyguardStatusBarHeaderHeight = 200; + mCutoutTopInset = 50; + int upperSpaceAvailable = mKeyguardStatusBarHeaderHeight - mCutoutTopInset; + + // GIVEN the bottom of the clock and the top of UDFPS are 100px apart + mClockBottom = SCREEN_HEIGHT - 500; + mUdfpsTop = SCREEN_HEIGHT - 400; + float lowerSpaceAvailable = mUdfpsTop - mClockBottom; + + // GIVEN it's AOD and the burn-in y value is 200 + givenAOD(); + givenMaxBurnInOffset(200); + + // WHEN the clock position algorithm is run with the highest burn in offset + givenHighestBurnInOffset(); + positionClock(); + + // THEN the algo should shift the clock up and use both the area above + // the clock and below the clock (vertically centered in its allowed area) + assertThat(mClockPosition.clockY).isEqualTo( + (int) (mCutoutTopInset + upperSpaceAvailable + lowerSpaceAvailable)); + + // WHEN the clock position algorithm is run with the lowest burn in offset + givenLowestBurnInOffset(); + positionClock(); + + // THEN lowest case starts at mCutoutTopInset + assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset); + } + + private void givenHighestBurnInOffset() { + when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).then(returnsFirstArg()); + } + + private void givenLowestBurnInOffset() { + when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(0); + } + + private void givenMaxBurnInOffset(int offset) { + when(mResources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y_clock)) + .thenReturn(offset); + mClockPositionAlgorithm.loadDimens(mResources); + } + private void givenAOD() { mPanelExpansion = 1.f; mDark = 1.f; @@ -281,12 +468,28 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { mDark = 0.f; } + /** + * Setup and run the clock position algorithm. + * + * mClockPosition.clockY will contain the top y-coordinate for the clock position + */ private void positionClock() { - mClockPositionAlgorithm.setup(EMPTY_MARGIN, mPanelExpansion, mKeyguardStatusHeight, - 0 /* userSwitchHeight */, 0 /* userSwitchPreferredY */, - mDark, ZERO_DRAG, false /* bypassEnabled */, - 0 /* unlockedStackScrollerPadding */, mQsExpansion, - mCutoutTopInsetPx, mIsSplitShade); + mClockPositionAlgorithm.setup( + mKeyguardStatusBarHeaderHeight, + mPanelExpansion, + mKeyguardStatusHeight, + 0 /* userSwitchHeight */, + 0 /* userSwitchPreferredY */, + mDark, + ZERO_DRAG, + false /* bypassEnabled */, + 0 /* unlockedStackScrollerPadding */, + mQsExpansion, + mCutoutTopInset, + mIsSplitShade, + mUdfpsTop, + mClockBottom, + mClockTopAligned); mClockPositionAlgorithm.run(mClockPosition); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java index 3d887dd92be2..c62d73c98354 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java @@ -32,6 +32,7 @@ import android.view.MotionEvent; import androidx.test.filters.SmallTest; +import com.android.keyguard.LockIconViewController; import com.android.systemui.R; import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; @@ -96,6 +97,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; + @Mock private LockIconViewController mLockIconViewController; @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler> mInteractionEventHandlerCaptor; @@ -141,7 +143,8 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mNotificationPanelViewController, mStatusBarWindowView, mNotificationStackScrollLayoutController, - mStatusBarKeyguardViewManager); + mStatusBarKeyguardViewManager, + mLockIconViewController); mController.setupExpandedStatusBar(); mController.setService(mStatusBar, mNotificationShadeWindowController); mController.setDragDownHelper(mDragDownHelper); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index d63730d596d0..52a5e064f984 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -16,21 +16,39 @@ package com.android.systemui.statusbar.phone +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.FrameLayout import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.mockito.Mock import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest class PhoneStatusBarViewControllerTest : SysuiTestCase() { + private val stateChangeListener = TestStateChangedListener() + @Mock private lateinit var commandQueue: CommandQueue + @Mock + private lateinit var panelViewController: PanelViewController + @Mock + private lateinit var panelView: ViewGroup + @Mock + private lateinit var scrimController: ScrimController + + @Mock + private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController private lateinit var view: PhoneStatusBarView private lateinit var controller: PhoneStatusBarViewController @@ -38,8 +56,23 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - view = PhoneStatusBarView(mContext, null) - controller = PhoneStatusBarViewController(view, commandQueue) + `when`(panelViewController.view).thenReturn(panelView) + + // create the view on main thread as it requires main looper + InstrumentationRegistry.getInstrumentation().runOnMainSync { + val parent = FrameLayout(mContext) // add parent to keep layout params + view = LayoutInflater.from(mContext) + .inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView + view.setPanel(panelViewController) + view.setScrimController(scrimController) + } + + controller = PhoneStatusBarViewController( + view, + commandQueue, + null, + stateChangeListener + ) } @Test @@ -56,4 +89,32 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { assertThat(providerUsed).isTrue() } + + @Test + fun constructor_moveFromCenterAnimationIsNotNull_moveFromCenterAnimationInitialized() { + controller = PhoneStatusBarViewController( + view, commandQueue, moveFromCenterAnimation, stateChangeListener + ) + + verify(moveFromCenterAnimation).init(any(), any()) + } + + @Test + fun constructor_setsExpansionStateChangedListenerOnView() { + assertThat(stateChangeListener.stateChangeCalled).isFalse() + + // If the constructor correctly set the listener, then it should be used when + // [PhoneStatusBarView.panelExpansionChanged] is called. + view.panelExpansionChanged(0f, false) + + assertThat(stateChangeListener.stateChangeCalled).isTrue() + } + + private class TestStateChangedListener : PhoneStatusBarView.PanelExpansionStateChangedListener { + var stateChangeCalled: Boolean = false + + override fun onPanelExpansionStateChanged() { + stateChangeCalled = true + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt index 49ab6ebbde93..aee9f12c3844 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt @@ -16,20 +16,38 @@ package com.android.systemui.statusbar.phone +import android.view.ViewGroup import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations @SmallTest class PhoneStatusBarViewTest : SysuiTestCase() { + @Mock + private lateinit var panelViewController: PanelViewController + @Mock + private lateinit var panelView: ViewGroup + @Mock + private lateinit var scrimController: ScrimController + private lateinit var view: PhoneStatusBarView @Before fun setUp() { + MockitoAnnotations.initMocks(this) + // TODO(b/197137564): Setting up a panel view and its controller feels unnecessary when + // testing just [PhoneStatusBarView]. + `when`(panelViewController.view).thenReturn(panelView) + view = PhoneStatusBarView(mContext, null) + view.setPanel(panelViewController) + view.setScrimController(scrimController) } @Test @@ -51,4 +69,48 @@ class PhoneStatusBarViewTest : SysuiTestCase() { view.panelEnabled() // No assert needed, just testing no crash } + + @Test + fun panelExpansionChanged_fracZero_stateChangeListenerNotified() { + val listener = TestStateChangedListener() + view.setPanelExpansionStateChangedListener(listener) + + view.panelExpansionChanged(0f, false) + + assertThat(listener.stateChangeCalled).isTrue() + } + + @Test + fun panelExpansionChanged_fracOne_stateChangeListenerNotified() { + val listener = TestStateChangedListener() + view.setPanelExpansionStateChangedListener(listener) + + view.panelExpansionChanged(1f, false) + + assertThat(listener.stateChangeCalled).isTrue() + } + + @Test + fun panelExpansionChanged_fracHalf_stateChangeListenerNotNotified() { + val listener = TestStateChangedListener() + view.setPanelExpansionStateChangedListener(listener) + + view.panelExpansionChanged(0.5f, false) + + assertThat(listener.stateChangeCalled).isFalse() + } + + @Test + fun panelExpansionChanged_noStateChangeListener_noCrash() { + view.panelExpansionChanged(1f, false) + // No assert needed, just testing no crash + } + + private class TestStateChangedListener : PhoneStatusBarView.PanelExpansionStateChangedListener { + var stateChangeCalled: Boolean = false + + override fun onPanelExpansionStateChanged() { + stateChangeCalled = true + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java index 52538c7f5496..8555306bae04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java @@ -38,6 +38,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.AssistManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.DisableFlagsLogger; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; @@ -112,6 +113,7 @@ public class StatusBarCommandQueueCallbacksTest extends SysuiTestCase { mVibratorHelper, Optional.of(mVibrator), mLightBarController, + new DisableFlagsLogger(), DEFAULT_DISPLAY); when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index b23414bacf10..751bc815bdd6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -233,6 +233,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private ViewMediatorCallback mKeyguardVieMediatorCallback; @Mock private VolumeComponent mVolumeComponent; @Mock private CommandQueue mCommandQueue; + @Mock private CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger; @Mock private StatusBarComponent.Factory mStatusBarComponentFactory; @Mock private StatusBarComponent mStatusBarComponent; @Mock private PluginManager mPluginManager; @@ -254,6 +255,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private BrightnessSlider.Factory mBrightnessSliderFactory; @Mock private UnfoldTransitionConfig mUnfoldTransitionConfig; @Mock private Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealOverlayAnimationLazy; + @Mock private Lazy<StatusBarMoveFromCenterAnimationController> mMoveFromCenterAnimationLazy; @Mock private OngoingCallController mOngoingCallController; @Mock private SystemStatusAnimationScheduler mAnimationScheduler; @Mock private StatusBarLocationPublisher mLocationPublisher; @@ -403,6 +405,7 @@ public class StatusBarTest extends SysuiTestCase { mDozeScrimController, mVolumeComponent, mCommandQueue, + mCollapsedStatusBarFragmentLogger, mStatusBarComponentFactory, mPluginManager, Optional.of(mLegacySplitScreen), @@ -428,6 +431,7 @@ public class StatusBarTest extends SysuiTestCase { mBrightnessSliderFactory, mUnfoldTransitionConfig, mUnfoldLightRevealOverlayAnimationLazy, + mMoveFromCenterAnimationLazy, mOngoingCallController, mAnimationScheduler, mLocationPublisher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index cd911cc533f7..4c282bfaed93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -167,7 +167,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) .isEqualTo(new OverlayIdentifier("ffff0000")); assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR)) - .isEqualTo(new OverlayIdentifier("ff0000ff")); + .isEqualTo(new OverlayIdentifier("ffff0000")); // Should not ask again if changed to same value mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 30de4b416410..4748a86b79c6 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -566,7 +566,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind return PendingIntent.getActivityAsUser(getContext(), 0 /* request code */, NotificationAccessConfirmationActivityContract.launcherIntent( - userId, component, packageTitle), + getContext(), userId, component, packageTitle), PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT, null /* options */, diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 10071309bc0d..81627a05c9a4 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -1167,11 +1167,16 @@ final class UiModeManagerService extends SystemService { } private boolean doesPackageHaveCallingUid(@NonNull String packageName) { + int callingUid = mInjector.getCallingUid(); + int callingUserId = UserHandle.getUserId(callingUid); + final long ident = Binder.clearCallingIdentity(); try { return getContext().getPackageManager().getPackageUidAsUser(packageName, - UserHandle.getCallingUserId()) == mInjector.getCallingUid(); + callingUserId) == callingUid; } catch (PackageManager.NameNotFoundException e) { return false; + } finally { + Binder.restoreCallingIdentity(ident); } } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index f32aa2295cb8..9da6d528a967 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -157,6 +157,9 @@ public final class CachedAppOptimizer { static final int SYNC_RECEIVED_WHILE_FROZEN = 1; static final int ASYNC_RECEIVED_WHILE_FROZEN = 2; + // Bitfield values for sync transactions received by frozen binder threads + static final int TXNS_PENDING_WHILE_FROZEN = 4; + /** * This thread must be moved to the system background cpuset. * If that doesn't happen, it's probably going to draw a lot of power. @@ -611,8 +614,9 @@ public final class CachedAppOptimizer { * binder for the specificed pid. * * @throws RuntimeException in case a flush/freeze operation could not complete successfully. + * @return 0 if success, or -EAGAIN indicating there's pending transaction. */ - private static native void freezeBinder(int pid, boolean freeze); + private static native int freezeBinder(int pid, boolean freeze); /** * Retrieves binder freeze info about a process. @@ -948,7 +952,7 @@ public final class CachedAppOptimizer { int freezeInfo = getBinderFreezeInfo(pid); if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) { - Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " " + Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " received sync transactions while frozen, killing"); app.killLocked("Sync transaction while in frozen state", ApplicationExitInfo.REASON_OTHER, @@ -956,8 +960,8 @@ public final class CachedAppOptimizer { processKilled = true; } - if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) { - Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " " + if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0 && DEBUG_FREEZER) { + Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " received async transactions while frozen"); } } catch (Exception e) { @@ -1292,7 +1296,9 @@ public final class CachedAppOptimizer { public void handleMessage(Message msg) { switch (msg.what) { case SET_FROZEN_PROCESS_MSG: - freezeProcess((ProcessRecord) msg.obj); + synchronized (mAm) { + freezeProcess((ProcessRecord) msg.obj); + } break; case REPORT_UNFREEZE_MSG: int pid = msg.arg1; @@ -1306,6 +1312,15 @@ public final class CachedAppOptimizer { } } + @GuardedBy({"mAm", "mProcLock"}) + private void rescheduleFreeze(final ProcessRecord proc, final String reason) { + Slog.d(TAG_AM, "Reschedule freeze for process " + proc.getPid() + + " " + proc.processName + " (" + reason + ")"); + unfreezeAppLSP(proc); + freezeAppAsyncLSP(proc); + } + + @GuardedBy({"mAm"}) private void freezeProcess(final ProcessRecord proc) { int pid = proc.getPid(); // Unlocked intentionally final String name = proc.processName; @@ -1355,10 +1370,15 @@ public final class CachedAppOptimizer { return; } + Slog.d(TAG_AM, "freezing " + pid + " " + name); + // Freeze binder interface before the process, to flush any // transactions that might be pending. try { - freezeBinder(pid, true); + if (freezeBinder(pid, true) != 0) { + rescheduleFreeze(proc, "outstanding txns"); + return; + } } catch (RuntimeException e) { Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name); mFreezeHandler.post(() -> { @@ -1404,24 +1424,36 @@ public final class CachedAppOptimizer { try { // post-check to prevent races + int freezeInfo = getBinderFreezeInfo(pid); + + if ((freezeInfo & TXNS_PENDING_WHILE_FROZEN) != 0) { + synchronized (mProcLock) { + rescheduleFreeze(proc, "new pending txns"); + } + return; + } + } catch (RuntimeException e) { + Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name); + mFreezeHandler.post(() -> { + synchronized (mAm) { + proc.killLocked("Unable to freeze binder interface", + ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true); + } + }); + } + + try { + // post-check to prevent races if (mProcLocksReader.hasFileLocks(pid)) { if (DEBUG_FREEZER) { Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze"); } - - synchronized (mAm) { - synchronized (mProcLock) { - unfreezeAppLSP(proc); - } - } + unfreezeAppLSP(proc); } } catch (Exception e) { Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e); - synchronized (mAm) { - synchronized (mProcLock) { - unfreezeAppLSP(proc); - } - } + unfreezeAppLSP(proc); } } diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index e022e977e02f..2f20efbf5730 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -84,6 +84,7 @@ public class SettingsToPropertiesMapper { DeviceConfig.NAMESPACE_CONNECTIVITY, DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT, DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS, + DeviceConfig.NAMESPACE_LMKD_NATIVE, DeviceConfig.NAMESPACE_MEDIA_NATIVE, DeviceConfig.NAMESPACE_NETD_NATIVE, DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index b9b90c0f64ea..5d0bad23f024 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -94,6 +94,8 @@ import android.media.IPlaybackConfigDispatcher; import android.media.IRecordingConfigDispatcher; import android.media.IRingtonePlayer; import android.media.ISpatializerCallback; +import android.media.ISpatializerHeadToSoundStagePoseCallback; +import android.media.ISpatializerHeadTrackingModeCallback; import android.media.IStrategyPreferredDevicesDispatcher; import android.media.IVolumeController; import android.media.MediaMetrics; @@ -200,7 +202,8 @@ import java.util.stream.Collectors; */ public class AudioService extends IAudioService.Stub implements AccessibilityManager.TouchExplorationStateChangeListener, - AccessibilityManager.AccessibilityServicesStateChangeListener { + AccessibilityManager.AccessibilityServicesStateChangeListener, + AudioSystemAdapter.OnRoutingUpdatedListener { private static final String TAG = "AS.AudioService"; @@ -314,12 +317,14 @@ public class AudioService extends IAudioService.Stub private static final int MSG_SET_A2DP_DEV_CONNECTION_STATE = 38; private static final int MSG_A2DP_DEV_CONFIG_CHANGE = 39; private static final int MSG_DISPATCH_AUDIO_MODE = 40; + private static final int MSG_ROUTING_UPDATED = 41; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) private static final int MSG_DISABLE_AUDIO_FOR_UID = 100; private static final int MSG_INIT_STREAMS_VOLUMES = 101; + private static final int MSG_INIT_SPATIALIZER = 102; // end of messages handled under wakelock // retry delay in case of failure to indicate system ready to AudioFlinger @@ -869,6 +874,8 @@ public class AudioService extends IAudioService.Stub mSfxHelper = new SoundEffectsHelper(mContext); + mSpatializerHelper = new SpatializerHelper(this, mAudioSystem); + mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator(); @@ -1033,6 +1040,9 @@ public class AudioService extends IAudioService.Stub // done with service initialization, continue additional work in our Handler thread queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES, 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); + queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_SPATIALIZER, + 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); + } /** @@ -1222,6 +1232,22 @@ public class AudioService extends IAudioService.Stub updateVibratorInfos(); } + //----------------------------------------------------------------- + // routing monitoring from AudioSystemAdapter + @Override + public void onRoutingUpdatedFromNative() { + sendMsg(mAudioHandler, + MSG_ROUTING_UPDATED, + SENDMSG_REPLACE, 0, 0, null, + /*delay*/ 0); + } + + void monitorRoutingChanges(boolean enabled) { + mAudioSystem.setRoutingListener(enabled ? this : null); + } + + + //----------------------------------------------------------------- RoleObserver mRoleObserver; class RoleObserver implements OnRoleHoldersChangedListener { @@ -1406,6 +1432,9 @@ public class AudioService extends IAudioService.Stub } } + // TODO check property if feature enabled + mSpatializerHelper.reset(/* featureEnabled */ SPATIALIZER_FEATURE_ENABLED_DEFAULT); + onIndicateSystemReady(); // indicate the end of reconfiguration phase to audio HAL AudioSystem.setParameters("restarting=false"); @@ -7539,6 +7568,13 @@ public class AudioService extends IAudioService.Stub mAudioEventWakeLock.release(); break; + case MSG_INIT_SPATIALIZER: + mSpatializerHelper.init(); + // TODO read property to see if enabled + mSpatializerHelper.setFeatureEnabled(SPATIALIZER_FEATURE_ENABLED_DEFAULT); + mAudioEventWakeLock.release(); + break; + case MSG_CHECK_MUSIC_ACTIVE: onCheckMusicActive((String) msg.obj); break; @@ -7671,6 +7707,10 @@ public class AudioService extends IAudioService.Stub case MSG_DISPATCH_AUDIO_MODE: dispatchMode(msg.arg1); break; + + case MSG_ROUTING_UPDATED: + mSpatializerHelper.onRoutingUpdated(); + break; } } } @@ -8239,7 +8279,8 @@ public class AudioService extends IAudioService.Stub } //========================================================================================== - private final SpatializerHelper mSpatializerHelper = new SpatializerHelper(); + private final @NonNull SpatializerHelper mSpatializerHelper; + private static final boolean SPATIALIZER_FEATURE_ENABLED_DEFAULT = false; private void enforceModifyDefaultAudioEffectsPermission() { if (mContext.checkCallingOrSelfPermission( @@ -8249,9 +8290,12 @@ public class AudioService extends IAudioService.Stub } } - /** @see AudioManager#getSpatializerImmersiveAudioLevel() */ + /** + * Returns the immersive audio level that the platform is capable of + * @see Spatializer#getImmersiveAudioLevel() + */ public int getSpatializerImmersiveAudioLevel() { - return mSpatializerHelper.getImmersiveAudioLevel(); + return mSpatializerHelper.getCapableImmersiveAudioLevel(); } /** @see Spatializer#isEnabled() */ @@ -8267,7 +8311,7 @@ public class AudioService extends IAudioService.Stub /** @see Spatializer#setSpatializerEnabled(boolean) */ public void setSpatializerEnabled(boolean enabled) { enforceModifyDefaultAudioEffectsPermission(); - mSpatializerHelper.setEnabled(enabled); + mSpatializerHelper.setFeatureEnabled(enabled); } /** @see Spatializer#canBeSpatialized() */ @@ -8280,16 +8324,48 @@ public class AudioService extends IAudioService.Stub /** @see Spatializer.SpatializerInfoDispatcherStub */ public void registerSpatializerCallback( - @NonNull ISpatializerCallback dispatcher) { - Objects.requireNonNull(dispatcher); - mSpatializerHelper.registerStateCallback(dispatcher); + @NonNull ISpatializerCallback cb) { + Objects.requireNonNull(cb); + mSpatializerHelper.registerStateCallback(cb); } /** @see Spatializer.SpatializerInfoDispatcherStub */ public void unregisterSpatializerCallback( - @NonNull ISpatializerCallback dispatcher) { - Objects.requireNonNull(dispatcher); - mSpatializerHelper.unregisterStateCallback(dispatcher); + @NonNull ISpatializerCallback cb) { + Objects.requireNonNull(cb); + mSpatializerHelper.unregisterStateCallback(cb); + } + + /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */ + public void registerSpatializerHeadTrackingCallback( + @NonNull ISpatializerHeadTrackingModeCallback cb) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(cb); + mSpatializerHelper.registerHeadTrackingModeCallback(cb); + } + + /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */ + public void unregisterSpatializerHeadTrackingCallback( + @NonNull ISpatializerHeadTrackingModeCallback cb) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(cb); + mSpatializerHelper.unregisterHeadTrackingModeCallback(cb); + } + + /** @see Spatializer#setOnHeadToSoundstagePoseUpdatedListener */ + public void registerHeadToSoundstagePoseCallback( + @NonNull ISpatializerHeadToSoundStagePoseCallback cb) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(cb); + mSpatializerHelper.registerHeadToSoundstagePoseCallback(cb); + } + + /** @see Spatializer#clearOnHeadToSoundstagePoseUpdatedListener */ + public void unregisterHeadToSoundstagePoseCallback( + @NonNull ISpatializerHeadToSoundStagePoseCallback cb) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(cb); + mSpatializerHelper.unregisterHeadToSoundstagePoseCallback(cb); } /** @see Spatializer#getSpatializerCompatibleAudioDevices() */ @@ -8312,6 +8388,51 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.removeCompatibleAudioDevice(ada); } + /** @see Spatializer#getSupportedHeadTrackingModes() */ + public int[] getSupportedHeadTrackingModes() { + enforceModifyDefaultAudioEffectsPermission(); + return mSpatializerHelper.getSupportedHeadTrackingModes(); + } + + /** @see Spatializer#getHeadTrackingMode() */ + public int getActualHeadTrackingMode() { + enforceModifyDefaultAudioEffectsPermission(); + return mSpatializerHelper.getActualHeadTrackingMode(); + } + + /** @see Spatializer#getDesiredHeadTrackingMode() */ + public int getDesiredHeadTrackingMode() { + enforceModifyDefaultAudioEffectsPermission(); + return mSpatializerHelper.getDesiredHeadTrackingMode(); + } + + /** @see Spatializer#setGlobalTransform */ + public void setSpatializerGlobalTransform(@NonNull float[] transform) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(transform); + mSpatializerHelper.setGlobalTransform(transform); + } + + /** @see Spatializer#recenterHeadTracker() */ + public void recenterHeadTracker() { + enforceModifyDefaultAudioEffectsPermission(); + mSpatializerHelper.recenterHeadTracker(); + } + + /** @see Spatializer#setDesiredHeadTrackingMode */ + public void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) { + enforceModifyDefaultAudioEffectsPermission(); + switch(mode) { + case Spatializer.HEAD_TRACKING_MODE_DISABLED: + case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD: + case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE: + break; + default: + return; + } + mSpatializerHelper.setDesiredHeadTrackingMode(mode); + } + //========================================================================================== private boolean readCameraSoundForced() { return SystemProperties.getBoolean("audio.camerasound.force", false) || diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 6d567807f357..ac212eee21e6 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -17,6 +17,7 @@ package com.android.server.audio; import android.annotation.NonNull; +import android.annotation.Nullable; import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; import android.media.AudioSystem; @@ -24,6 +25,8 @@ import android.media.audiopolicy.AudioMix; import android.os.SystemClock; import android.util.Log; +import com.android.internal.annotations.GuardedBy; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -59,6 +62,9 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback { private ConcurrentHashMap<AudioAttributes, ArrayList<AudioDeviceAttributes>> mDevicesForAttrCache; private int[] mMethodCacheHit; + private static final Object sRoutingListenerLock = new Object(); + @GuardedBy("sRoutingListenerLock") + private static @Nullable OnRoutingUpdatedListener sRoutingListener; /** * should be false except when trying to debug caching errors. When true, the value retrieved @@ -76,6 +82,23 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback { Log.d(TAG, "---- onRoutingUpdated (from native) ----------"); } invalidateRoutingCache(); + final OnRoutingUpdatedListener listener; + synchronized (sRoutingListenerLock) { + listener = sRoutingListener; + } + if (listener != null) { + listener.onRoutingUpdatedFromNative(); + } + } + + interface OnRoutingUpdatedListener { + void onRoutingUpdatedFromNative(); + } + + static void setRoutingListener(@Nullable OnRoutingUpdatedListener listener) { + synchronized (sRoutingListenerLock) { + sRoutingListener = listener; + } } /** diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 708d9e10ad73..1d8597497a2c 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -17,17 +17,25 @@ package com.android.server.audio; import android.annotation.NonNull; +import android.annotation.Nullable; import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; import android.media.AudioFormat; +import android.media.AudioSystem; +import android.media.INativeSpatializerCallback; +import android.media.ISpatializer; import android.media.ISpatializerCallback; +import android.media.ISpatializerHeadToSoundStagePoseCallback; +import android.media.ISpatializerHeadTrackingModeCallback; import android.media.Spatializer; +import android.media.SpatializerHeadTrackingMode; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; import java.util.ArrayList; import java.util.List; +import java.util.Locale; /** * A helper class to manage Spatializer related functionality @@ -35,12 +43,193 @@ import java.util.List; public class SpatializerHelper { private static final String TAG = "AS.SpatializerHelper"; + private static final boolean DEBUG = true; + + private static void logd(String s) { + if (DEBUG) { + Log.i(TAG, s); + } + } + + private final @NonNull AudioSystemAdapter mASA; + private final @NonNull AudioService mAudioService; + + //------------------------------------------------------------ + // Spatializer state machine + private static final int STATE_UNINITIALIZED = 0; + private static final int STATE_NOT_SUPPORTED = 1; + private static final int STATE_DISABLED_UNAVAILABLE = 3; + private static final int STATE_ENABLED_UNAVAILABLE = 4; + private static final int STATE_ENABLED_AVAILABLE = 5; + private static final int STATE_DISABLED_AVAILABLE = 6; + private int mState = STATE_UNINITIALIZED; + + /** current level as reported by native Spatializer in callback */ + private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; + private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; + private @Nullable ISpatializer mSpat; + private @Nullable SpatializerCallback mSpatCallback; + + // default attributes and format that determine basic availability of spatialization + private static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .build(); + private static final AudioFormat DEFAULT_FORMAT = new AudioFormat.Builder() + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .setSampleRate(48000) + .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) + .build(); + // device array to store the routing for the default attributes and format, size 1 because + // media is never expected to be duplicated + private static final AudioDeviceAttributes[] ROUTING_DEVICES = new AudioDeviceAttributes[1]; //--------------------------------------------------------------- // audio device compatibility / enabled private final ArrayList<AudioDeviceAttributes> mCompatibleAudioDevices = new ArrayList<>(0); + //------------------------------------------------------ + // initialization + SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa) { + mAudioService = mother; + mASA = asa; + } + + synchronized void init() { + Log.i(TAG, "Initializing"); + if (mState != STATE_UNINITIALIZED) { + throw new IllegalStateException(("init() called in state:" + mState)); + } + // is there a spatializer? + mSpatCallback = new SpatializerCallback(); + final ISpatializer spat = AudioSystem.getSpatializer(mSpatCallback); + if (spat == null) { + Log.i(TAG, "init(): No Spatializer found"); + mState = STATE_NOT_SUPPORTED; + return; + } + // capabilities of spatializer? + try { + byte[] levels = spat.getSupportedLevels(); + if (levels == null + || levels.length == 0 + || (levels.length == 1 + && levels[0] == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE)) { + Log.e(TAG, "Spatializer is useless"); + mState = STATE_NOT_SUPPORTED; + return; + } + for (byte level : levels) { + logd("found support for level: " + level); + if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL) { + logd("Setting Spatializer to LEVEL_MULTICHANNEL"); + mCapableSpatLevel = level; + break; + } + } + } catch (RemoteException e) { + /* capable level remains at NONE*/ + } finally { + if (spat != null) { + try { + spat.release(); + } catch (RemoteException e) { /* capable level remains at NONE*/ } + } + } + if (mCapableSpatLevel == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { + mState = STATE_NOT_SUPPORTED; + return; + } + mState = STATE_DISABLED_UNAVAILABLE; + // note at this point mSpat is still not instantiated + } + + /** + * Like init() but resets the state and spatializer levels + * @param featureEnabled + */ + synchronized void reset(boolean featureEnabled) { + Log.i(TAG, "Resetting"); + mState = STATE_UNINITIALIZED; + mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; + init(); + setFeatureEnabled(featureEnabled); + } + + //------------------------------------------------------ + // routing monitoring + void onRoutingUpdated() { + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + return; + case STATE_DISABLED_UNAVAILABLE: + case STATE_ENABLED_UNAVAILABLE: + case STATE_ENABLED_AVAILABLE: + case STATE_DISABLED_AVAILABLE: + break; + } + mASA.getDevicesForAttributes(DEFAULT_ATTRIBUTES).toArray(ROUTING_DEVICES); + final boolean able = + AudioSystem.canBeSpatialized(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, ROUTING_DEVICES); + logd("onRoutingUpdated: can spatialize media 5.1:" + able + + " on device:" + ROUTING_DEVICES[0]); + setDispatchAvailableState(able); + } + + //------------------------------------------------------ + // spatializer callback from native + private final class SpatializerCallback extends INativeSpatializerCallback.Stub { + + public void onLevelChanged(byte level) { + logd("SpatializerCallback.onLevelChanged level:" + level); + synchronized (SpatializerHelper.this) { + mSpatLevel = level; + } + // TODO use reported spat level to change state + } + + public void onHeadTrackingModeChanged(byte mode) { + logd("SpatializerCallback.onHeadTrackingModeChanged mode:" + mode); + int oldMode, newMode; + synchronized (this) { + oldMode = mActualHeadTrackingMode; + mActualHeadTrackingMode = headTrackingModeTypeToSpatializerInt(mode); + newMode = mActualHeadTrackingMode; + } + if (oldMode != newMode) { + dispatchActualHeadTrackingMode(newMode); + } + } + + public void onHeadToSoundStagePoseUpdated(float[] headToStage) { + if (headToStage == null) { + Log.e(TAG, "SpatializerCallback.onHeadToStagePoseUpdated null transform"); + return; + } + if (headToStage.length != 6) { + Log.e(TAG, "SpatializerCallback.onHeadToStagePoseUpdated invalid transform length" + + headToStage.length); + return; + } + if (DEBUG) { + // 6 values * (4 digits + 1 dot + 2 brackets) = 42 characters + StringBuilder t = new StringBuilder(42); + for (float val : headToStage) { + t.append("[").append(String.format(Locale.ENGLISH, "%.3f", val)).append("]"); + } + logd("SpatializerCallback.onHeadToStagePoseUpdated headToStage:" + t); + } + dispatchPoseUpdate(headToStage); + } + }; + + //------------------------------------------------------ + // compatible devices /** * @return a shallow copy of the list of compatible audio devices */ @@ -59,37 +248,72 @@ public class SpatializerHelper { } //------------------------------------------------------ - // enabled state - - // global state of feature - boolean mFeatureEnabled = false; - // initialized state, checked after each audio_server start - boolean mInitialized = false; + // states synchronized boolean isEnabled() { - return mFeatureEnabled; + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + return false; + case STATE_ENABLED_UNAVAILABLE: + case STATE_ENABLED_AVAILABLE: + default: + return true; + } } synchronized boolean isAvailable() { - if (!mInitialized) { - return false; + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + return false; + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + default: + return true; } - // TODO check device compatibility - // ... - return true; } - synchronized void setEnabled(boolean enabled) { - final boolean oldState = mFeatureEnabled; - mFeatureEnabled = enabled; - if (oldState != enabled) { - dispatchEnabledState(); + synchronized void setFeatureEnabled(boolean enabled) { + switch (mState) { + case STATE_UNINITIALIZED: + if (enabled) { + throw(new IllegalStateException("Can't enable when uninitialized")); + } + return; + case STATE_NOT_SUPPORTED: + if (enabled) { + Log.e(TAG, "Can't enable when unsupported"); + } + return; + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + if (enabled) { + createSpat(); + break; + } else { + // already in disabled state + return; + } + case STATE_ENABLED_UNAVAILABLE: + case STATE_ENABLED_AVAILABLE: + if (!enabled) { + releaseSpat(); + break; + } else { + // already in enabled state + return; + } } + setDispatchFeatureEnabledState(enabled); } - public int getImmersiveAudioLevel() { - // TODO replace placeholder code with actual effect discovery - return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + synchronized int getCapableImmersiveAudioLevel() { + return mCapableSpatLevel; } final RemoteCallbackList<ISpatializerCallback> mStateCallbacks = @@ -105,12 +329,94 @@ public class SpatializerHelper { mStateCallbacks.unregister(callback); } - private synchronized void dispatchEnabledState() { + /** + * precondition: mState = STATE_* + * isFeatureEnabled() != featureEnabled + * @param featureEnabled + */ + private synchronized void setDispatchFeatureEnabledState(boolean featureEnabled) { + if (featureEnabled) { + switch (mState) { + case STATE_DISABLED_UNAVAILABLE: + mState = STATE_ENABLED_UNAVAILABLE; + break; + case STATE_DISABLED_AVAILABLE: + mState = STATE_ENABLED_AVAILABLE; + break; + default: + throw(new IllegalStateException("Invalid mState:" + mState + + " for enabled true")); + } + } else { + switch (mState) { + case STATE_ENABLED_UNAVAILABLE: + mState = STATE_DISABLED_UNAVAILABLE; + break; + case STATE_ENABLED_AVAILABLE: + mState = STATE_DISABLED_AVAILABLE; + break; + default: + throw (new IllegalStateException("Invalid mState:" + mState + + " for enabled false")); + } + } + final int nbCallbacks = mStateCallbacks.beginBroadcast(); + for (int i = 0; i < nbCallbacks; i++) { + try { + mStateCallbacks.getBroadcastItem(i) + .dispatchSpatializerEnabledChanged(featureEnabled); + } catch (RemoteException e) { + Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e); + } + } + mStateCallbacks.finishBroadcast(); + // TODO persist enabled state + } + + private synchronized void setDispatchAvailableState(boolean available) { + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + throw(new IllegalStateException( + "Should not update available state in state:" + mState)); + case STATE_DISABLED_UNAVAILABLE: + if (available) { + mState = STATE_DISABLED_AVAILABLE; + break; + } else { + // already in unavailable state + return; + } + case STATE_ENABLED_UNAVAILABLE: + if (available) { + mState = STATE_ENABLED_AVAILABLE; + break; + } else { + // already in unavailable state + return; + } + case STATE_DISABLED_AVAILABLE: + if (available) { + // already in available state + return; + } else { + mState = STATE_DISABLED_UNAVAILABLE; + break; + } + case STATE_ENABLED_AVAILABLE: + if (available) { + // already in available state + return; + } else { + mState = STATE_ENABLED_UNAVAILABLE; + break; + } + } final int nbCallbacks = mStateCallbacks.beginBroadcast(); for (int i = 0; i < nbCallbacks; i++) { try { mStateCallbacks.getBroadcastItem(i) - .dispatchSpatializerEnabledChanged(mFeatureEnabled); + .dispatchSpatializerAvailableChanged(available); } catch (RemoteException e) { Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e); } @@ -119,10 +425,307 @@ public class SpatializerHelper { } //------------------------------------------------------ + // native Spatializer management + + /** + * precondition: mState == STATE_DISABLED_* + */ + private void createSpat() { + if (mSpat == null) { + mSpatCallback = new SpatializerCallback(); + mSpat = AudioSystem.getSpatializer(mSpatCallback); + try { + mSpat.setLevel((byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL); + } catch (RemoteException e) { + Log.e(TAG, "Can't set spatializer level", e); + mState = STATE_NOT_SUPPORTED; + mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + } + } + } + + /** + * precondition: mState == STATE_ENABLED_* + */ + private void releaseSpat() { + if (mSpat != null) { + mSpatCallback = null; + try { + mSpat.release(); + mSpat = null; + } catch (RemoteException e) { + Log.e(TAG, "Can't set release spatializer cleanly", e); + } + } + } + + //------------------------------------------------------ // virtualization capabilities synchronized boolean canBeSpatialized( @NonNull AudioAttributes attributes, @NonNull AudioFormat format) { - // TODO hook up to spatializer effect for query - return false; + logd("canBeSpatialized usage:" + attributes.getUsage() + + " format:" + format.toLogFriendlyString()); + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + logd("canBeSpatialized false due to state:" + mState); + return false; + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + break; + } + + // filter on AudioAttributes usage + switch (attributes.getUsage()) { + case AudioAttributes.USAGE_MEDIA: + case AudioAttributes.USAGE_GAME: + break; + default: + logd("canBeSpatialized false due to usage:" + attributes.getUsage()); + return false; + } + AudioDeviceAttributes[] devices = + // going through adapter to take advantage of routing cache + (AudioDeviceAttributes[]) mASA.getDevicesForAttributes(attributes).toArray(); + final boolean able = AudioSystem.canBeSpatialized(attributes, format, devices); + logd("canBeSpatialized returning " + able); + return able; + } + + //------------------------------------------------------ + // head tracking + final RemoteCallbackList<ISpatializerHeadTrackingModeCallback> mHeadTrackingModeCallbacks = + new RemoteCallbackList<ISpatializerHeadTrackingModeCallback>(); + + synchronized void registerHeadTrackingModeCallback( + @NonNull ISpatializerHeadTrackingModeCallback callback) { + mHeadTrackingModeCallbacks.register(callback); + } + + synchronized void unregisterHeadTrackingModeCallback( + @NonNull ISpatializerHeadTrackingModeCallback callback) { + mHeadTrackingModeCallbacks.unregister(callback); + } + + synchronized int[] getSupportedHeadTrackingModes() { + switch (mState) { + case STATE_UNINITIALIZED: + return new int[0]; + case STATE_NOT_SUPPORTED: + // return an empty list when Spatializer functionality is not supported + // because the list of head tracking modes you can set is actually empty + // as defined in {@link Spatializer#getSupportedHeadTrackingModes()} + return new int[0]; + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + if (mSpat == null) { + return new int[0]; + } + break; + } + // mSpat != null + try { + final byte[] values = mSpat.getSupportedHeadTrackingModes(); + ArrayList<Integer> list = new ArrayList<>(0); + for (byte value : values) { + switch (value) { + case SpatializerHeadTrackingMode.OTHER: + case SpatializerHeadTrackingMode.DISABLED: + // not expected here, skip + break; + case SpatializerHeadTrackingMode.RELATIVE_WORLD: + case SpatializerHeadTrackingMode.RELATIVE_SCREEN: + list.add(headTrackingModeTypeToSpatializerInt(value)); + break; + default: + Log.e(TAG, "Unexpected head tracking mode:" + value, + new IllegalArgumentException("invalid mode")); + break; + } + } + int[] modes = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + modes[i] = list.get(i); + } + return modes; + } catch (RemoteException e) { + Log.e(TAG, "Error calling getSupportedHeadTrackingModes", e); + return new int[] { Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED }; + } + } + + synchronized int getActualHeadTrackingMode() { + switch (mState) { + case STATE_UNINITIALIZED: + return Spatializer.HEAD_TRACKING_MODE_DISABLED; + case STATE_NOT_SUPPORTED: + return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + if (mSpat == null) { + return Spatializer.HEAD_TRACKING_MODE_DISABLED; + } + break; + } + // mSpat != null + try { + return headTrackingModeTypeToSpatializerInt(mSpat.getActualHeadTrackingMode()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling getActualHeadTrackingMode", e); + return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; + } + } + + synchronized int getDesiredHeadTrackingMode() { + return mDesiredHeadTrackingMode; + } + + synchronized void setGlobalTransform(@NonNull float[] transform) { + if (transform.length != 6) { + throw new IllegalArgumentException("invalid array size" + transform.length); + } + if (!checkSpatForHeadTracking("setGlobalTransform")) { + return; + } + try { + mSpat.setGlobalTransform(transform); + } catch (RemoteException e) { + Log.e(TAG, "Error calling setGlobalTransform", e); + } + } + + synchronized void recenterHeadTracker() { + if (!checkSpatForHeadTracking("recenterHeadTracker")) { + return; + } + try { + mSpat.recenterHeadTracker(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling recenterHeadTracker", e); + } + } + + synchronized void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) { + if (!checkSpatForHeadTracking("setDesiredHeadTrackingMode")) { + return; + } + try { + if (mode != mDesiredHeadTrackingMode) { + mSpat.setDesiredHeadTrackingMode(spatializerIntToHeadTrackingModeType(mode)); + mDesiredHeadTrackingMode = mode; + dispatchDesiredHeadTrackingMode(mode); + } + + } catch (RemoteException e) { + Log.e(TAG, "Error calling setDesiredHeadTrackingMode", e); + } + } + + private int headTrackingModeTypeToSpatializerInt(byte mode) { + switch (mode) { + case SpatializerHeadTrackingMode.OTHER: + return Spatializer.HEAD_TRACKING_MODE_OTHER; + case SpatializerHeadTrackingMode.DISABLED: + return Spatializer.HEAD_TRACKING_MODE_DISABLED; + case SpatializerHeadTrackingMode.RELATIVE_WORLD: + return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; + case SpatializerHeadTrackingMode.RELATIVE_SCREEN: + return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE; + default: + throw(new IllegalArgumentException("Unexpected head tracking mode:" + mode)); + } + } + + private byte spatializerIntToHeadTrackingModeType(int sdkMode) { + switch (sdkMode) { + case Spatializer.HEAD_TRACKING_MODE_OTHER: + return SpatializerHeadTrackingMode.OTHER; + case Spatializer.HEAD_TRACKING_MODE_DISABLED: + return SpatializerHeadTrackingMode.DISABLED; + case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD: + return SpatializerHeadTrackingMode.RELATIVE_WORLD; + case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE: + return SpatializerHeadTrackingMode.RELATIVE_SCREEN; + default: + throw(new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode)); + } + } + + private boolean checkSpatForHeadTracking(String funcName) { + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + return false; + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + if (mSpat == null) { + throw (new IllegalStateException( + "null Spatializer when calling " + funcName)); + } + break; + } + return true; + } + + private void dispatchActualHeadTrackingMode(int newMode) { + final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast(); + for (int i = 0; i < nbCallbacks; i++) { + try { + mHeadTrackingModeCallbacks.getBroadcastItem(i) + .dispatchSpatializerActualHeadTrackingModeChanged(newMode); + } catch (RemoteException e) { + Log.e(TAG, "Error in dispatchSpatializerActualHeadTrackingModeChanged", e); + } + } + mHeadTrackingModeCallbacks.finishBroadcast(); + } + + private void dispatchDesiredHeadTrackingMode(int newMode) { + final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast(); + for (int i = 0; i < nbCallbacks; i++) { + try { + mHeadTrackingModeCallbacks.getBroadcastItem(i) + .dispatchSpatializerDesiredHeadTrackingModeChanged(newMode); + } catch (RemoteException e) { + Log.e(TAG, "Error in dispatchSpatializerDesiredHeadTrackingModeChanged", e); + } + } + mHeadTrackingModeCallbacks.finishBroadcast(); + } + + //------------------------------------------------------ + // head pose + final RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback> mHeadPoseCallbacks = + new RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback>(); + + synchronized void registerHeadToSoundstagePoseCallback( + @NonNull ISpatializerHeadToSoundStagePoseCallback callback) { + mHeadPoseCallbacks.register(callback); + } + + synchronized void unregisterHeadToSoundstagePoseCallback( + @NonNull ISpatializerHeadToSoundStagePoseCallback callback) { + mHeadPoseCallbacks.unregister(callback); + } + + private void dispatchPoseUpdate(float[] pose) { + final int nbCallbacks = mHeadPoseCallbacks.beginBroadcast(); + for (int i = 0; i < nbCallbacks; i++) { + try { + mHeadPoseCallbacks.getBroadcastItem(i) + .dispatchPoseChanged(pose); + } catch (RemoteException e) { + Log.e(TAG, "Error in dispatchPoseChanged", e); + } + } + mHeadPoseCallbacks.finishBroadcast(); } } diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index b42f8980d1c0..9c8ccd946b7f 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -47,6 +47,7 @@ import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.PromptInfo; +import android.hardware.biometrics.SensorLocationInternal; import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.face.FaceSensorProperties; import android.hardware.face.FaceSensorPropertiesInternal; @@ -766,8 +767,9 @@ public class AuthService extends SystemService { if (isUdfps && udfpsProps.length == 3) { return new FingerprintSensorPropertiesInternal(sensorId, Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser, - componentInfo, sensorType, resetLockoutRequiresHardwareAuthToken, udfpsProps[0], - udfpsProps[1], udfpsProps[2]); + componentInfo, sensorType, resetLockoutRequiresHardwareAuthToken, + List.of(new SensorLocationInternal("" /* display */, + udfpsProps[0], udfpsProps[1], udfpsProps[2]))); } else { return new FingerprintSensorPropertiesInternal(sensorId, Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser, diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 996f0fd3a55f..4f7c6b012c23 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -50,7 +50,6 @@ import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorProperties; import android.hardware.biometrics.SensorPropertiesInternal; -import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.Binder; import android.os.Build; import android.os.RemoteException; @@ -62,7 +61,6 @@ import android.util.Slog; import com.android.internal.R; import com.android.internal.widget.LockPatternUtils; -import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BaseClientMonitor; import java.util.List; @@ -541,14 +539,4 @@ public class Utils { throw new IllegalArgumentException("Unknown strength: " + strength); } } - - public static int getUdfpsAuthReason(@NonNull AuthenticationClient<?> client) { - if (client.isKeyguard()) { - return IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD; - } else if (client.isBiometricPrompt()) { - return IUdfpsOverlayController.REASON_AUTH_BP; - } else { - return IUdfpsOverlayController.REASON_AUTH_FPM_OTHER; - } - } } diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 6f38ed04cd96..85d6d7fb2f6a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -28,6 +28,7 @@ import android.content.pm.ApplicationInfo; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.BiometricOverlayConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.os.RemoteException; @@ -457,4 +458,14 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> public boolean wasAuthAttempted() { return mAuthAttempted; } + + protected int getShowOverlayReason() { + if (isKeyguard()) { + return BiometricOverlayConstants.REASON_AUTH_KEYGUARD; + } else if (isBiometricPrompt()) { + return BiometricOverlayConstants.REASON_AUTH_BP; + } else { + return BiometricOverlayConstants.REASON_AUTH_OTHER; + } + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java index 9191b8b55989..2826e0c97305 100644 --- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java @@ -19,7 +19,9 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricOverlayConstants; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.fingerprint.FingerprintManager; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; @@ -128,4 +130,15 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En public boolean interruptsPrecedingClients() { return true; } + + protected int getOverlayReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) { + switch (reason) { + case FingerprintManager.ENROLL_FIND_SENSOR: + return BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR; + case FingerprintManager.ENROLL_ENROLL: + return BiometricOverlayConstants.REASON_ENROLL_ENROLLING; + default: + return BiometricOverlayConstants.REASON_UNKNOWN; + } + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java new file mode 100644 index 000000000000..008717899aba --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.fingerprint.ISidefpsController; +import android.hardware.fingerprint.IUdfpsOverlayController; +import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; +import android.os.RemoteException; +import android.util.Slog; + +import java.util.Optional; +import java.util.function.Consumer; + +/** + * Single entry point & holder for controllers managing UI overlays for biometrics. + * + * For common operations, like {@link #show(int, int, AcquisitionClient)}, modalities are + * skipped if they are not present (provided as null via the constructor). + * + * Use the getters, such as {@link #ifUdfps(OverlayControllerConsumer)}, to get a controller for + * operations that are unique to a single modality. + */ +public final class SensorOverlays { + + private static final String TAG = "SensorOverlays"; + + @NonNull private final Optional<IUdfpsOverlayController> mUdfpsOverlayController; + @NonNull private final Optional<ISidefpsController> mSidefpsController; + + /** + * Create an overlay controller for each modality. + * + * @param udfpsOverlayController under display fps or null if not present on device + * @param sidefpsController side fps or null if not present on device + */ + public SensorOverlays( + @Nullable IUdfpsOverlayController udfpsOverlayController, + @Nullable ISidefpsController sidefpsController) { + mUdfpsOverlayController = Optional.ofNullable(udfpsOverlayController); + mSidefpsController = Optional.ofNullable(sidefpsController); + } + + /** + * Show the overlay. + * + * @param sensorId sensor id + * @param reason reason for showing + * @param client client performing operation + */ + public void show(int sensorId, @BiometricOverlayConstants.ShowReason int reason, + @NonNull AcquisitionClient<?> client) { + if (mSidefpsController.isPresent()) { + try { + mSidefpsController.get().show(sensorId, reason); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when showing the side-fps overlay", e); + } + } + + if (mUdfpsOverlayController.isPresent()) { + final IUdfpsOverlayControllerCallback callback = + new IUdfpsOverlayControllerCallback.Stub() { + @Override + public void onUserCanceled() { + client.onUserCanceled(); + } + }; + + try { + mUdfpsOverlayController.get().showUdfpsOverlay(sensorId, reason, callback); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e); + } + } + } + + /** + * Hide the overlay. + * + * @param sensorId sensor id + */ + public void hide(int sensorId) { + if (mSidefpsController.isPresent()) { + try { + mSidefpsController.get().hide(sensorId); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when hiding the side-fps overlay", e); + } + } + + if (mUdfpsOverlayController.isPresent()) { + try { + mUdfpsOverlayController.get().hideUdfpsOverlay(sensorId); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e); + } + } + } + + /** + * Use the udfps controller, if present. + * @param consumer action + */ + public void ifUdfps(OverlayControllerConsumer<IUdfpsOverlayController> consumer) { + if (mUdfpsOverlayController.isPresent()) { + try { + consumer.accept(mUdfpsOverlayController.get()); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception using overlay controller", e); + } + } + } + + /** + * Consumer for a biometric overlay controller. + * + * This behaves like a normal {@link Consumer} except that it will trap and log + * any thrown {@link RemoteException}. + * + * @param <T> the type of the input to the operation + **/ + @FunctionalInterface + public interface OverlayControllerConsumer<T> { + /** Perform the operation. */ + void accept(T t) throws RemoteException; + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/SidefpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/SidefpsHelper.java deleted file mode 100644 index 474066c227d2..000000000000 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/SidefpsHelper.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.biometrics.sensors.fingerprint; - -import android.annotation.Nullable; -import android.hardware.fingerprint.ISidefpsController; -import android.os.RemoteException; -import android.util.Slog; - -/** - * Contains helper methods for side-fps fingerprint controller. - */ -public class SidefpsHelper { - private static final String TAG = "SidefpsHelper"; - - /** - * Shows the side-fps affordance - * @param sidefpsController controller that shows and hides the side-fps affordance - */ - public static void showOverlay(@Nullable ISidefpsController sidefpsController) { - if (sidefpsController == null) { - return; - } - - try { - sidefpsController.show(); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when showing the side-fps overlay", e); - } - } - - /** - * Hides the side-fps affordance - * @param sidefpsController controller that shows and hides the side-fps affordance - */ - public static void hideOverlay(@Nullable ISidefpsController sidefpsController) { - if (sidefpsController == null) { - return; - } - try { - sidefpsController.hide(); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when hiding the side-fps overlay", e); - } - } -} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java index 879c8a0317d7..29661d46f328 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java @@ -17,17 +17,12 @@ package com.android.server.biometrics.sensors.fingerprint; import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.FingerprintManager; -import android.hardware.fingerprint.IUdfpsOverlayController; -import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; import android.os.RemoteException; import android.util.Slog; -import com.android.server.biometrics.sensors.AcquisitionClient; - /** * Contains helper methods for under-display fingerprint HIDL. */ @@ -68,88 +63,6 @@ public class UdfpsHelper { } } - public static int getReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) { - switch (reason) { - case FingerprintManager.ENROLL_FIND_SENSOR: - return IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR; - case FingerprintManager.ENROLL_ENROLL: - return IUdfpsOverlayController.REASON_ENROLL_ENROLLING; - default: - return IUdfpsOverlayController.REASON_UNKNOWN; - } - } - - public static void showUdfpsOverlay(int sensorId, int reason, - @Nullable IUdfpsOverlayController udfpsOverlayController, - @NonNull AcquisitionClient<?> client) { - if (udfpsOverlayController == null) { - return; - } - - final IUdfpsOverlayControllerCallback callback = - new IUdfpsOverlayControllerCallback.Stub() { - @Override - public void onUserCanceled() { - client.onUserCanceled(); - } - }; - - try { - udfpsOverlayController.showUdfpsOverlay(sensorId, reason, callback); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e); - } - } - - public static void hideUdfpsOverlay(int sensorId, - @Nullable IUdfpsOverlayController udfpsOverlayController) { - if (udfpsOverlayController == null) { - return; - } - try { - udfpsOverlayController.hideUdfpsOverlay(sensorId); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e); - } - } - - public static void onAcquiredGood(int sensorId, - @Nullable IUdfpsOverlayController udfpsOverlayController) { - if (udfpsOverlayController == null) { - return; - } - - try { - udfpsOverlayController.onAcquiredGood(sensorId); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when sending onAcquiredGood", e); - } - } - - public static void onEnrollmentProgress(int sensorId, int remaining, - @Nullable IUdfpsOverlayController udfpsOverlayController) { - if (udfpsOverlayController == null) { - return; - } - try { - udfpsOverlayController.onEnrollmentProgress(sensorId, remaining); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when sending onEnrollmentProgress", e); - } - } - - public static void onEnrollmentHelp(int sensorId, - @Nullable IUdfpsOverlayController udfpsOverlayController) { - if (udfpsOverlayController == null) { - return; - } - try { - udfpsOverlayController.onEnrollmentHelp(sensorId); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when sending onEnrollmentHelp", e); - } - } - public static boolean isValidAcquisitionMessage(@NonNull Context context, int acquireInfo, int vendorCode) { return FingerprintManager.getAcquiredString(context, acquireInfo, vendorCode) != null; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 9d911e0a320b..8a11e8d1cc53 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -27,20 +27,20 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.fingerprint.ISession; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; -import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.LockoutTracker; +import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.Udfps; -import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; import java.util.ArrayList; @@ -53,7 +53,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp private static final String TAG = "FingerprintAuthenticationClient"; @NonNull private final LockoutCache mLockoutCache; - @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; + @NonNull private final SensorOverlays mSensorOverlays; @NonNull private final FingerprintSensorPropertiesInternal mSensorProps; @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback; @@ -68,6 +68,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp int sensorId, boolean isStrongBiometric, int statsClient, @Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache, @Nullable IUdfpsOverlayController udfpsOverlayController, + @Nullable ISidefpsController sidefpsController, boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps) { super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner, @@ -77,7 +78,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp false /* isKeyguardBypassEnabled */); setRequestId(requestId); mLockoutCache = lockoutCache; - mUdfpsOverlayController = udfpsOverlayController; + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mSensorProps = sensorProps; mALSProbeCallback = createALSCallback(false /* startWithClient */); } @@ -120,7 +121,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp if (authenticated) { mState = STATE_STOPPED; - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); } else { mState = STATE_STARTED_PAUSED_ATTEMPTED; } @@ -131,7 +132,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp // For UDFPS, notify SysUI that the illumination can be turned off. // See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD) { - UdfpsHelper.onAcquiredGood(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.ifUdfps(controller -> controller.onAcquiredGood(getSensorId())); } super.onAcquired(acquiredInfo, vendorCode); @@ -145,27 +146,28 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp BiometricNotificationUtils.showBadCalibrationNotification(getContext()); } - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); } @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(getSensorId(), Utils.getUdfpsAuthReason(this), - mUdfpsOverlayController, this); + mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this); + try { mCancellationSignal = getFreshDaemon().authenticate(mOperationId); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } } @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); + try { mCancellationSignal.cancel(); } catch (RemoteException e) { @@ -235,7 +237,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp Slog.e(TAG, "Remote exception", e); } - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } @@ -252,7 +254,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp Slog.e(TAG, "Remote exception", e); } - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index da91cdd981b9..ac3ce896049b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.hardware.biometrics.BiometricOverlayConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.fingerprint.ISession; @@ -31,7 +32,7 @@ import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.DetectionConsumer; -import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; +import com.android.server.biometrics.sensors.SensorOverlays; /** * Performs fingerprint detection without exposing any matching information (e.g. accept/reject @@ -42,8 +43,7 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> implements Det private static final String TAG = "FingerprintDetectClient"; private final boolean mIsStrongBiometric; - @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; - + @NonNull private final SensorOverlays mSensorOverlays; @Nullable private ICancellationSignal mCancellationSignal; FingerprintDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, @@ -57,7 +57,7 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> implements Det BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient); setRequestId(requestId); mIsStrongBiometric = isStrongBiometric; - mUdfpsOverlayController = udfpsOverlayController; + mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/); } @Override @@ -68,7 +68,8 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> implements Det @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); + try { mCancellationSignal.cancel(); } catch (RemoteException e) { @@ -79,14 +80,13 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> implements Det @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(getSensorId(), - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, - mUdfpsOverlayController, this); + mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this); + try { mCancellationSignal = getFreshDaemon().detectInteraction(); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting finger detect", e); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index c420c5c57241..ccb34aad3198 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -39,8 +39,8 @@ import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.EnrollClient; +import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; -import com.android.server.biometrics.sensors.fingerprint.SidefpsHelper; import com.android.server.biometrics.sensors.fingerprint.Udfps; import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; @@ -49,8 +49,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { private static final String TAG = "FingerprintEnrollClient"; @NonNull private final FingerprintSensorPropertiesInternal mSensorProps; - @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; - @Nullable private final ISidefpsController mSidefpsController; + @NonNull private final SensorOverlays mSensorOverlays; private final @FingerprintManager.EnrollReason int mEnrollReason; @Nullable private ICancellationSignal mCancellationSignal; @@ -63,7 +62,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, @NonNull FingerprintSensorPropertiesInternal sensorProps, - @Nullable IUdfpsOverlayController udfpsOvelayController, + @Nullable IUdfpsOverlayController udfpsOverlayController, @Nullable ISidefpsController sidefpsController, int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) { // UDFPS haptics occur when an image is acquired (instead of when the result is known) @@ -71,8 +70,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { 0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId, !sensorProps.isAnyUdfpsType() /* shouldVibrate */); mSensorProps = sensorProps; - mUdfpsOverlayController = udfpsOvelayController; - mSidefpsController = sidefpsController; + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mMaxTemplatesPerUser = maxTemplatesPerUser; mEnrollReason = enrollReason; @@ -91,11 +89,11 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) { super.onEnrollResult(identifier, remaining); - UdfpsHelper.onEnrollmentProgress(getSensorId(), remaining, mUdfpsOverlayController); + mSensorOverlays.ifUdfps( + controller -> controller.onEnrollmentProgress(getSensorId(), remaining)); if (remaining == 0) { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - SidefpsHelper.hideOverlay(mSidefpsController); + mSensorOverlays.hide(getSensorId()); } } @@ -106,12 +104,14 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD && mSensorProps.isAnyUdfpsType()) { vibrateSuccess(); - UdfpsHelper.onAcquiredGood(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.ifUdfps(controller -> controller.onAcquiredGood(getSensorId())); } - if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) { - UdfpsHelper.onEnrollmentHelp(getSensorId(), mUdfpsOverlayController); - } + mSensorOverlays.ifUdfps(controller -> { + if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) { + controller.onEnrollmentHelp(getSensorId()); + } + }); super.onAcquired(acquiredInfo, vendorCode); } @@ -120,8 +120,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { public void onError(int errorCode, int vendorCode) { super.onError(errorCode, vendorCode); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - SidefpsHelper.hideOverlay(mSidefpsController); + mSensorOverlays.hide(getSensorId()); } @Override @@ -133,8 +132,8 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - SidefpsHelper.hideOverlay(mSidefpsController); + mSensorOverlays.hide(getSensorId()); + if (mCancellationSignal != null) { try { mCancellationSignal.cancel(); @@ -149,10 +148,8 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps { @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(getSensorId(), - UdfpsHelper.getReasonFromEnrollReason(mEnrollReason), - mUdfpsOverlayController, this); - SidefpsHelper.showOverlay(mSidefpsController); + mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this); + BiometricNotificationUtils.cancelBadCalibrationNotification(getContext()); try { mCancellationSignal = getFreshDaemon().enroll( diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index ca83dda3bc4e..0defc3fb6a50 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -25,10 +25,12 @@ import android.app.ActivityTaskManager; import android.app.TaskStackListener; import android.content.Context; import android.content.pm.UserInfo; +import android.content.res.TypedArray; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; +import android.hardware.biometrics.SensorLocationInternal; import android.hardware.biometrics.common.ComponentInfo; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.SensorProps; @@ -145,6 +147,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mActivityTaskManager = ActivityTaskManager.getInstance(); mTaskStackListener = new BiometricTaskStackListener(); + final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context); + for (SensorProps prop : props) { final int sensorId = prop.commonProps.sensorId; @@ -164,9 +168,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi componentInfo, prop.sensorType, true /* resetLockoutRequiresHardwareAuthToken */, - prop.sensorLocations[0].sensorLocationX, - prop.sensorLocations[0].sensorLocationY, - prop.sensorLocations[0].sensorRadius); + !workaroundLocations.isEmpty() ? workaroundLocations : + List.of(new SensorLocationInternal( + "" /* displayId */, + prop.sensorLocations[0].sensorLocationX, + prop.sensorLocations[0].sensorLocationY, + prop.sensorLocations[0].sensorRadius))); final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler, internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher); @@ -403,7 +410,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi userId, operationId, restricted, opPackageName, cookie, false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient, mTaskStackListener, mSensors.get(sensorId).getLockoutCache(), - mUdfpsOverlayController, allowBackgroundAuthentication, + mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication, mSensors.get(sensorId).getSensorProperties()); scheduleForSensor(sensorId, client, mFingerprintStateCallback); }); @@ -647,4 +654,45 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi void setTestHalEnabled(boolean enabled) { mTestHalEnabled = enabled; } + + // TODO(b/174868353): workaround for gaps in HAL interface (remove and get directly from HAL) + // reads values via an overlay instead of querying the HAL + @NonNull + private List<SensorLocationInternal> getWorkaroundSensorProps(@NonNull Context context) { + final List<SensorLocationInternal> sensorLocations = new ArrayList<>(); + + final TypedArray sfpsProps = context.getResources().obtainTypedArray( + com.android.internal.R.array.config_sfps_sensor_props); + for (int i = 0; i < sfpsProps.length(); i++) { + final int id = sfpsProps.getResourceId(i, -1); + if (id > 0) { + final SensorLocationInternal location = parseSensorLocation( + context.getResources().obtainTypedArray(id)); + if (location != null) { + sensorLocations.add(location); + } + } + } + sfpsProps.recycle(); + + return sensorLocations; + } + + @Nullable + private SensorLocationInternal parseSensorLocation(@Nullable TypedArray array) { + if (array == null) { + return null; + } + + try { + return new SensorLocationInternal( + array.getString(0), + array.getInt(1, 0), + array.getInt(2, 0), + array.getInt(3, 0)); + } catch (Exception e) { + Slog.w(getTag(), "malformed sensor location", e); + } + return null; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index d2882aa4094c..5f2f4cf6ef3c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -629,7 +629,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mContext, mLazyDaemon, token, requestId, listener, userId, operationId, restricted, opPackageName, cookie, false /* requireConfirmation */, mSensorProperties.sensorId, isStrongBiometric, statsClient, - mTaskStackListener, mLockoutTracker, mUdfpsOverlayController, + mTaskStackListener, mLockoutTracker, + mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication, mSensorProperties); mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback); }); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index 79ad8e1a5c70..dd68b4d37e2a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -426,8 +426,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage mSensorProperties = new FingerprintSensorPropertiesInternal(sensorProps.sensorId, sensorProps.sensorStrength, maxTemplatesAllowed, sensorProps.componentInfo, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, - resetLockoutRequiresHardwareAuthToken, sensorProps.sensorLocationX, - sensorProps.sensorLocationY, sensorProps.sensorRadius); + resetLockoutRequiresHardwareAuthToken, sensorProps.getAllLocations()); mMockHalResultController = controller; mUserHasTrust = new SparseBooleanArray(); mTrustManager = context.getSystemService(TrustManager.class); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java index 7d95ec098fee..3058e2508f5f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java @@ -26,16 +26,17 @@ import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.fingerprint.ISidefpsController; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; -import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutTracker; +import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.Udfps; import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; @@ -52,7 +53,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi private static final String TAG = "Biometrics/FingerprintAuthClient"; private final LockoutFrameworkImpl mLockoutFrameworkImpl; - @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; + @NonNull private final SensorOverlays mSensorOverlays; @NonNull private final FingerprintSensorPropertiesInternal mSensorProps; @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback; @@ -67,6 +68,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi @NonNull TaskStackListener taskStackListener, @NonNull LockoutFrameworkImpl lockoutTracker, @Nullable IUdfpsOverlayController udfpsOverlayController, + @Nullable ISidefpsController sidefpsController, boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps) { super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, @@ -76,7 +78,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi false /* isKeyguardBypassEnabled */); setRequestId(requestId); mLockoutFrameworkImpl = lockoutTracker; - mUdfpsOverlayController = udfpsOverlayController; + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mSensorProps = sensorProps; mALSProbeCallback = createALSCallback(false /* startWithClient */); } @@ -112,7 +114,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi if (authenticated) { mState = STATE_STOPPED; resetFailedAttempts(getTargetUserId()); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); } else { mState = STATE_STARTED_PAUSED_ATTEMPTED; final @LockoutTracker.LockoutMode int lockoutMode = @@ -125,7 +127,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi // Send the error, but do not invoke the FinishCallback yet. Since lockout is not // controlled by the HAL, the framework must stop the sensor before finishing the // client. - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */); cancel(); } @@ -140,7 +142,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi BiometricNotificationUtils.showBadCalibrationNotification(getContext()); } - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); } private void resetFailedAttempts(int userId) { @@ -168,8 +170,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(getSensorId(), Utils.getUdfpsAuthReason(this), - mUdfpsOverlayController, this); + mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this); + try { // GroupId was never used. In fact, groupId is always the same as userId. getFreshDaemon().authenticate(mOperationId, getTargetUserId()); @@ -177,14 +179,15 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi Slog.e(TAG, "Remote exception when requesting auth", e); onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } } @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); + try { getFreshDaemon().cancel(); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java index 147a20699b54..b854fb300ece 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; +import android.hardware.biometrics.BiometricOverlayConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.IUdfpsOverlayController; @@ -33,6 +34,7 @@ import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.PerformanceTracker; +import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.Udfps; import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; @@ -48,7 +50,7 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> private static final String TAG = "FingerprintDetectClient"; private final boolean mIsStrongBiometric; - @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; + @NonNull private final SensorOverlays mSensorOverlays; private boolean mIsPointerDown; public FingerprintDetectClient(@NonNull Context context, @@ -61,13 +63,14 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT, BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient); setRequestId(requestId); - mUdfpsOverlayController = udfpsOverlayController; + mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */); mIsStrongBiometric = isStrongBiometric; } @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); + try { getFreshDaemon().cancel(); } catch (RemoteException e) { @@ -86,16 +89,15 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(getSensorId(), - IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, - mUdfpsOverlayController, this); + mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this); + try { getFreshDaemon().authenticate(0 /* operationId */, getTargetUserId()); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting auth", e); onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); + mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java index dc705346f534..1ebf44ca707f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java @@ -35,7 +35,7 @@ import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.EnrollClient; -import com.android.server.biometrics.sensors.fingerprint.SidefpsHelper; +import com.android.server.biometrics.sensors.SensorOverlays; import com.android.server.biometrics.sensors.fingerprint.Udfps; import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; @@ -49,8 +49,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint private static final String TAG = "FingerprintEnrollClient"; - @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; - @Nullable private final ISidefpsController mSidefpsController; + @NonNull private final SensorOverlays mSensorOverlays; private final @FingerprintManager.EnrollReason int mEnrollReason; private boolean mIsPointerDown; @@ -65,8 +64,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId, true /* shouldVibrate */); - mUdfpsOverlayController = udfpsOverlayController; - mSidefpsController = sidefpsController; + mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mEnrollReason = enrollReason; if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) { @@ -95,10 +93,8 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint @Override protected void startHalOperation() { - UdfpsHelper.showUdfpsOverlay(getSensorId(), - UdfpsHelper.getReasonFromEnrollReason(mEnrollReason), - mUdfpsOverlayController, this); - SidefpsHelper.showOverlay(mSidefpsController); + mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this); + BiometricNotificationUtils.cancelBadCalibrationNotification(getContext()); try { // GroupId was never used. In fact, groupId is always the same as userId. @@ -107,16 +103,15 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint Slog.e(TAG, "Remote exception when requesting enroll", e); onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - SidefpsHelper.hideOverlay(mSidefpsController); + mSensorOverlays.hide(getSensorId()); mCallback.onClientFinished(this, false /* success */); } } @Override protected void stopHalOperation() { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - SidefpsHelper.hideOverlay(mSidefpsController); + mSensorOverlays.hide(getSensorId()); + try { getFreshDaemon().cancel(); } catch (RemoteException e) { @@ -131,11 +126,11 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) { super.onEnrollResult(identifier, remaining); - UdfpsHelper.onEnrollmentProgress(getSensorId(), remaining, mUdfpsOverlayController); + mSensorOverlays.ifUdfps( + controller -> controller.onEnrollmentProgress(getSensorId(), remaining)); if (remaining == 0) { - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - SidefpsHelper.hideOverlay(mSidefpsController); + mSensorOverlays.hide(getSensorId()); } } @@ -143,17 +138,18 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint public void onAcquired(int acquiredInfo, int vendorCode) { super.onAcquired(acquiredInfo, vendorCode); - if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) { - UdfpsHelper.onEnrollmentHelp(getSensorId(), mUdfpsOverlayController); - } + mSensorOverlays.ifUdfps(controller -> { + if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) { + controller.onEnrollmentHelp(getSensorId()); + } + }); } @Override public void onError(int errorCode, int vendorCode) { super.onError(errorCode, vendorCode); - UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); - SidefpsHelper.hideOverlay(mSidefpsController); + mSensorOverlays.hide(getSensorId()); } @Override diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index 0f97b9042ebe..f6ba369de769 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -36,9 +36,9 @@ import com.android.server.compat.overrides.ChangeOverrides; import com.android.server.compat.overrides.OverrideValue; import com.android.server.compat.overrides.RawOverrideValue; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * Represents the state of a single compatibility change. @@ -82,8 +82,8 @@ public final class CompatChange extends CompatibilityChangeInfo { ChangeListener mListener = null; - private Map<String, Boolean> mEvaluatedOverrides; - private Map<String, PackageOverride> mRawOverrides; + private ConcurrentHashMap<String, Boolean> mEvaluatedOverrides; + private ConcurrentHashMap<String, PackageOverride> mRawOverrides; public CompatChange(long changeId) { this(changeId, null, -1, -1, false, false, null, false); @@ -114,11 +114,11 @@ public final class CompatChange extends CompatibilityChangeInfo { description, overridable); // Initialize override maps. - mEvaluatedOverrides = new HashMap<>(); - mRawOverrides = new HashMap<>(); + mEvaluatedOverrides = new ConcurrentHashMap<>(); + mRawOverrides = new ConcurrentHashMap<>(); } - void registerListener(ChangeListener listener) { + synchronized void registerListener(ChangeListener listener) { if (mListener != null) { throw new IllegalStateException( "Listener for change " + toString() + " already registered."); @@ -131,8 +131,6 @@ public final class CompatChange extends CompatibilityChangeInfo { * Force the enabled state of this change for a given package name. The change will only take * effect after that packages process is killed and restarted. * - * <p>Note, this method is not thread safe so callers must ensure thread safety. - * * @param pname Package name to enable the change for. * @param enabled Whether or not to enable the change. */ @@ -155,14 +153,12 @@ public final class CompatChange extends CompatibilityChangeInfo { * Tentatively set the state of this change for a given package name. * The override will only take effect after that package is installed, if applicable. * - * <p>Note, this method is not thread safe so callers must ensure thread safety. - * * @param packageName Package name to tentatively enable the change for. * @param override The package override to be set * @param allowedState Whether the override is allowed. * @param versionCode The version code of the package. */ - void addPackageOverride(String packageName, PackageOverride override, + synchronized void addPackageOverride(String packageName, PackageOverride override, OverrideAllowedState allowedState, @Nullable Long versionCode) { if (getLoggingOnly()) { throw new IllegalArgumentException( @@ -185,12 +181,12 @@ public final class CompatChange extends CompatibilityChangeInfo { * @return {@code true} if the recheck yielded a result that requires invalidating caches * (a deferred override was consolidated or a regular override was removed). */ - boolean recheckOverride(String packageName, OverrideAllowedState allowedState, + synchronized boolean recheckOverride(String packageName, OverrideAllowedState allowedState, @Nullable Long versionCode) { boolean allowed = (allowedState.state == OverrideAllowedState.ALLOWED); // If the app is not installed or no longer has raw overrides, evaluate to false - if (versionCode == null || !hasRawOverride(packageName) || !allowed) { + if (versionCode == null || !mRawOverrides.containsKey(packageName) || !allowed) { removePackageOverrideInternal(packageName); return false; } @@ -211,10 +207,6 @@ public final class CompatChange extends CompatibilityChangeInfo { return true; } - boolean hasPackageOverride(String pname) { - return mRawOverrides.containsKey(pname); - } - /** * Remove any package override for the given package name, restoring the default behaviour. * @@ -224,9 +216,11 @@ public final class CompatChange extends CompatibilityChangeInfo { * @param allowedState Whether the override is allowed. * @param versionCode The version code of the package. */ - boolean removePackageOverride(String pname, OverrideAllowedState allowedState, + synchronized boolean removePackageOverride(String pname, OverrideAllowedState allowedState, @Nullable Long versionCode) { - if (mRawOverrides.remove(pname) != null) { + if (mRawOverrides.containsKey(pname)) { + allowedState.enforce(getId(), pname); + mRawOverrides.remove(pname); recheckOverride(pname, allowedState, versionCode); return true; } @@ -244,8 +238,11 @@ public final class CompatChange extends CompatibilityChangeInfo { if (app == null) { return defaultValue(); } - if (mEvaluatedOverrides.containsKey(app.packageName)) { - return mEvaluatedOverrides.get(app.packageName); + if (app.packageName != null) { + final Boolean enabled = mEvaluatedOverrides.get(app.packageName); + if (enabled != null) { + return enabled; + } } if (getDisabled()) { return false; @@ -269,9 +266,9 @@ public final class CompatChange extends CompatibilityChangeInfo { * @return {@code true} if the change should be enabled for the package. */ boolean willBeEnabled(String packageName) { - if (hasRawOverride(packageName)) { - int eval = mRawOverrides.get(packageName).evaluateForAllVersions(); - switch (eval) { + final PackageOverride override = mRawOverrides.get(packageName); + if (override != null) { + switch (override.evaluateForAllVersions()) { case VALUE_ENABLED: return true; case VALUE_DISABLED: @@ -292,30 +289,12 @@ public final class CompatChange extends CompatibilityChangeInfo { return !getDisabled(); } - /** - * Checks whether a change has an override for a package. - * @param packageName name of the package - * @return true if there is such override - */ - private boolean hasOverride(String packageName) { - return mEvaluatedOverrides.containsKey(packageName); - } - - /** - * Checks whether a change has a deferred override for a package. - * @param packageName name of the package - * @return true if there is such a deferred override - */ - private boolean hasRawOverride(String packageName) { - return mRawOverrides.containsKey(packageName); - } - - void clearOverrides() { + synchronized void clearOverrides() { mRawOverrides.clear(); mEvaluatedOverrides.clear(); } - void loadOverrides(ChangeOverrides changeOverrides) { + synchronized void loadOverrides(ChangeOverrides changeOverrides) { // Load deferred overrides for backwards compatibility if (changeOverrides.getDeferred() != null) { for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) { @@ -348,7 +327,7 @@ public final class CompatChange extends CompatibilityChangeInfo { } } - ChangeOverrides saveOverrides() { + synchronized ChangeOverrides saveOverrides() { if (mRawOverrides.isEmpty()) { return null; } @@ -406,7 +385,7 @@ public final class CompatChange extends CompatibilityChangeInfo { return sb.append(")").toString(); } - private void notifyListener(String packageName) { + private synchronized void notifyListener(String packageName) { if (mListener != null) { mListener.onCompatChange(packageName); } diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 3faffe198ac9..2bf1ccd51939 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -28,7 +28,6 @@ import android.content.pm.PackageManager; import android.os.Environment; import android.text.TextUtils; import android.util.LongArray; -import android.util.LongSparseArray; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -55,11 +54,12 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import javax.xml.datatype.DatatypeConfigurationException; @@ -76,9 +76,7 @@ final class CompatConfig { private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat"; private static final String OVERRIDES_FILE = "compat_framework_overrides.xml"; - private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock(); - @GuardedBy("mReadWriteLock") - private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>(); + private final ConcurrentHashMap<Long, CompatChange> mChanges = new ConcurrentHashMap<>(); private final OverrideValidatorImpl mOverrideValidator; private final AndroidBuildClassifier mAndroidBuildClassifier; @@ -113,21 +111,13 @@ final class CompatConfig { /** * Adds a change. * - * <p>This is intended to be used by code that reads change config from the filesystem. This - * should be done at system startup time. - * - * <p>Any change with the same ID will be overwritten. + * <p>This is intended to be used by unit tests only. * * @param change the change to add */ + @VisibleForTesting void addChange(CompatChange change) { - mReadWriteLock.writeLock().lock(); - try { - mChanges.put(change.getId(), change); - invalidateCache(); - } finally { - mReadWriteLock.writeLock().unlock(); - } + mChanges.put(change.getId(), change); } /** @@ -143,20 +133,14 @@ final class CompatConfig { */ long[] getDisabledChanges(ApplicationInfo app) { LongArray disabled = new LongArray(); - mReadWriteLock.readLock().lock(); - try { - for (int i = 0; i < mChanges.size(); ++i) { - CompatChange c = mChanges.valueAt(i); - if (!c.isEnabled(app, mAndroidBuildClassifier)) { - disabled.add(c.getId()); - } + for (CompatChange c : mChanges.values()) { + if (!c.isEnabled(app, mAndroidBuildClassifier)) { + disabled.add(c.getId()); } - } finally { - mReadWriteLock.readLock().unlock(); } - // Note: we don't need to explicitly sort the array, as the behaviour of LongSparseArray - // (mChanges) ensures it's already sorted. - return disabled.toArray(); + final long[] sortedChanges = disabled.toArray(); + Arrays.sort(sortedChanges); + return sortedChanges; } /** @@ -166,15 +150,10 @@ final class CompatConfig { * @return the change ID, or {@code -1} if no change with that name exists */ long lookupChangeId(String name) { - mReadWriteLock.readLock().lock(); - try { - for (int i = 0; i < mChanges.size(); ++i) { - if (TextUtils.equals(mChanges.valueAt(i).getName(), name)) { - return mChanges.keyAt(i); - } + for (CompatChange c : mChanges.values()) { + if (TextUtils.equals(c.getName(), name)) { + return c.getId(); } - } finally { - mReadWriteLock.readLock().unlock(); } return -1; } @@ -188,17 +167,12 @@ final class CompatConfig { * change ID is not known, as unknown changes are enabled by default. */ boolean isChangeEnabled(long changeId, ApplicationInfo app) { - mReadWriteLock.readLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - if (c == null) { - // we know nothing about this change: default behaviour is enabled. - return true; - } - return c.isEnabled(app, mAndroidBuildClassifier); - } finally { - mReadWriteLock.readLock().unlock(); + CompatChange c = mChanges.get(changeId); + if (c == null) { + // we know nothing about this change: default behaviour is enabled. + return true; } + return c.isEnabled(app, mAndroidBuildClassifier); } /** @@ -210,17 +184,12 @@ final class CompatConfig { * {@code true} if the change ID is not known, as unknown changes are enabled by default. */ boolean willChangeBeEnabled(long changeId, String packageName) { - mReadWriteLock.readLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - if (c == null) { - // we know nothing about this change: default behaviour is enabled. - return true; - } - return c.willBeEnabled(packageName); - } finally { - mReadWriteLock.readLock().unlock(); + CompatChange c = mChanges.get(changeId); + if (c == null) { + // we know nothing about this change: default behaviour is enabled. + return true; } + return c.willBeEnabled(packageName); } /** @@ -239,7 +208,7 @@ final class CompatConfig { * @return {@code true} if the change existed before adding the override * @throws IllegalStateException if overriding is not allowed */ - boolean addOverride(long changeId, String packageName, boolean enabled) { + synchronized boolean addOverride(long changeId, String packageName, boolean enabled) { boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, new PackageOverride.Builder().setEnabled(enabled).build()); saveOverrides(); @@ -250,12 +219,11 @@ final class CompatConfig { /** * Overrides the enabled state for a given change and app. * - * <p>Note, package overrides are not persistent and will be lost on system or runtime restart. * * @param overrides list of overrides to default changes config. * @param packageName app for which the overrides will be applied. */ - void addOverrides(CompatibilityOverrideConfig overrides, String packageName) { + synchronized void addOverrides(CompatibilityOverrideConfig overrides, String packageName) { for (Long changeId : overrides.overrides.keySet()) { addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId)); } @@ -265,36 +233,24 @@ final class CompatConfig { private boolean addOverrideUnsafe(long changeId, String packageName, PackageOverride overrides) { - boolean alreadyKnown = true; + final AtomicBoolean alreadyKnown = new AtomicBoolean(true); OverrideAllowedState allowedState = mOverrideValidator.getOverrideAllowedState(changeId, packageName); allowedState.enforce(changeId, packageName); Long versionCode = getVersionCodeOrNull(packageName); - mReadWriteLock.writeLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - if (c == null) { - alreadyKnown = false; - c = new CompatChange(changeId); - addChange(c); - } - c.addPackageOverride(packageName, overrides, allowedState, versionCode); - invalidateCache(); - } finally { - mReadWriteLock.writeLock().unlock(); - } - return alreadyKnown; + + final CompatChange c = mChanges.computeIfAbsent(changeId, (key) -> { + alreadyKnown.set(false); + return new CompatChange(changeId); + }); + c.addPackageOverride(packageName, overrides, allowedState, versionCode); + invalidateCache(); + return alreadyKnown.get(); } /** Checks whether the change is known to the compat config. */ boolean isKnownChangeId(long changeId) { - mReadWriteLock.readLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - return c != null; - } finally { - mReadWriteLock.readLock().unlock(); - } + return mChanges.containsKey(changeId); } /** @@ -302,55 +258,35 @@ final class CompatConfig { * target SDK gated). */ int maxTargetSdkForChangeIdOptIn(long changeId) { - mReadWriteLock.readLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - if (c != null && c.getEnableSinceTargetSdk() != -1) { - return c.getEnableSinceTargetSdk() - 1; - } - return -1; - } finally { - mReadWriteLock.readLock().unlock(); + CompatChange c = mChanges.get(changeId); + if (c != null && c.getEnableSinceTargetSdk() != -1) { + return c.getEnableSinceTargetSdk() - 1; } + return -1; } /** * Returns whether the change is marked as logging only. */ boolean isLoggingOnly(long changeId) { - mReadWriteLock.readLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - return c != null && c.getLoggingOnly(); - } finally { - mReadWriteLock.readLock().unlock(); - } + CompatChange c = mChanges.get(changeId); + return c != null && c.getLoggingOnly(); } /** * Returns whether the change is marked as disabled. */ boolean isDisabled(long changeId) { - mReadWriteLock.readLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - return c != null && c.getDisabled(); - } finally { - mReadWriteLock.readLock().unlock(); - } + CompatChange c = mChanges.get(changeId); + return c != null && c.getDisabled(); } /** * Returns whether the change is overridable. */ boolean isOverridable(long changeId) { - mReadWriteLock.readLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - return c != null && c.getOverridable(); - } finally { - mReadWriteLock.readLock().unlock(); - } + CompatChange c = mChanges.get(changeId); + return c != null && c.getOverridable(); } /** @@ -363,10 +299,12 @@ final class CompatConfig { * @param packageName the app package name that was overridden * @return {@code true} if an override existed; */ - boolean removeOverride(long changeId, String packageName) { + synchronized boolean removeOverride(long changeId, String packageName) { boolean overrideExists = removeOverrideUnsafe(changeId, packageName); - saveOverrides(); - invalidateCache(); + if (overrideExists) { + saveOverrides(); + invalidateCache(); + } return overrideExists; } @@ -376,14 +314,9 @@ final class CompatConfig { */ private boolean removeOverrideUnsafe(long changeId, String packageName) { Long versionCode = getVersionCodeOrNull(packageName); - mReadWriteLock.writeLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - if (c != null) { - return removeOverrideUnsafe(c, packageName, versionCode); - } - } finally { - mReadWriteLock.writeLock().unlock(); + CompatChange c = mChanges.get(changeId); + if (c != null) { + return removeOverrideUnsafe(c, packageName, versionCode); } return false; } @@ -397,13 +330,7 @@ final class CompatConfig { long changeId = change.getId(); OverrideAllowedState allowedState = mOverrideValidator.getOverrideAllowedState(changeId, packageName); - if (change.hasPackageOverride(packageName)) { - allowedState.enforce(changeId, packageName); - change.removePackageOverride(packageName, allowedState, versionCode); - invalidateCache(); - return true; - } - return false; + return change.removePackageOverride(packageName, allowedState, versionCode); } /** @@ -414,19 +341,16 @@ final class CompatConfig { * * @param packageName the package for which the overrides should be purged */ - void removePackageOverrides(String packageName) { + synchronized void removePackageOverrides(String packageName) { Long versionCode = getVersionCodeOrNull(packageName); - mReadWriteLock.writeLock().lock(); - try { - for (int i = 0; i < mChanges.size(); ++i) { - CompatChange change = mChanges.valueAt(i); - removeOverrideUnsafe(change, packageName, versionCode); - } - } finally { - mReadWriteLock.writeLock().unlock(); + boolean shouldInvalidateCache = false; + for (CompatChange change : mChanges.values()) { + shouldInvalidateCache |= removeOverrideUnsafe(change, packageName, versionCode); + } + if (shouldInvalidateCache) { + saveOverrides(); + invalidateCache(); } - saveOverrides(); - invalidateCache(); } /** @@ -439,34 +363,31 @@ final class CompatConfig { * @param overridesToRemove list of change IDs for which to restore the default behaviour. * @param packageName the package for which the overrides should be purged */ - void removePackageOverrides(CompatibilityOverridesToRemoveConfig overridesToRemove, + synchronized void removePackageOverrides(CompatibilityOverridesToRemoveConfig overridesToRemove, String packageName) { + boolean shouldInvalidateCache = false; for (Long changeId : overridesToRemove.changeIds) { - removeOverrideUnsafe(changeId, packageName); + shouldInvalidateCache |= removeOverrideUnsafe(changeId, packageName); + } + if (shouldInvalidateCache) { + saveOverrides(); + invalidateCache(); } - saveOverrides(); - invalidateCache(); } private long[] getAllowedChangesSinceTargetSdkForPackage(String packageName, int targetSdkVersion) { LongArray allowed = new LongArray(); - mReadWriteLock.readLock().lock(); - try { - for (int i = 0; i < mChanges.size(); ++i) { - CompatChange change = mChanges.valueAt(i); - if (change.getEnableSinceTargetSdk() != targetSdkVersion) { - continue; - } - OverrideAllowedState allowedState = - mOverrideValidator.getOverrideAllowedState(change.getId(), - packageName); - if (allowedState.state == OverrideAllowedState.ALLOWED) { - allowed.add(change.getId()); - } + for (CompatChange change : mChanges.values()) { + if (change.getEnableSinceTargetSdk() != targetSdkVersion) { + continue; + } + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedState(change.getId(), + packageName); + if (allowedState.state == OverrideAllowedState.ALLOWED) { + allowed.add(change.getId()); } - } finally { - mReadWriteLock.readLock().unlock(); } return allowed.toArray(); } @@ -479,12 +400,15 @@ final class CompatConfig { */ int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) { long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion); + boolean shouldInvalidateCache = false; for (long changeId : changes) { - addOverrideUnsafe(changeId, packageName, + shouldInvalidateCache |= addOverrideUnsafe(changeId, packageName, new PackageOverride.Builder().setEnabled(true).build()); } - saveOverrides(); - invalidateCache(); + if (shouldInvalidateCache) { + saveOverrides(); + invalidateCache(); + } return changes.length; } @@ -496,30 +420,27 @@ final class CompatConfig { */ int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) { long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion); + boolean shouldInvalidateCache = false; for (long changeId : changes) { - addOverrideUnsafe(changeId, packageName, + shouldInvalidateCache |= addOverrideUnsafe(changeId, packageName, new PackageOverride.Builder().setEnabled(false).build()); } - saveOverrides(); - invalidateCache(); + if (shouldInvalidateCache) { + saveOverrides(); + invalidateCache(); + } return changes.length; } boolean registerListener(long changeId, CompatChange.ChangeListener listener) { - boolean alreadyKnown = true; - mReadWriteLock.writeLock().lock(); - try { - CompatChange c = mChanges.get(changeId); - if (c == null) { - alreadyKnown = false; - c = new CompatChange(changeId); - addChange(c); - } - c.registerListener(listener); - } finally { - mReadWriteLock.writeLock().unlock(); - } - return alreadyKnown; + final AtomicBoolean alreadyKnown = new AtomicBoolean(true); + final CompatChange c = mChanges.computeIfAbsent(changeId, (key) -> { + alreadyKnown.set(false); + invalidateCache(); + return new CompatChange(changeId); + }); + c.registerListener(listener); + return alreadyKnown.get(); } boolean defaultChangeIdValue(long changeId) { @@ -537,12 +458,7 @@ final class CompatConfig { @VisibleForTesting void clearChanges() { - mReadWriteLock.writeLock().lock(); - try { - mChanges.clear(); - } finally { - mReadWriteLock.writeLock().unlock(); - } + mChanges.clear(); } /** @@ -551,18 +467,12 @@ final class CompatConfig { * @param pw {@link PrintWriter} instance to which the information will be dumped */ void dumpConfig(PrintWriter pw) { - mReadWriteLock.readLock().lock(); - try { - if (mChanges.size() == 0) { - pw.println("No compat overrides."); - return; - } - for (int i = 0; i < mChanges.size(); ++i) { - CompatChange c = mChanges.valueAt(i); - pw.println(c.toString()); - } - } finally { - mReadWriteLock.readLock().unlock(); + if (mChanges.size() == 0) { + pw.println("No compat overrides."); + return; + } + for (CompatChange c : mChanges.values()) { + pw.println(c.toString()); } } @@ -574,18 +484,12 @@ final class CompatConfig { CompatibilityChangeConfig getAppConfig(ApplicationInfo applicationInfo) { Set<Long> enabled = new HashSet<>(); Set<Long> disabled = new HashSet<>(); - mReadWriteLock.readLock().lock(); - try { - for (int i = 0; i < mChanges.size(); ++i) { - CompatChange c = mChanges.valueAt(i); - if (c.isEnabled(applicationInfo, mAndroidBuildClassifier)) { - enabled.add(c.getId()); - } else { - disabled.add(c.getId()); - } + for (CompatChange c : mChanges.values()) { + if (c.isEnabled(applicationInfo, mAndroidBuildClassifier)) { + enabled.add(c.getId()); + } else { + disabled.add(c.getId()); } - } finally { - mReadWriteLock.readLock().unlock(); } return new CompatibilityChangeConfig(new ChangeConfig(enabled, disabled)); } @@ -596,17 +500,12 @@ final class CompatConfig { * @return an array of {@link CompatibilityChangeInfo} with the current changes */ CompatibilityChangeInfo[] dumpChanges() { - mReadWriteLock.readLock().lock(); - try { - CompatibilityChangeInfo[] changeInfos = new CompatibilityChangeInfo[mChanges.size()]; - for (int i = 0; i < mChanges.size(); ++i) { - CompatChange change = mChanges.valueAt(i); - changeInfos[i] = new CompatibilityChangeInfo(change); - } - return changeInfos; - } finally { - mReadWriteLock.readLock().unlock(); + CompatibilityChangeInfo[] changeInfos = new CompatibilityChangeInfo[mChanges.size()]; + int i = 0; + for (CompatChange change : mChanges.values()) { + changeInfos[i++] = new CompatibilityChangeInfo(change); } + return changeInfos; } void initConfigFromLib(File libraryDir) { @@ -626,10 +525,12 @@ final class CompatConfig { Config config = com.android.server.compat.config.XmlParser.read(in); for (Change change : config.getCompatChange()) { Slog.d(TAG, "Adding: " + change.toString()); - addChange(new CompatChange(change)); + mChanges.put(change.getId(), new CompatChange(change)); } } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { Slog.e(TAG, "Encountered an error while reading/parsing compat config file", e); + } finally { + invalidateCache(); } } @@ -641,15 +542,12 @@ final class CompatConfig { @VisibleForTesting void initOverrides(File dynamicOverridesFile, File staticOverridesFile) { // Clear overrides from all changes before loading. - mReadWriteLock.writeLock().lock(); - try { - for (int i = 0; i < mChanges.size(); ++i) { - mChanges.valueAt(i).clearOverrides(); - } - } finally { - mReadWriteLock.writeLock().unlock(); + + for (CompatChange c : mChanges.values()) { + c.clearOverrides(); } + loadOverrides(staticOverridesFile); mOverridesFile = dynamicOverridesFile; @@ -698,18 +596,12 @@ final class CompatConfig { } synchronized (mOverridesFile) { Overrides overrides = new Overrides(); - mReadWriteLock.readLock().lock(); - try { - List<ChangeOverrides> changeOverridesList = overrides.getChangeOverrides(); - for (int idx = 0; idx < mChanges.size(); ++idx) { - CompatChange c = mChanges.valueAt(idx); - ChangeOverrides changeOverrides = c.saveOverrides(); - if (changeOverrides != null) { - changeOverridesList.add(changeOverrides); - } + List<ChangeOverrides> changeOverridesList = overrides.getChangeOverrides(); + for (CompatChange c : mChanges.values()) { + ChangeOverrides changeOverrides = c.saveOverrides(); + if (changeOverrides != null) { + changeOverridesList.add(changeOverrides); } - } finally { - mReadWriteLock.readLock().unlock(); } // Create the file if it doesn't already exist try { @@ -741,20 +633,11 @@ final class CompatConfig { void recheckOverrides(String packageName) { Long versionCode = getVersionCodeOrNull(packageName); boolean shouldInvalidateCache = false; - mReadWriteLock.readLock().lock(); - try { - for (int idx = 0; idx < mChanges.size(); ++idx) { - CompatChange c = mChanges.valueAt(idx); - if (!c.hasPackageOverride(packageName)) { - continue; - } - OverrideAllowedState allowedState = - mOverrideValidator.getOverrideAllowedStateForRecheck(c.getId(), - packageName); - shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, versionCode); - } - } finally { - mReadWriteLock.readLock().unlock(); + for (CompatChange c : mChanges.values()) { + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedStateForRecheck(c.getId(), + packageName); + shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, versionCode); } if (shouldInvalidateCache) { invalidateCache(); diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 806a5dd65a13..792feea01e27 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -31,6 +31,7 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.devicestate.DeviceStateInfo; import android.hardware.devicestate.DeviceStateManager; +import android.hardware.devicestate.DeviceStateManagerInternal; import android.hardware.devicestate.IDeviceStateManager; import android.hardware.devicestate.IDeviceStateManagerCallback; import android.os.Binder; @@ -161,6 +162,7 @@ public final class DeviceStateManagerService extends SystemService { @Override public void onStart() { publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService); + publishLocalService(DeviceStateManagerInternal.class, new LocalService()); } @VisibleForTesting @@ -240,13 +242,6 @@ public final class DeviceStateManagerService extends SystemService { } /** Returns the list of currently supported device state identifiers. */ - private int[] getSupportedStateIdentifiers() { - synchronized (mLock) { - return getSupportedStateIdentifiersLocked(); - } - } - - /** Returns the list of currently supported device state identifiers. */ private int[] getSupportedStateIdentifiersLocked() { int[] supportedStates = new int[mDeviceStates.size()]; for (int i = 0; i < supportedStates.length; i++) { @@ -848,4 +843,14 @@ public final class DeviceStateManagerService extends SystemService { } } } + + /** Implementation of {@link DeviceStateManagerInternal} published as a local service. */ + private final class LocalService extends DeviceStateManagerInternal { + @Override + public int[] getSupportedStateIdentifiers() { + synchronized (mLock) { + return getSupportedStateIdentifiersLocked(); + } + } + } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 73bcea6de115..f16ed41af5ca 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -53,6 +53,7 @@ import android.graphics.Point; import android.hardware.Sensor; import android.hardware.SensorManager; import android.hardware.devicestate.DeviceStateManager; +import android.hardware.devicestate.DeviceStateManagerInternal; import android.hardware.display.AmbientBrightnessDayStats; import android.hardware.display.BrightnessChangeEvent; import android.hardware.display.BrightnessConfiguration; @@ -131,6 +132,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; @@ -210,6 +212,7 @@ public final class DisplayManagerService extends SystemService { private WindowManagerInternal mWindowManagerInternal; private InputManagerInternal mInputManagerInternal; private IMediaProjectionManager mProjectionService; + private DeviceStateManagerInternal mDeviceStateManager; private int[] mUserDisabledHdrTypes = {}; private boolean mAreUserDisabledHdrTypesAllowed = true; @@ -557,10 +560,9 @@ public final class DisplayManagerService extends SystemService { mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); - DeviceStateManager deviceStateManager = - mContext.getSystemService(DeviceStateManager.class); - deviceStateManager.registerCallback(new HandlerExecutor(mHandler), - new DeviceStateListener()); + mDeviceStateManager = LocalServices.getService(DeviceStateManagerInternal.class); + mContext.getSystemService(DeviceStateManager.class).registerCallback( + new HandlerExecutor(mHandler), new DeviceStateListener()); scheduleTraversalLocked(false); } @@ -2252,6 +2254,9 @@ public final class DisplayManagerService extends SystemService { int displayId = msg.arg1; final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); + if (display == null) { + break; + } uids = display.getPendingFrameRateOverrideUids(); display.clearPendingFrameRateOverrideUids(); } @@ -3271,6 +3276,53 @@ public final class DisplayManagerService extends SystemService { } @Override + public Set<DisplayInfo> getPossibleDisplayInfo(int displayId) { + synchronized (mSyncRoot) { + // Retrieve the group associated with this display id. + final int displayGroupId = + mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(displayId); + if (displayGroupId == Display.INVALID_DISPLAY_GROUP) { + Slog.w(TAG, + "Can't get possible display info since display group for " + displayId + + " does not exist"); + return new ArraySet<>(); + } + + // Assume any display in this group can be swapped out for the given display id. + Set<DisplayInfo> possibleInfo = new ArraySet<>(); + final DisplayGroup group = mLogicalDisplayMapper.getDisplayGroupLocked( + displayGroupId); + for (int i = 0; i < group.getSizeLocked(); i++) { + final int id = group.getIdLocked(i); + final LogicalDisplay logical = mLogicalDisplayMapper.getDisplayLocked(id); + if (logical == null) { + Slog.w(TAG, + "Can't get possible display info since logical display for " + + "display id " + id + " does not exist, as part of group " + + displayGroupId); + } else { + possibleInfo.add(logical.getDisplayInfoLocked()); + } + } + + // For the supported device states, retrieve the DisplayInfos for the logical + // display layout. + if (mDeviceStateManager == null) { + Slog.w(TAG, "Can't get supported states since DeviceStateManager not ready"); + } else { + final int[] supportedStates = + mDeviceStateManager.getSupportedStateIdentifiers(); + for (int state : supportedStates) { + possibleInfo.addAll( + mLogicalDisplayMapper.getDisplayInfoForStateLocked(state, displayId, + displayGroupId)); + } + } + return possibleInfo; + } + } + + @Override public Point getDisplayPosition(int displayId) { synchronized (mSyncRoot) { final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId); diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index a93171835365..973dcc4c79e5 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -24,6 +24,7 @@ import android.os.Looper; import android.os.Message; import android.os.SystemProperties; import android.text.TextUtils; +import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; @@ -38,6 +39,7 @@ import com.android.server.display.layout.Layout; import java.io.PrintWriter; import java.util.Arrays; +import java.util.Set; import java.util.function.Consumer; /** @@ -254,6 +256,61 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return mDisplayGroups.get(groupId); } + /** + * Returns the set of {@link DisplayInfo} for this device state, only fetching the info that is + * part of the same display group as the provided display id. The DisplayInfo represent the + * logical display layouts possible for the given device state. + * + * @param deviceState the state to query possible layouts for + * @param displayId the display id to apply to all displays within the group + * @param groupId the display group to filter display info for. Must be the same group as + * the display with the provided display id. + */ + public Set<DisplayInfo> getDisplayInfoForStateLocked(int deviceState, int displayId, + int groupId) { + Set<DisplayInfo> displayInfos = new ArraySet<>(); + final Layout layout = mDeviceStateToLayoutMap.get(deviceState); + final int layoutSize = layout.size(); + for (int i = 0; i < layoutSize; i++) { + Layout.Display displayLayout = layout.getAt(i); + if (displayLayout == null) { + continue; + } + + // If the underlying display-device we want to use for this display + // doesn't exist, then skip it. This can happen at startup as display-devices + // trickle in one at a time. When the new display finally shows up, the layout is + // recalculated so that the display is properly added to the current layout. + final DisplayAddress address = displayLayout.getAddress(); + final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address); + if (device == null) { + Slog.w(TAG, "The display device (" + address + "), is not available" + + " for the display state " + deviceState); + continue; + } + + // Find or create the LogicalDisplay to map the DisplayDevice to. + final int logicalDisplayId = displayLayout.getLogicalDisplayId(); + final LogicalDisplay logicalDisplay = getDisplayLocked(logicalDisplayId); + if (logicalDisplay == null) { + Slog.w(TAG, "The logical display (" + address + "), is not available" + + " for the display state " + deviceState); + continue; + } + final DisplayInfo temp = logicalDisplay.getDisplayInfoLocked(); + DisplayInfo displayInfo = new DisplayInfo(temp); + if (displayInfo.displayGroupId != groupId) { + // Ignore any displays not in the provided group. + continue; + } + // A display in the same group can be swapped out at any point, so set the display id + // for all results to the provided display id. + displayInfo.displayId = displayId; + displayInfos.add(displayInfo); + } + return displayInfos; + } + public void dumpLocked(PrintWriter pw) { pw.println("LogicalDisplayMapper:"); IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 688a3b2f1d59..8c7d257d271b 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -31,6 +31,8 @@ import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING; import static android.os.PowerManagerInternal.wakefulnessToString; +import static com.android.internal.util.LatencyTracker.ACTION_TURN_ON_SCREEN; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -104,6 +106,7 @@ import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; +import com.android.internal.util.LatencyTracker; import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; import com.android.server.LockGuard; @@ -1842,6 +1845,9 @@ public final class PowerManagerService extends SystemService + ", details=" + details + ")..."); Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, groupId); + // The instrument will be timed out automatically after 2 seconds. + LatencyTracker.getInstance(mContext) + .onActionStart(ACTION_TURN_ON_SCREEN, String.valueOf(groupId)); setWakefulnessLocked(groupId, WAKEFULNESS_AWAKE, eventTime, uid, reason, opUid, opPackageName, details); @@ -3225,6 +3231,7 @@ public final class PowerManagerService extends SystemService && mDisplayGroupPowerStateMapper.getWakefulnessLocked( groupId) == WAKEFULNESS_AWAKE) { mDisplayGroupPowerStateMapper.setPoweringOnLocked(groupId, false); + LatencyTracker.getInstance(mContext).onActionEnd(ACTION_TURN_ON_SCREEN); Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, groupId); final int latencyMs = (int) (mClock.uptimeMillis() - mDisplayGroupPowerStateMapper.getLastPowerOnTimeLocked(groupId)); diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index 382398a210bb..e0cc8e182079 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -352,7 +352,7 @@ public class Vcn extends Handler { } private void handleSafeModeStatusChanged() { - logDbg("VcnGatewayConnection safe mode status changed"); + logVdbg("VcnGatewayConnection safe mode status changed"); boolean hasSafeModeGatewayConnection = false; // If any VcnGatewayConnection is in safe mode, mark the entire VCN as being in safe mode @@ -368,7 +368,7 @@ public class Vcn extends Handler { hasSafeModeGatewayConnection ? VCN_STATUS_CODE_SAFE_MODE : VCN_STATUS_CODE_ACTIVE; if (oldStatus != mCurrentStatus) { mVcnCallback.onSafeModeStatusChanged(hasSafeModeGatewayConnection); - logDbg( + logInfo( "Safe mode " + (mCurrentStatus == VCN_STATUS_CODE_SAFE_MODE ? "entered" : "exited")); } @@ -539,6 +539,16 @@ public class Vcn extends Handler { Slog.d(TAG, getLogPrefix() + msg, tr); } + private void logInfo(String msg) { + Slog.i(TAG, getLogPrefix() + msg); + LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg); + } + + private void logInfo(String msg, Throwable tr) { + Slog.i(TAG, getLogPrefix() + msg, tr); + LOCAL_LOG.log(getLogPrefix() + "INFO: " + msg + tr); + } + private void logErr(String msg) { Slog.e(TAG, getLogPrefix() + msg); LOCAL_LOG.log(getLogPrefix() + "ERR: " + msg); diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 450257fcdecb..7dec4e785f5c 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -1677,10 +1677,8 @@ public class VcnGatewayConnection extends StateMachine { mFailedAttempts = 0; cancelSafeModeAlarm(); - if (mIsInSafeMode) { - mIsInSafeMode = false; - mGatewayStatusCallback.onSafeModeStatusChanged(); - } + mIsInSafeMode = false; + mGatewayStatusCallback.onSafeModeStatusChanged(); } protected void applyTransform( diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index cea30ed3e299..e59c82cfb6d0 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1701,7 +1701,7 @@ final class AccessibilityController { boolean focusedWindowAdded = false; final int visibleWindowCount = visibleWindows.size(); - HashSet<Integer> skipRemainingWindowsForTasks = new HashSet<>(); + ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments = new ArrayList<>(); ArrayList<ShellRoot> shellRoots = getSortedShellRoots(dc.mShellRoots); @@ -1723,10 +1723,10 @@ final class AccessibilityController { computeWindowRegionInScreen(windowState, regionInScreen); if (windowMattersToAccessibility(windowState, regionInScreen, unaccountedSpace, - skipRemainingWindowsForTasks)) { + skipRemainingWindowsForTaskFragments)) { addPopulatedWindowInfo(windowState, regionInScreen, windows, addedWindows); updateUnaccountedSpace(windowState, regionInScreen, unaccountedSpace, - skipRemainingWindowsForTasks); + skipRemainingWindowsForTaskFragments); focusedWindowAdded |= windowState.isFocused(); } else if (isUntouchableNavigationBar(windowState, mTempRegion1)) { // If this widow is navigation bar without touchable region, accounting the @@ -1782,7 +1782,7 @@ final class AccessibilityController { private boolean windowMattersToAccessibility(WindowState windowState, Region regionInScreen, Region unaccountedSpace, - HashSet<Integer> skipRemainingWindowsForTasks) { + ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments) { final RecentsAnimationController controller = mService.getRecentsAnimationController(); if (controller != null && controller.shouldIgnoreForAccessibility(windowState)) { return false; @@ -1793,8 +1793,9 @@ final class AccessibilityController { } // If the window is part of a task that we're finished with - ignore. - final Task task = windowState.getTask(); - if (task != null && skipRemainingWindowsForTasks.contains(task.mTaskId)) { + final TaskFragment taskFragment = windowState.getTaskFragment(); + if (taskFragment != null + && skipRemainingWindowsForTaskFragments.contains(taskFragment)) { return false; } @@ -1820,7 +1821,8 @@ final class AccessibilityController { } private void updateUnaccountedSpace(WindowState windowState, Region regionInScreen, - Region unaccountedSpace, HashSet<Integer> skipRemainingWindowsForTasks) { + Region unaccountedSpace, + ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments) { if (windowState.mAttrs.type != WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) { @@ -1850,11 +1852,11 @@ final class AccessibilityController { Region.Op.REVERSE_DIFFERENCE); } - final Task task = windowState.getTask(); - if (task != null) { + final TaskFragment taskFragment = windowState.getTaskFragment(); + if (taskFragment != null) { // If the window is associated with a particular task, we can skip the // rest of the windows for that task. - skipRemainingWindowsForTasks.add(task.mTaskId); + skipRemainingWindowsForTaskFragments.add(taskFragment); } else if (!windowState.hasTapExcludeRegion()) { // If the window is not associated with a particular task, then it is // globally modal. In this case we can skip all remaining windows when diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 605cb8a088e7..dfb2f8ff8bc9 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1389,6 +1389,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return parent != null ? parent.asTaskFragment() : null; } + /** Whether we should prepare a transition for this {@link ActivityRecord} parent change. */ + private boolean shouldStartChangeTransition( + @Nullable TaskFragment newParent, @Nullable TaskFragment oldParent) { + if (mWmService.mDisableTransitionAnimation + || mDisplayContent == null || newParent == null || oldParent == null + || getSurfaceControl() == null || !isVisible() || !isVisibleRequested()) { + return false; + } + + // Transition change for the activity moving into a TaskFragment of different bounds. + return newParent.isOrganizedTaskFragment() + && !newParent.getBounds().equals(oldParent.getBounds()); + } + @Override void onParentChanged(ConfigurationContainer rawNewParent, ConfigurationContainer rawOldParent) { final TaskFragment oldParent = (TaskFragment) rawOldParent; @@ -1397,6 +1411,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Task newTask = newParent != null ? newParent.getTask() : null; this.task = newTask; + if (shouldStartChangeTransition(newParent, oldParent)) { + initializeChangeTransition(getBounds()); + } + super.onParentChanged(newParent, oldParent); if (isPersistable()) { @@ -2380,6 +2398,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A }); } + void removeStartingWindowIfNeeded() { + // Removing the task snapshot after the task is actually focused (see + // Task#onWindowFocusChanged). Since some of the app contents may draw in this time and + // requires more times to draw finish, in case flicking may happen when removing the task + // snapshot too early. (i.e. Showing IME.) + if ((mStartingData instanceof SnapshotStartingData) && !getTask().isFocused()) { + return; + } + removeStartingWindow(); + } + void removeStartingWindow() { if (transferSplashScreenIfNeeded()) { return; @@ -2597,6 +2626,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } @Override + boolean isEmbedded() { + final TaskFragment parent = getTaskFragment(); + return parent != null && parent.isEmbedded(); + } + + @Override @Nullable TaskDisplayArea getDisplayArea() { return (TaskDisplayArea) super.getDisplayArea(); @@ -2687,7 +2722,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean isResizeable() { return mAtmService.mForceResizableActivities || ActivityInfo.isResizeableMode(info.resizeMode) - || info.supportsPictureInPicture(); + || info.supportsPictureInPicture() + // If the activity can be embedded, it should inherit the bounds of task fragment. + || isEmbedded(); } /** @return whether this activity is non-resizeable but is forced to be resizable. */ @@ -4934,10 +4971,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * this has become invisible. */ private void postApplyAnimation(boolean visible) { + final boolean usingShellTransitions = + mAtmService.getTransitionController().getTransitionPlayer() != null; final boolean delayed = isAnimating(PARENTS | CHILDREN, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION | ANIMATION_TYPE_RECENTS); - if (!delayed) { + if (!delayed && !usingShellTransitions) { // We aren't delayed anything, but exiting windows rely on the animation finished // callback being called in case the ActivityRecord was pretending to be delayed, // which we might have done because we were in closing/opening apps list. @@ -4956,8 +4995,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // updated. // If we're becoming invisible, update the client visibility if we are not running an // animation. Otherwise, we'll update client visibility in onAnimationFinished. - if (visible || !isAnimating(PARENTS, - ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) { + if (visible || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS) + || usingShellTransitions) { setClientVisible(visible); } @@ -6028,13 +6067,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Task associatedTask = mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null; if (associatedTask == null) { - removeStartingWindow(); + removeStartingWindowIfNeeded(); } else if (associatedTask.getActivity( r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) { // The last drawn activity may not be the one that owns the starting window. final ActivityRecord r = associatedTask.topActivityContainsStartingWindow(); if (r != null) { - r.removeStartingWindow(); + r.removeStartingWindowIfNeeded(); } } updateReportedVisibilityLocked(); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 3b43e48a53af..71ac73091986 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -30,7 +30,7 @@ import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -1573,11 +1573,7 @@ class ActivityStarter { newTransition.setRemoteTransition(remoteTransition); } mService.getTransitionController().collect(r); - // TODO(b/188669821): Remove when navbar reparenting moves to shell - if (r.getActivityType() == ACTIVITY_TYPE_HOME && r.getOptions() != null - && r.getOptions().getTransientLaunch()) { - mService.getTransitionController().setIsLegacyRecents(); - } + final boolean isTransient = r.getOptions() != null && r.getOptions().getTransientLaunch(); try { mService.deferWindowLayout(); Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner"); @@ -1625,6 +1621,11 @@ class ActivityStarter { // it as an existence change. mService.getTransitionController().collectExistenceChange(r); } + if (isTransient) { + // `r` isn't guaranteed to be the actual relevant activity, so we must wait + // until after we launched to identify the relevant activity. + mService.getTransitionController().setTransientLaunch(mLastStartActivityRecord); + } if (newTransition != null) { mService.getTransitionController().requestStartTransition(newTransition, mTargetTask, remoteTransition); @@ -2698,7 +2699,11 @@ class ActivityStarter { mStartActivity.appTimeTracker, DEFER_RESUME, "bringingFoundTaskToFront"); mMovedToFront = !wasTopOfVisibleRootTask; - } else { + } else if (intentActivity.getWindowingMode() != WINDOWING_MODE_PINNED) { + // Leaves reparenting pinned task operations to task organizer to make sure it + // dismisses pinned task properly. + // TODO(b/199997762): Consider leaving all reparent operation of organized tasks + // to task organizer. intentTask.reparent(mTargetRootTask, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT, ANIMATE, DEFER_RESUME, "reparentToTargetRootTask"); mMovedToFront = true; @@ -2722,6 +2727,17 @@ class ActivityStarter { mTargetRootTask = intentActivity.getRootTask(); mSupervisor.handleNonResizableTaskIfNeeded(intentTask, WINDOWING_MODE_UNDEFINED, mRootWindowContainer.getDefaultTaskDisplayArea(), mTargetRootTask); + + // We need to check if there is a launch root task in TDA for this target root task. + // If it exist, we need to reparent target root task from TDA to launch root task. + final TaskDisplayArea tda = mTargetRootTask.getDisplayArea(); + final Task launchRootTask = tda.getLaunchRootTask(mTargetRootTask.getWindowingMode(), + mTargetRootTask.getActivityType(), null /** options */, null /** sourceTask */, + 0 /** launchFlags */); + if (launchRootTask != null && launchRootTask != mTargetRootTask) { + mTargetRootTask.reparent(launchRootTask, POSITION_TOP); + mTargetRootTask = launchRootTask; + } } private void resumeTargetRootTaskIfNeeded() { @@ -2778,7 +2794,9 @@ class ActivityStarter { // request. If the task was resolved and different than mInTaskFragment, reparent the // task to mInTaskFragment for embedding. if (mInTaskFragment.getTask() != task) { - task.reparent(mInTaskFragment, POSITION_TOP); + if (shouldReparentInTaskFragment(task)) { + task.reparent(mInTaskFragment, POSITION_TOP); + } } else { newParent = mInTaskFragment; } @@ -2797,6 +2815,18 @@ class ActivityStarter { } } + private boolean shouldReparentInTaskFragment(Task task) { + // The task has not been embedded. We should reparent the task to TaskFragment. + if (!task.isEmbedded()) { + return true; + } + WindowContainer<?> parent = task.getParent(); + // If the Activity is going to launch on top of embedded Task in the same TaskFragment, + // we don't need to reparent the Task. Otherwise, the embedded Task should reparent to + // another TaskFragment. + return parent.asTaskFragment() != mInTaskFragment; + } + private int adjustLaunchFlagsToDocumentMode(ActivityRecord r, boolean launchSingleInstance, boolean launchSingleTask, int launchFlags) { if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 5174a38d5edc..0ba77d8552d3 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -30,6 +30,7 @@ import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; import android.os.Bundle; import android.os.IBinder; +import android.os.LocaleList; import android.os.RemoteException; import android.service.voice.IVoiceInteractionSession; import android.util.IntArray; @@ -611,6 +612,14 @@ public abstract class ActivityTaskManagerInternal { PackageConfigurationUpdater setNightMode(int nightMode); /** + * Sets the app-specific locales for the application referenced by this updater. + * This setting is persisted and will overlay on top of the system locales for + * the said application. + * @return the current {@link PackageConfigurationUpdater} updated with the provided locale. + */ + PackageConfigurationUpdater setLocales(LocaleList locales); + + /** * Commit changes. */ void commit(); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 1652c3b2a9a7..1c8f6f1f851d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -653,16 +653,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { */ volatile int mTopProcessState = ActivityManager.PROCESS_STATE_TOP; + /** Whether to keep higher priority to launch app while device is sleeping. */ + private volatile boolean mRetainPowerModeAndTopProcessState; + + /** The timeout to restore power mode if {@link #mRetainPowerModeAndTopProcessState} is set. */ + private static final long POWER_MODE_UNKNOWN_VISIBILITY_TIMEOUT_MS = 1000; + @Retention(RetentionPolicy.SOURCE) @IntDef({ POWER_MODE_REASON_START_ACTIVITY, POWER_MODE_REASON_FREEZE_DISPLAY, + POWER_MODE_REASON_UNKNOWN_VISIBILITY, POWER_MODE_REASON_ALL, }) @interface PowerModeReason {} static final int POWER_MODE_REASON_START_ACTIVITY = 1 << 0; static final int POWER_MODE_REASON_FREEZE_DISPLAY = 1 << 1; + /** @see UnknownAppVisibilityController */ + static final int POWER_MODE_REASON_UNKNOWN_VISIBILITY = 1 << 2; /** This can only be used by {@link #endLaunchPowerMode(int)}.*/ static final int POWER_MODE_REASON_ALL = (1 << 2) - 1; @@ -947,7 +956,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { setRecentTasks(new RecentTasks(this, mTaskSupervisor)); mVrController = new VrController(mGlobalLock); mKeyguardController = mTaskSupervisor.getKeyguardController(); - mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue); + mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue, this); } public void onActivityManagerInternalAdded() { @@ -4248,15 +4257,39 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } void startLaunchPowerMode(@PowerModeReason int reason) { - if (mPowerManagerInternal == null) return; - mPowerManagerInternal.setPowerMode(Mode.LAUNCH, true); + if (mPowerManagerInternal != null) { + mPowerManagerInternal.setPowerMode(Mode.LAUNCH, true); + } mLaunchPowerModeReasons |= reason; + if ((reason & POWER_MODE_REASON_UNKNOWN_VISIBILITY) != 0) { + if (mRetainPowerModeAndTopProcessState) { + mH.removeMessages(H.END_POWER_MODE_UNKNOWN_VISIBILITY_MSG); + } + mRetainPowerModeAndTopProcessState = true; + mH.sendEmptyMessageDelayed(H.END_POWER_MODE_UNKNOWN_VISIBILITY_MSG, + POWER_MODE_UNKNOWN_VISIBILITY_TIMEOUT_MS); + Slog.d(TAG, "Temporarily retain top process state for launching app"); + } } void endLaunchPowerMode(@PowerModeReason int reason) { - if (mPowerManagerInternal == null || mLaunchPowerModeReasons == 0) return; + if (mLaunchPowerModeReasons == 0) return; mLaunchPowerModeReasons &= ~reason; - if (mLaunchPowerModeReasons == 0) { + + if ((mLaunchPowerModeReasons & POWER_MODE_REASON_UNKNOWN_VISIBILITY) != 0) { + boolean allResolved = true; + for (int i = mRootWindowContainer.getChildCount() - 1; i >= 0; i--) { + allResolved &= mRootWindowContainer.getChildAt(i).mUnknownAppVisibilityController + .allResolved(); + } + if (allResolved) { + mLaunchPowerModeReasons &= ~POWER_MODE_REASON_UNKNOWN_VISIBILITY; + mRetainPowerModeAndTopProcessState = false; + mH.removeMessages(H.END_POWER_MODE_UNKNOWN_VISIBILITY_MSG); + } + } + + if (mLaunchPowerModeReasons == 0 && mPowerManagerInternal != null) { mPowerManagerInternal.setPowerMode(Mode.LAUNCH, false); } } @@ -5123,6 +5156,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final class H extends Handler { static final int REPORT_TIME_TRACKER_MSG = 1; static final int UPDATE_PROCESS_ANIMATING_STATE = 2; + static final int END_POWER_MODE_UNKNOWN_VISIBILITY_MSG = 3; static final int FIRST_ACTIVITY_TASK_MSG = 100; static final int FIRST_SUPERVISOR_TASK_MSG = 200; @@ -5146,6 +5180,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } break; + case END_POWER_MODE_UNKNOWN_VISIBILITY_MSG: { + synchronized (mGlobalLock) { + mRetainPowerModeAndTopProcessState = false; + endLaunchPowerMode(POWER_MODE_REASON_UNKNOWN_VISIBILITY); + if (mTopApp != null + && mTopProcessState == ActivityManager.PROCESS_STATE_TOP_SLEEPING) { + // Restore the scheduling group for sleeping. + mTopApp.updateProcessInfo(false /* updateServiceConnection */, + false /* activityChange */, true /* updateOomAdj */, + false /* addPendingTopUid */); + } + } + } + break; } } } @@ -5464,6 +5512,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @HotPath(caller = HotPath.OOM_ADJUSTMENT) @Override public int getTopProcessState() { + if (mRetainPowerModeAndTopProcessState) { + // There is a launching app while device may be sleeping, force the top state so + // the launching process can have top-app scheduling group. + return ActivityManager.PROCESS_STATE_TOP; + } return mTopProcessState; } @@ -6522,7 +6575,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final class PackageConfigurationUpdaterImpl implements ActivityTaskManagerInternal.PackageConfigurationUpdater { private final int mPid; - private int mNightMode; + private Integer mNightMode; + private LocaleList mLocales; PackageConfigurationUpdaterImpl(int pid) { mPid = pid; @@ -6535,6 +6589,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public ActivityTaskManagerInternal.PackageConfigurationUpdater + setLocales(LocaleList locales) { + mLocales = locales; + return this; + } + + @Override public void commit() { synchronized (mGlobalLock) { final long ident = Binder.clearCallingIdentity(); @@ -6544,8 +6605,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { Slog.w(TAG, "Override application configuration: cannot find pid " + mPid); return; } - wpc.setOverrideNightMode(mNightMode); - wpc.updateNightModeForAllActivities(mNightMode); + LocaleList localesOverride = LocaleOverlayHelper.combineLocalesIfOverlayExists( + mLocales, getGlobalConfiguration().getLocales()); + wpc.applyAppSpecificConfig(mNightMode, localesOverride); + wpc.updateAppSpecificSettingsForAllActivities(mNightMode, localesOverride); mPackageConfigPersister.updateFromImpl(wpc.mName, wpc.mUserId, this); } finally { Binder.restoreCallingIdentity(ident); @@ -6553,8 +6616,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - int getNightMode() { + Integer getNightMode() { return mNightMode; } + + LocaleList getLocales() { + return mLocales; + } } } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 7a42351c33c1..c0b69794966b 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -237,6 +237,8 @@ public class AppTransitionController { // Check if there is any override if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) { + // Unfreeze the windows that were previously frozen for TaskFragment animation. + unfreezeEmbeddedChangingWindows(); overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes); } @@ -341,6 +343,9 @@ public class AppTransitionController { switch (changingType) { case TYPE_TASK: return TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; + case TYPE_ACTIVITY: + // ActivityRecord is put in a change transition only when it is reparented + // to an organized TaskFragment. See ActivityRecord#shouldStartChangeTransition. case TYPE_TASK_FRAGMENT: return TRANSIT_OLD_TASK_FRAGMENT_CHANGE; default: @@ -511,6 +516,16 @@ public class AppTransitionController { : null; } + private void unfreezeEmbeddedChangingWindows() { + final ArraySet<WindowContainer> changingContainers = mDisplayContent.mChangingContainers; + for (int i = changingContainers.size() - 1; i >= 0; i--) { + final WindowContainer wc = changingContainers.valueAt(i); + if (wc.isEmbedded()) { + wc.mSurfaceFreezer.unfreeze(wc.getSyncTransaction()); + } + } + } + /** * Overrides the pending transition with the remote animation defined by the * {@link ITaskFragmentOrganizer} if all windows in the transition are children of diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 6fafc0291427..eeb85c585876 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -39,6 +39,7 @@ import android.app.WindowConfiguration; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; +import android.os.LocaleList; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; @@ -512,7 +513,7 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM; } - /** Returns the activity type associated with the the configuration container. */ + /** Returns the activity type associated with the configuration container. */ /*@WindowConfiguration.ActivityType*/ public int getActivityType() { return mFullConfiguration.windowConfiguration.getActivityType(); @@ -546,20 +547,48 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } /** + * Applies app-specific nightMode and {@link LocaleList} on requested configuration. + * @return true if any of the requested configuration has been updated. + */ + public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales) { + mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration()); + boolean newNightModeSet = (nightMode != null) && setOverrideNightMode(mRequestsTmpConfig, + nightMode); + boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig, + locales); + if (newNightModeSet || newLocalesSet) { + onRequestedOverrideConfigurationChanged(mRequestsTmpConfig); + } + return newNightModeSet || newLocalesSet; + } + + /** * Overrides the night mode applied to this ConfigurationContainer. * @return true if the nightMode has been changed. */ - public boolean setOverrideNightMode(int nightMode) { + private boolean setOverrideNightMode(Configuration requestsTmpConfig, int nightMode) { final int currentUiMode = mRequestedOverrideConfiguration.uiMode; final int currentNightMode = currentUiMode & Configuration.UI_MODE_NIGHT_MASK; final int validNightMode = nightMode & Configuration.UI_MODE_NIGHT_MASK; if (currentNightMode == validNightMode) { return false; } - mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration()); - mRequestsTmpConfig.uiMode = validNightMode + requestsTmpConfig.uiMode = validNightMode | (currentUiMode & ~Configuration.UI_MODE_NIGHT_MASK); - onRequestedOverrideConfigurationChanged(mRequestsTmpConfig); + return true; + } + + /** + * Overrides the locales applied to this ConfigurationContainer. + * @return true if the LocaleList has been changed. + */ + private boolean setOverrideLocales(Configuration requestsTmpConfig, + @NonNull LocaleList overrideLocales) { + if (mRequestedOverrideConfiguration.getLocales().equals(overrideLocales)) { + return false; + } + requestsTmpConfig.setLocales(overrideLocales); + requestsTmpConfig.userSetLocale = true; return true; } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 199159ee0580..63f6387c87ae 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -578,6 +578,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * Specifies the count to determine whether to defer updating the IME target until ready. */ private int mDeferUpdateImeTargetCount; + private boolean mUpdateImeRequestedWhileDeferred; private MagnificationSpec mMagnificationSpec; @@ -3729,6 +3730,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final WindowState curTarget = mImeLayeringTarget; if (!canUpdateImeTarget()) { if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Defer updating IME target"); + mUpdateImeRequestedWhileDeferred = true; return curTarget; } @@ -4991,6 +4993,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * Increment the deferral count to determine whether to update the IME target. */ void deferUpdateImeTarget() { + if (mDeferUpdateImeTargetCount == 0) { + mUpdateImeRequestedWhileDeferred = false; + } mDeferUpdateImeTargetCount++; } @@ -5004,7 +5009,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } mDeferUpdateImeTargetCount--; - if (mDeferUpdateImeTargetCount == 0) { + if (mDeferUpdateImeTargetCount == 0 && mUpdateImeRequestedWhileDeferred) { computeImeTarget(true /* updateImeTarget */); } } diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 73d6cecd9155..c9db14de507c 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -444,7 +444,9 @@ public class DisplayRotation { } if (mDisplayContent.mFixedRotationTransitionListener - .isTopFixedOrientationRecentsAnimating()) { + .isTopFixedOrientationRecentsAnimating() + // If screen is off or the device is going to sleep, then still allow to update. + && mService.mPolicy.okToAnimate(false /* ignoreScreenOn */)) { // During the recents animation, the closing app might still be considered on top. // In order to ignore its requested orientation to avoid a sensor led rotation (e.g // user rotating the device while the recents animation is running), we ignore diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 18ea738b08ce..aa257f847e25 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -464,7 +464,8 @@ class DragState { if (mDragInProgress && isValidDropTarget(newWin, containsAppExtras, interceptsGlobalDrag)) { // Only allow the extras to be dispatched to a global-intercepting drag target ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null; - DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED, touchX, touchY, + DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED, + newWin.translateToWindowX(touchX), newWin.translateToWindowY(touchY), data, false /* includeDragSurface */, null /* dragAndDropPermission */); try { diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 4f6a693b8c3f..cbefe7f3ade4 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -101,6 +101,24 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { super.updateControlForTarget(target, force); } + @Override + protected boolean updateClientVisibility(InsetsControlTarget caller) { + boolean changed = super.updateClientVisibility(caller); + if (changed && caller.getRequestedVisibility(mSource.getType())) { + reportImeDrawnForOrganizer(caller); + } + return changed; + } + + private void reportImeDrawnForOrganizer(InsetsControlTarget caller) { + if (caller.getWindow() != null && caller.getWindow().getTask() != null) { + if (caller.getWindow().getTask().isOrganized()) { + mWin.mWmService.mAtmService.mTaskOrganizerController.reportImeDrawnOnTask( + caller.getWindow().getTask()); + } + } + } + private void onSourceChanged() { if (mLastSource.equals(mSource)) { return; diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index f93e08531b67..767e2c2f1917 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -267,7 +267,7 @@ class InsetsSourceProvider { && mWin.okToDisplay()) { mWin.applyWithNextDraw(mSetLeashPositionConsumer); } else { - mSetLeashPositionConsumer.accept(mWin.getPendingTransaction()); + mSetLeashPositionConsumer.accept(mWin.getSyncTransaction()); } } if (mServerVisible && !mLastSourceFrame.equals(mSource.getFrame())) { @@ -331,7 +331,7 @@ class InsetsSourceProvider { if (getSource().getType() == ITYPE_IME) { setClientVisible(target.getRequestedVisibility(mSource.getType())); } - final Transaction t = mDisplayContent.getPendingTransaction(); + final Transaction t = mDisplayContent.getSyncTransaction(); mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */, ANIMATION_TYPE_INSETS_CONTROL); diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 4a8c36f9bc47..c630e91ee306 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -109,13 +109,13 @@ class KeyguardController { } /** - * @return {@code true} for default display when AOD is showing. Otherwise, same as - * {@link #isKeyguardOrAodShowing(int)} + * @return {@code true} for default display when AOD is showing, not going away. Otherwise, same + * as {@link #isKeyguardOrAodShowing(int)} * TODO(b/125198167): Replace isKeyguardOrAodShowing() by this logic. */ boolean isKeyguardUnoccludedOrAodShowing(int displayId) { if (displayId == DEFAULT_DISPLAY && mAodShowing) { - return true; + return !mKeyguardGoingAway; } return isKeyguardOrAodShowing(displayId); } @@ -488,7 +488,7 @@ class KeyguardController { final KeyguardDisplayState state = getDisplayState(displayId); if (isKeyguardUnoccludedOrAodShowing(displayId)) { state.mSleepTokenAcquirer.acquire(displayId); - } else if (!isKeyguardUnoccludedOrAodShowing(displayId)) { + } else { state.mSleepTokenAcquirer.release(displayId); } } diff --git a/services/core/java/com/android/server/wm/LocaleOverlayHelper.java b/services/core/java/com/android/server/wm/LocaleOverlayHelper.java new file mode 100644 index 000000000000..a1a01dba769a --- /dev/null +++ b/services/core/java/com/android/server/wm/LocaleOverlayHelper.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.os.LocaleList; + +import java.util.Locale; + +/** + * Static utilities to overlay locales on top of another localeList. + * + * <p>This is used to overlay application-specific locales in + * {@link com.android.server.wm.ActivityTaskManagerInternal.PackageConfigurationUpdater} on top of + * system locales. + */ +final class LocaleOverlayHelper { + + /** + * Combines the overlay locales and base locales. + * @return the combined {@link LocaleList} if the overlay locales is not empty/null else + * returns the empty/null LocaleList. + */ + static LocaleList combineLocalesIfOverlayExists(LocaleList overlayLocales, + LocaleList baseLocales) { + if (overlayLocales == null || overlayLocales.isEmpty()) { + return overlayLocales; + } + return combineLocales(overlayLocales, baseLocales); + } + + /** + * Creates a combined {@link LocaleList} by placing overlay locales before base locales and + * dropping duplicates from the base locales. + */ + private static LocaleList combineLocales(LocaleList overlayLocales, LocaleList baseLocales) { + Locale[] combinedLocales = new Locale[overlayLocales.size() + baseLocales.size()]; + for (int i = 0; i < overlayLocales.size(); i++) { + combinedLocales[i] = overlayLocales.get(i); + } + for (int i = 0; i < baseLocales.size(); i++) { + combinedLocales[i + overlayLocales.size()] = baseLocales.get(i); + } + // Constructor of {@link LocaleList} removes duplicates + return new LocaleList(combinedLocales); + } + + +} diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java index 1552a96d699a..505c4beb8fdc 100644 --- a/services/core/java/com/android/server/wm/PackageConfigPersister.java +++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java @@ -21,6 +21,7 @@ import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; import android.annotation.NonNull; import android.os.Environment; +import android.os.LocaleList; import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; @@ -54,12 +55,14 @@ public class PackageConfigPersister { private static final String TAG_CONFIG = "config"; private static final String ATTR_PACKAGE_NAME = "package_name"; private static final String ATTR_NIGHT_MODE = "night_mode"; + private static final String ATTR_LOCALES = "locale_list"; private static final String PACKAGE_DIRNAME = "package_configs"; private static final String SUFFIX_FILE_NAME = "_config.xml"; private final PersisterQueue mPersisterQueue; private final Object mLock = new Object(); + private final ActivityTaskManagerService mAtm; @GuardedBy("mLock") private final SparseArray<HashMap<String, PackageConfigRecord>> mPendingWrite = @@ -72,8 +75,9 @@ public class PackageConfigPersister { return new File(Environment.getDataSystemCeDirectory(userId), PACKAGE_DIRNAME); } - PackageConfigPersister(PersisterQueue queue) { + PackageConfigPersister(PersisterQueue queue, ActivityTaskManagerService atm) { mPersisterQueue = queue; + mAtm = atm; } @GuardedBy("mLock") @@ -100,7 +104,8 @@ public class PackageConfigPersister { final TypedXmlPullParser in = Xml.resolvePullParser(is); int event; String packageName = null; - int nightMode = MODE_NIGHT_AUTO; + Integer nightMode = null; + LocaleList locales = null; while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && event != XmlPullParser.END_TAG) { final String name = in.getName(); @@ -120,6 +125,9 @@ public class PackageConfigPersister { case ATTR_NIGHT_MODE: nightMode = Integer.parseInt(attrValue); break; + case ATTR_LOCALES: + locales = LocaleList.forLanguageTags(attrValue); + break; } } } @@ -130,6 +138,7 @@ public class PackageConfigPersister { final PackageConfigRecord initRecord = findRecordOrCreate(mModified, packageName, userId); initRecord.mNightMode = nightMode; + initRecord.mLocales = locales; if (DEBUG) { Slog.d(TAG, "loadPackages: load one package " + initRecord); } @@ -155,7 +164,9 @@ public class PackageConfigPersister { "updateConfigIfNeeded record " + container + " find? " + modifiedRecord); } if (modifiedRecord != null) { - container.setOverrideNightMode(modifiedRecord.mNightMode); + container.applyAppSpecificConfig(modifiedRecord.mNightMode, + LocaleOverlayHelper.combineLocalesIfOverlayExists( + modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales())); } } } @@ -165,10 +176,16 @@ public class PackageConfigPersister { ActivityTaskManagerService.PackageConfigurationUpdaterImpl impl) { synchronized (mLock) { PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId); - record.mNightMode = impl.getNightMode(); - - if (record.isResetNightMode()) { - removePackage(record.mName, record.mUserId); + if (impl.getNightMode() != null) { + record.mNightMode = impl.getNightMode(); + } + if (impl.getLocales() != null) { + record.mLocales = impl.getLocales(); + } + if ((record.mNightMode == null || record.isResetNightMode()) + && (record.mLocales == null || record.mLocales.isEmpty())) { + // if all values default to system settings, we can remove the package. + removePackage(packageName, userId); } else { final PackageConfigRecord pendingRecord = findRecord(mPendingWrite, record.mName, record.mUserId); @@ -179,10 +196,11 @@ public class PackageConfigPersister { } else { writeRecord = pendingRecord; } - if (writeRecord.mNightMode == record.mNightMode) { + + if (!updateNightMode(record, writeRecord) && !updateLocales(record, writeRecord)) { return; } - writeRecord.mNightMode = record.mNightMode; + if (DEBUG) { Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord); } @@ -191,6 +209,22 @@ public class PackageConfigPersister { } } + private boolean updateNightMode(PackageConfigRecord record, PackageConfigRecord writeRecord) { + if (record.mNightMode == null || record.mNightMode.equals(writeRecord.mNightMode)) { + return false; + } + writeRecord.mNightMode = record.mNightMode; + return true; + } + + private boolean updateLocales(PackageConfigRecord record, PackageConfigRecord writeRecord) { + if (record.mLocales == null || record.mLocales.equals(writeRecord.mLocales)) { + return false; + } + writeRecord.mLocales = record.mLocales; + return true; + } + @GuardedBy("mLock") void removeUser(int userId) { synchronized (mLock) { @@ -210,7 +244,7 @@ public class PackageConfigPersister { @GuardedBy("mLock") void onPackageUninstall(String packageName) { synchronized (mLock) { - for (int i = mModified.size() - 1; i > 0; i--) { + for (int i = mModified.size() - 1; i >= 0; i--) { final int userId = mModified.keyAt(i); removePackage(packageName, userId); } @@ -242,7 +276,8 @@ public class PackageConfigPersister { static class PackageConfigRecord { final String mName; final int mUserId; - int mNightMode; + Integer mNightMode; + LocaleList mLocales; PackageConfigRecord(String name, int userId) { mName = name; @@ -256,7 +291,7 @@ public class PackageConfigPersister { @Override public String toString() { return "PackageConfigRecord package name: " + mName + " userId " + mUserId - + " nightMode " + mNightMode; + + " nightMode " + mNightMode + " locales " + mLocales; } } @@ -369,7 +404,13 @@ public class PackageConfigPersister { } xmlSerializer.startTag(null, TAG_CONFIG); xmlSerializer.attribute(null, ATTR_PACKAGE_NAME, mRecord.mName); - xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode); + if (mRecord.mNightMode != null) { + xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode); + } + if (mRecord.mLocales != null) { + xmlSerializer.attribute(null, ATTR_LOCALES, mRecord.mLocales + .toLanguageTags()); + } xmlSerializer.endTag(null, TAG_CONFIG); xmlSerializer.endDocument(); xmlSerializer.flush(); diff --git a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java new file mode 100644 index 000000000000..ef8dee401b05 --- /dev/null +++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_270; + +import android.hardware.display.DisplayManagerInternal; +import android.util.ArraySet; +import android.util.Slog; +import android.util.SparseArray; +import android.view.DisplayInfo; +import android.view.Surface; + +import java.util.Set; + +/** + * Maintains a map of possible {@link DisplayInfo} for displays and states that may be encountered + * on a device. This is not guaranteed to include all possible device states for all displays. + * + * By 'possible', this class only handles device states for displays and display groups it is + * currently aware of. It can not handle all eventual states the system may enter, for example, if + * an external display is added, or a new display is added to the group. + */ +public class PossibleDisplayInfoMapper { + private static final String TAG = "PossibleDisplayInfoMapper"; + private static final boolean DEBUG = false; + + private final DisplayManagerInternal mDisplayManagerInternal; + + /** + * Map of all logical displays, indexed by logical display id. + * Each logical display has multiple entries, one for each possible rotation and device + * state. + * + * Emptied and re-calculated when a display is added, removed, or changed. + */ + private final SparseArray<Set<DisplayInfo>> mDisplayInfos = new SparseArray<>(); + + PossibleDisplayInfoMapper(DisplayManagerInternal displayManagerInternal) { + mDisplayManagerInternal = displayManagerInternal; + } + + + /** + * Returns, for the given displayId, a set of display infos. Set contains the possible rotations + * for each supported device state. + */ + public Set<DisplayInfo> getPossibleDisplayInfos(int displayId) { + // Update display infos before returning, since any cached values would have been removed + // in response to any display event. This model avoids re-computing the cache for every + // display change event (which occurs extremely frequently in the normal usage of the + // device). + updatePossibleDisplayInfos(displayId); + if (!mDisplayInfos.contains(displayId)) { + return new ArraySet<>(); + } + return Set.copyOf(mDisplayInfos.get(displayId)); + } + + /** + * Updates the possible {@link DisplayInfo}s for the given display, by calculating the + * DisplayInfo for each rotation across supported device states. + */ + public void updatePossibleDisplayInfos(int displayId) { + Set<DisplayInfo> displayInfos = mDisplayManagerInternal.getPossibleDisplayInfo(displayId); + if (DEBUG) { + Slog.v(TAG, "updatePossibleDisplayInfos, calculate rotations for given DisplayInfo " + + displayInfos.size() + " on display " + displayId); + } + updateDisplayInfos(displayInfos); + } + + /** + * For the given displayId, removes all possible {@link DisplayInfo}. + */ + public void removePossibleDisplayInfos(int displayId) { + if (DEBUG && mDisplayInfos.get(displayId) != null) { + Slog.v(TAG, "onDisplayRemoved, remove all DisplayInfo (" + mDisplayInfos.get( + displayId).size() + ") with id " + displayId); + } + mDisplayInfos.remove(displayId); + } + + private void updateDisplayInfos(Set<DisplayInfo> displayInfos) { + // Empty out cache before re-computing. + mDisplayInfos.clear(); + DisplayInfo[] originalDisplayInfos = new DisplayInfo[displayInfos.size()]; + displayInfos.toArray(originalDisplayInfos); + // Iterate over each logical display layout for the current state. + Set<DisplayInfo> rotatedDisplayInfos; + for (DisplayInfo di : originalDisplayInfos) { + rotatedDisplayInfos = new ArraySet<>(); + // Calculate all possible rotations for each logical display. + for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) { + rotatedDisplayInfos.add(applyRotation(di, rotation)); + } + // Combine all results under the logical display id. + Set<DisplayInfo> priorDisplayInfos = mDisplayInfos.get(di.displayId, new ArraySet<>()); + priorDisplayInfos.addAll(rotatedDisplayInfos); + mDisplayInfos.put(di.displayId, priorDisplayInfos); + } + } + + private static DisplayInfo applyRotation(DisplayInfo displayInfo, + @Surface.Rotation int rotation) { + DisplayInfo updatedDisplayInfo = new DisplayInfo(); + updatedDisplayInfo.copyFrom(displayInfo); + updatedDisplayInfo.rotation = rotation; + + final int naturalWidth = updatedDisplayInfo.getNaturalWidth(); + final int naturalHeight = updatedDisplayInfo.getNaturalHeight(); + updatedDisplayInfo.logicalWidth = naturalWidth; + updatedDisplayInfo.logicalHeight = naturalHeight; + return updatedDisplayInfo; + } +} diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 6c2322b6d7e5..40207882d73c 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2577,6 +2577,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (mService.isBooted() || mService.isBooting()) { startSystemDecorations(display.mDisplayContent); } + // Drop any cached DisplayInfos associated with this display id - the values are now + // out of date given this display added event. + mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId); } } @@ -2597,8 +2600,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (displayContent == null) { return; } - displayContent.remove(); + mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId); } } @@ -2610,6 +2613,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (displayContent != null) { displayContent.onDisplayChanged(); } + // Drop any cached DisplayInfos associated with this display id - the values are now + // out of date given this display changed event. + mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId); } } @@ -3545,14 +3551,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } void startPowerModeLaunchIfNeeded(boolean forceSend, ActivityRecord targetActivity) { - final boolean sendPowerModeLaunch; - - if (forceSend) { - sendPowerModeLaunch = true; - } else if (targetActivity == null || targetActivity.app == null) { - // Set power mode if we don't know what we're launching yet. - sendPowerModeLaunch = true; - } else { + if (!forceSend && targetActivity != null && targetActivity.app != null) { // Set power mode when the activity's process is different than the current top resumed // activity on all display areas, or if there are no resumed activities in the system. boolean[] noResumedActivities = {true}; @@ -3568,13 +3567,28 @@ class RootWindowContainer extends WindowContainer<DisplayContent> !resumedActivityProcess.equals(targetActivity.app); } }); - sendPowerModeLaunch = noResumedActivities[0] || allFocusedProcessesDiffer[0]; + if (!noResumedActivities[0] && !allFocusedProcessesDiffer[0]) { + // All focused activities are resumed and the process of the target activity is + // the same as them, e.g. delivering new intent to the current top. + return; + } } - if (sendPowerModeLaunch) { - mService.startLaunchPowerMode( - ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); + int reason = ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY; + // If the activity is launching while keyguard is locked (including occluded), the activity + // may be visible until its first relayout is done (e.g. apply show-when-lock flag). To + // avoid power mode from being cleared before that, add a special reason to consider whether + // the unknown visibility is resolved. The case from SystemUI is excluded because it should + // rely on keyguard-going-away. + if (mService.mKeyguardController.isKeyguardLocked() && targetActivity != null + && !targetActivity.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_SYSTEMUI)) { + final ActivityOptions opts = targetActivity.getOptions(); + if (opts == null || opts.getSourceInfo() == null + || opts.getSourceInfo().type != ActivityOptions.SourceInfo.TYPE_LOCKSCREEN) { + reason |= ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY; + } } + mService.startLaunchPowerMode(reason); } // TODO(b/191434136): handle this properly when we add multi-window support on secondary diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index e6e51b890305..ca98564c1d13 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1236,7 +1236,7 @@ class Task extends TaskFragment { mRootWindowContainer.updateUIDsPresentOnDisplay(); } - /** Returns the currently topmost resumed activity. */ + @Override @Nullable ActivityRecord getTopResumedActivity() { if (!isLeafTask()) { @@ -1253,15 +1253,7 @@ class Task extends TaskFragment { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer child = mChildren.get(i); if (child.asTaskFragment() != null) { - final ActivityRecord[] resumedActivity = new ActivityRecord[1]; - child.asTaskFragment().forAllLeafTaskFragments(fragment -> { - if (fragment.getResumedActivity() != null) { - resumedActivity[0] = fragment.getResumedActivity(); - return true; - } - return false; - }); - topResumedActivity = resumedActivity[0]; + topResumedActivity = child.asTaskFragment().getTopResumedActivity(); } else if (taskResumedActivity != null && child.asActivityRecord() == taskResumedActivity) { topResumedActivity = taskResumedActivity; @@ -1273,9 +1265,7 @@ class Task extends TaskFragment { return null; } - /** - * Returns the currently topmost pausing activity. - */ + @Override @Nullable ActivityRecord getTopPausingActivity() { if (!isLeafTask()) { @@ -1292,15 +1282,7 @@ class Task extends TaskFragment { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer child = mChildren.get(i); if (child.asTaskFragment() != null) { - final ActivityRecord[] pausingActivity = new ActivityRecord[1]; - child.asTaskFragment().forAllLeafTaskFragments(fragment -> { - if (fragment.getPausingActivity() != null) { - pausingActivity[0] = fragment.getPausingActivity(); - return true; - } - return false; - }); - topPausingActivity = pausingActivity[0]; + topPausingActivity = child.asTaskFragment().getTopPausingActivity(); } else if (taskPausingActivity != null && child.asActivityRecord() == taskPausingActivity) { topPausingActivity = taskPausingActivity; @@ -1606,9 +1588,15 @@ class Task extends TaskFragment { } else { forAllActivities((r) -> { if (r.finishing) return; - // TODO: figure-out how to avoid object creation due to capture of reason variable. - r.finishIfPossible(Activity.RESULT_CANCELED, - null /* resultData */, null /* resultGrants */, reason, false /* oomAdj */); + // Prevent the transition from being executed too early if the top activity is + // resumed but the mVisibleRequested of any other activity is true, the transition + // should wait until next activity resumed. + if (r.isState(RESUMED) || (r.isVisible() + && !mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CLOSE))) { + r.finishIfPossible(reason, false /* oomAdj */); + } else { + r.destroyIfPossible(reason); + } }); } } @@ -3515,6 +3503,7 @@ class Task extends TaskFragment { final WindowState mainWindow = activity.findMainWindow(); if (mainWindow != null) { info.mainWindowLayoutParams = mainWindow.getAttrs(); + info.requestedVisibilities.set(mainWindow.getRequestedVisibilities()); } // If the developer has persist a different configuration, we need to override it to the // starting window because persisted configuration does not effect to Task. @@ -4298,7 +4287,7 @@ class Task extends TaskFragment { /** * @return true if the task is currently focused. */ - private boolean isFocused() { + boolean isFocused() { if (mDisplayContent == null || mDisplayContent.mFocusedApp == null) { return false; } @@ -4360,6 +4349,10 @@ class Task extends TaskFragment { * @param hasFocus */ void onAppFocusChanged(boolean hasFocus) { + final ActivityRecord topAct = getTopVisibleActivity(); + if (topAct != null && (topAct.mStartingData instanceof SnapshotStartingData)) { + topAct.removeStartingWindowIfNeeded(); + } updateShadowsRadius(hasFocus, getSyncTransaction()); dispatchTaskInfoChangedIfNeeded(false /* force */); } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index c7ca180bfa14..4a1a922c8a02 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1095,29 +1095,27 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { return rootTask; } } else if (candidateTask != null) { - final Task rootTask = candidateTask; final int position = onTop ? POSITION_TOP : POSITION_BOTTOM; final Task launchRootTask = getLaunchRootTask(windowingMode, activityType, options, sourceTask, launchFlags); - if (launchRootTask != null) { - if (rootTask.getParent() == null) { - launchRootTask.addChild(rootTask, position); - } else if (rootTask.getParent() != launchRootTask) { - rootTask.reparent(launchRootTask, position); + if (candidateTask.getParent() == null) { + launchRootTask.addChild(candidateTask, position); + } else if (candidateTask.getParent() != launchRootTask) { + candidateTask.reparent(launchRootTask, position); } - } else if (rootTask.getDisplayArea() != this || !rootTask.isRootTask()) { - if (rootTask.getParent() == null) { - addChild(rootTask, position); + } else if (candidateTask.getDisplayArea() != this || !candidateTask.isRootTask()) { + if (candidateTask.getParent() == null) { + addChild(candidateTask, position); } else { - rootTask.reparent(this, onTop); + candidateTask.reparent(this, onTop); } } // Update windowing mode if necessary, e.g. moving a pinned task to fullscreen. if (candidateTask.getWindowingMode() != windowingMode) { candidateTask.setWindowingMode(windowingMode); } - return rootTask; + return candidateTask.getRootTask(); } return new Task.Builder(mAtmService) .setWindowingMode(windowingMode) diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index abe95fa80478..584f7bf5e5b1 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -33,7 +33,6 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.os.UserHandle.USER_NULL; import static android.view.Display.INVALID_DISPLAY; -import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_NONE; @@ -352,7 +351,29 @@ class TaskFragment extends WindowContainer<WindowContainer> { return mAdjacentTaskFragment; } - /** @return the currently resumed activity. */ + /** Returns the currently topmost resumed activity. */ + @Nullable + ActivityRecord getTopResumedActivity() { + final ActivityRecord taskFragResumedActivity = getResumedActivity(); + for (int i = getChildCount() - 1; i >= 0; --i) { + WindowContainer<?> child = getChildAt(i); + ActivityRecord topResumedActivity = null; + if (taskFragResumedActivity != null && child == taskFragResumedActivity) { + topResumedActivity = child.asActivityRecord(); + } else if (child.asTaskFragment() != null) { + topResumedActivity = child.asTaskFragment().getTopResumedActivity(); + } + if (topResumedActivity != null) { + return topResumedActivity; + } + } + return null; + } + + /** + * Returns the currently resumed activity in this TaskFragment's + * {@link #mChildren direct children} + */ ActivityRecord getResumedActivity() { return mResumedActivity; } @@ -376,6 +397,25 @@ class TaskFragment extends WindowContainer<WindowContainer> { mPausingActivity = pausing; } + /** Returns the currently topmost pausing activity. */ + @Nullable + ActivityRecord getTopPausingActivity() { + final ActivityRecord taskFragPausingActivity = getPausingActivity(); + for (int i = getChildCount() - 1; i >= 0; --i) { + WindowContainer<?> child = getChildAt(i); + ActivityRecord topPausingActivity = null; + if (taskFragPausingActivity != null && child == taskFragPausingActivity) { + topPausingActivity = child.asActivityRecord(); + } else if (child.asTaskFragment() != null) { + topPausingActivity = child.asTaskFragment().getTopPausingActivity(); + } + if (topPausingActivity != null) { + return topPausingActivity; + } + } + return null; + } + ActivityRecord getPausingActivity() { return mPausingActivity; } @@ -424,7 +464,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { return this; } - /** Returns {@code true} if this is a container for embedded activities or tasks. */ + @Override boolean isEmbedded() { if (mIsEmbedded) { return true; @@ -1673,7 +1713,9 @@ class TaskFragment extends WindowContainer<WindowContainer> { } final Task thisTask = asTask(); - if (thisTask != null) { + // Embedded Task's configuration should go with parent TaskFragment, so we don't re-compute + // configuration here. + if (thisTask != null && !thisTask.isEmbedded()) { thisTask.resolveLeafTaskOnlyOverrideConfigs(newParentConfig, mTmpBounds /* previousBounds */); } @@ -2030,15 +2072,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { return !startBounds.equals(getBounds()); } - /** - * Initializes a change transition. See {@link SurfaceFreezer} for more information. - */ - void initializeChangeTransition(Rect startBounds) { - mDisplayContent.prepareAppTransition(TRANSIT_CHANGE); - mDisplayContent.mChangingContainers.add(this); - mSurfaceFreezer.freeze(getSyncTransaction(), startBounds); - } - @Override void setSurfaceControl(SurfaceControl sc) { super.setSurfaceControl(sc); @@ -2120,6 +2153,11 @@ class TaskFragment extends WindowContainer<WindowContainer> { return mTaskFragmentOrganizer != null; } + /** Whether this is an organized {@link TaskFragment} and not a {@link Task}. */ + final boolean isOrganizedTaskFragment() { + return mTaskFragmentOrganizer != null; + } + /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */ void clearLastPausedActivity() { forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null); diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index fa7b276bc418..28beaf36d435 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -478,7 +478,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { if (launchTheme != 0) { info.splashScreenThemeResId = launchTheme; } - info.mTaskSnapshot = taskSnapshot; + info.taskSnapshot = taskSnapshot; // make this happen prior than prepare surface try { lastOrganizer.addStartingWindow(info, activity.token); @@ -703,6 +703,17 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { mPendingTaskEvents.clear(); } + void reportImeDrawnOnTask(Task task) { + final TaskOrganizerState state = mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder()); + if (state != null) { + try { + state.mOrganizer.mTaskOrganizer.onImeDrawnOnTask(task.mTaskId); + } catch (RemoteException e) { + Slog.e(TAG, "Exception sending onImeDrawnOnTask callback", e); + } + } + } + void onTaskInfoChanged(Task task, boolean force) { if (!task.mTaskAppearedSent) { // Skip if task still not appeared. diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 6b93364232d7..1a46d0f9a877 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -148,6 +148,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe */ private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>(); + /** Set of transient activities (lifecycle initially tied to this transition). */ + private ArraySet<ActivityRecord> mTransientLaunches = null; + /** Custom activity-level animation options and callbacks. */ private TransitionInfo.AnimationOptions mOverrideOptions; private IRemoteCallback mClientAnimationStartCallback = null; @@ -174,6 +177,20 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mFlags |= flag; } + /** Records an activity as transient-launch. This activity must be already collected. */ + void setTransientLaunch(@NonNull ActivityRecord activity) { + if (mTransientLaunches == null) { + mTransientLaunches = new ArraySet<>(); + } + mTransientLaunches.add(activity); + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as " + + "transient-launch", mSyncId, activity); + } + + boolean isTransientLaunch(@NonNull ActivityRecord activity) { + return mTransientLaunches != null && mTransientLaunches.contains(activity); + } + @VisibleForTesting int getSyncId() { return mSyncId; diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 69e6a541e009..c1d0f80adbb7 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; @@ -185,6 +186,20 @@ class TransitionController { return false; } + /** + * @return {@code true} if {@param ar} is part of a transient-launch activity in an active + * transition. + */ + boolean isTransientLaunch(@NonNull ActivityRecord ar) { + if (mCollectingTransition != null && mCollectingTransition.isTransientLaunch(ar)) { + return true; + } + for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { + if (mPlayingTransitions.get(i).isTransientLaunch(ar)) return true; + } + return false; + } + @WindowManager.TransitionType int getCollectingTransitionType() { return mCollectingTransition != null ? mCollectingTransition.mType : TRANSIT_NONE; @@ -331,13 +346,18 @@ class TransitionController { } /** - * Explicitly mark the collectingTransition as being part of recents gesture. Used for legacy - * behaviors. - * TODO(b/188669821): Remove once legacy recents behavior is moved to shell. + * Record that the launch of {@param activity} is transient (meaning its lifecycle is currently + * tied to the transition). */ - void setIsLegacyRecents() { + void setTransientLaunch(@NonNull ActivityRecord activity) { if (mCollectingTransition == null) return; - mCollectingTransition.addFlag(TRANSIT_FLAG_IS_RECENTS); + mCollectingTransition.setTransientLaunch(activity); + + // TODO(b/188669821): Remove once legacy recents behavior is moved to shell. + // Also interpret HOME transient launch as recents + if (activity.getActivityType() == ACTIVITY_TYPE_HOME) { + mCollectingTransition.addFlag(TRANSIT_FLAG_IS_RECENTS); + } } void legacyDetachNavigationBarFromApp(@NonNull IBinder token) { diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 2882a2391066..0862d9bedde0 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -30,6 +30,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.os.UserHandle.USER_NULL; import static android.view.SurfaceControl.Transaction; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; +import static android.view.WindowManager.TRANSIT_CHANGE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM; @@ -354,6 +355,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< throw new IllegalArgumentException("reparent: can't reparent to null " + this); } + if (newParent == this) { + throw new IllegalArgumentException("Can not reparent to itself " + this); + } + final WindowContainer oldParent = mParent; if (mParent == newParent) { throw new IllegalArgumentException("WC=" + this + " already child of " + mParent); @@ -2574,13 +2579,34 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mSurfaceFreezer.unfreeze(getPendingTransaction()); } + /** + * Initializes a change transition. See {@link SurfaceFreezer} for more information. + * + * For now, this will only be called for the following cases: + * 1. {@link Task} is changing windowing mode between fullscreen and freeform. + * 2. {@link TaskFragment} is organized and is changing window bounds. + * 3. {@link ActivityRecord} is reparented into an organized {@link TaskFragment}. + * + * This shouldn't be called on other {@link WindowContainer} unless there is a valid use case. + */ + void initializeChangeTransition(Rect startBounds) { + mDisplayContent.prepareAppTransition(TRANSIT_CHANGE); + mDisplayContent.mChangingContainers.add(this); + mSurfaceFreezer.freeze(getSyncTransaction(), startBounds); + } + ArraySet<WindowContainer> getAnimationSources() { return mSurfaceAnimationSources; } @Override public SurfaceControl getFreezeSnapshotTarget() { - return null; + // Only allow freezing if this window is in a TRANSIT_CHANGE + if (!mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CHANGE) + || !mDisplayContent.mChangingContainers.contains(this)) { + return null; + } + return getSurfaceControl(); } @Override @@ -3185,6 +3211,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return false; } + /** @return {@code true} if this is a container for embedded activities or tasks. */ + boolean isEmbedded() { + return false; + } + /** * @return {@code true} if this container's surface should be shown when it is created. */ diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 232c28369955..c5d7179be876 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -46,8 +46,6 @@ import static android.provider.Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COM import static android.provider.Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; -import static android.view.Surface.ROTATION_0; -import static android.view.Surface.ROTATION_270; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; @@ -326,13 +324,11 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; @@ -1054,6 +1050,10 @@ public class WindowManagerService extends IWindowManager.Stub final HighRefreshRateDenylist mHighRefreshRateDenylist; + // Maintainer of a collection of all possible DisplayInfo for all configurations of the + // logical displays. + final PossibleDisplayInfoMapper mPossibleDisplayInfoMapper; + // If true, only the core apps and services are being launched because the device // is in a special boot mode, such as being encrypted or waiting for a decryption password. // For example, when this flag is true, there will be no wallpaper service. @@ -1229,6 +1229,7 @@ public class WindowManagerService extends IWindowManager.Stub mInputManager = inputManager; // Must be before createDisplayContentLocked. mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); + mPossibleDisplayInfoMapper = new PossibleDisplayInfoMapper(mDisplayManagerInternal); mSurfaceControlFactory = surfaceControlFactory; mTransactionFactory = transactionFactory; @@ -7732,7 +7733,7 @@ public class WindowManagerService extends IWindowManager.Stub final WindowState currentFocus = displayContent.mCurrentFocus; if (currentFocus != null && currentFocus.mSession.mUid == uid && currentFocus.mSession.mPid == pid) { - return true; + return currentFocus.canBeImeTarget(); } } return false; @@ -8467,23 +8468,10 @@ public class WindowManagerService extends IWindowManager.Stub + " for getPossibleMaximumWindowMetrics"); return new ArrayList<>(); } - // TODO(181127261) DisplayInfo should be pushed from DisplayManager. - final DisplayContent dc = mRoot.getDisplayContent(displayId); - if (dc == null) { - Slog.e(TAG, "Invalid displayId " + displayId - + " for getPossibleMaximumWindowMetrics"); - return new ArrayList<>(); - } - // TODO(181127261) DisplayManager should provide a DisplayInfo for each rotation - DisplayInfo currentDisplayInfo = dc.getDisplayInfo(); - Set<DisplayInfo> displayInfoSet = new HashSet<>(); - for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) { - currentDisplayInfo.rotation = rotation; - // TODO(181127261) Retrieve the device state from display stack. - displayInfoSet.add(new DisplayInfo(currentDisplayInfo)); - } - return new ArrayList<DisplayInfo>(displayInfoSet); + // Retrieve the DisplayInfo for all possible rotations across all possible display + // layouts. + return List.copyOf(mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId)); } } finally { Binder.restoreCallingIdentity(origId); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 8bcd62dffca1..834b6e62305d 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -733,18 +733,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub tf1.setAdjacentTaskFragment(tf2); final Bundle bundle = hop.getLaunchOptions(); - final WindowContainerTransaction.TaskFragmentAdjacentOptions adjacentOptions = - bundle != null ? new WindowContainerTransaction.TaskFragmentAdjacentOptions( + final WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = + bundle != null ? new WindowContainerTransaction.TaskFragmentAdjacentParams( bundle) : null; - if (adjacentOptions == null) { + if (adjacentParams == null) { break; } tf1.setDelayLastActivityRemoval( - adjacentOptions.isDelayPrimaryLastActivityRemoval()); + adjacentParams.shouldDelayPrimaryLastActivityRemoval()); if (tf2 != null) { tf2.setDelayLastActivityRemoval( - adjacentOptions.isDelaySecondaryLastActivityRemoval()); + adjacentParams.shouldDelaySecondaryLastActivityRemoval()); } break; } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index cd29f0eb61a2..6eb2e8a2fd54 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -57,6 +57,7 @@ import android.content.res.Configuration; import android.os.Binder; import android.os.Build; import android.os.IBinder; +import android.os.LocaleList; import android.os.Message; import android.os.Process; import android.os.RemoteException; @@ -817,10 +818,13 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return false; } - void updateNightModeForAllActivities(int nightMode) { + // TODO(b/199277065): Re-assess how app-specific locales are applied based on UXR + // TODO(b/199277729): Consider whether we need to add special casing for edge cases like + // activity-embeddings etc. + void updateAppSpecificSettingsForAllActivities(Integer nightMode, LocaleList localesOverride) { for (int i = mActivities.size() - 1; i >= 0; --i) { final ActivityRecord r = mActivities.get(i); - if (r.setOverrideNightMode(nightMode) && r.mVisibleRequested) { + if (r.applyAppSpecificConfig(nightMode, localesOverride) && r.mVisibleRequested) { r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 22db2975ef41..2ccbf4070eba 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -372,6 +372,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private boolean mRedrawForSyncReported; /** + * {@code true} when the client was still drawing for sync when the sync-set was finished or + * cancelled. This can happen if the window goes away during a sync. In this situation we need + * to make sure to still apply the postDrawTransaction when it finishes to prevent the client + * from getting stuck in a bad state. + */ + boolean mClientWasDrawingForSync = false; + + /** * Special mode that is intended only for the rounded corner overlay: during rotation * transition, we un-rotate the window token such that the window appears as it did before the * rotation. @@ -1980,7 +1988,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final ActivityRecord atoken = mActivityRecord; return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE)) && isVisibleByPolicy() && !isParentWindowHidden() - && (atoken == null || atoken.mVisibleRequested) + && (atoken == null || atoken.isVisible()) && !mAnimatingExit && !mDestroying; } @@ -2706,6 +2714,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } + // Don't allow transient-launch activities to take IME. + if (rootTask != null && mActivityRecord != null + && mWmService.mAtmService.getTransitionController().isTransientLaunch( + mActivityRecord)) { + return false; + } + if (DEBUG_INPUT_METHOD) { Slog.i(TAG_WM, "isVisibleOrAdding " + this + ": " + isVisibleOrAdding()); if (!isVisibleOrAdding()) { @@ -6002,6 +6017,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return super.isSyncFinished(); } + @Override + void finishSync(Transaction outMergedTransaction, boolean cancel) { + if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mRedrawForSyncReported) { + mClientWasDrawingForSync = true; + } + super.finishSync(outMergedTransaction, cancel); + } + boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction) { if (mOrientationChangeRedrawRequestTime > 0) { final long duration = @@ -6017,8 +6040,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } executeDrawHandlers(postDrawTransaction); + + final boolean applyPostDrawNow = mClientWasDrawingForSync && postDrawTransaction != null; + mClientWasDrawingForSync = false; if (!onSyncFinishedDrawing()) { - return mWinAnimator.finishDrawingLocked(postDrawTransaction); + return mWinAnimator.finishDrawingLocked(postDrawTransaction, applyPostDrawNow); } if (mActivityRecord != null @@ -6032,7 +6058,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mSyncTransaction.merge(postDrawTransaction); } - mWinAnimator.finishDrawingLocked(null); + mWinAnimator.finishDrawingLocked(null, false /* forceApplyNow */); // We always want to force a traversal after a finish draw for blast sync. return true; } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index f25706a97fb6..a0dc2476da40 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -228,7 +228,8 @@ class WindowStateAnimator { } } - boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) { + boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction, + boolean forceApplyNow) { final boolean startingWindow = mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; if (startingWindow) { @@ -253,11 +254,11 @@ class WindowStateAnimator { // If there is no surface, the last draw was for the previous surface. We don't want to // wait until the new surface is shown and instead just apply the transaction right // away. - if (mLastHidden && mDrawState != NO_SURFACE) { + if (mLastHidden && mDrawState != NO_SURFACE && !forceApplyNow) { mPostDrawTransaction.merge(postDrawTransaction); layoutNeeded = true; } else { - postDrawTransaction.apply(); + mWin.getSyncTransaction().merge(postDrawTransaction); } } diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp index e319e3febc21..4190a91710fc 100644 --- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp +++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp @@ -55,6 +55,7 @@ using android::base::unique_fd; #define SYNC_RECEIVED_WHILE_FROZEN (1) #define ASYNC_RECEIVED_WHILE_FROZEN (2) +#define TXNS_PENDING_WHILE_FROZEN (4) namespace android { @@ -232,17 +233,20 @@ static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, job compactProcessOrFallback(pid, compactionFlags); } -static void com_android_server_am_CachedAppOptimizer_freezeBinder( +static jint com_android_server_am_CachedAppOptimizer_freezeBinder( JNIEnv *env, jobject clazz, jint pid, jboolean freeze) { - if (IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */) != 0) { + jint retVal = IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */); + if (retVal != 0 && retVal != -EAGAIN) { jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder"); } + + return retVal; } static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv *env, jobject clazz, jint pid) { - bool syncReceived = false, asyncReceived = false; + uint32_t syncReceived = 0, asyncReceived = 0; int error = IPCThreadState::getProcessFreezeInfo(pid, &syncReceived, &asyncReceived); @@ -252,13 +256,12 @@ static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv jint retVal = 0; - if(syncReceived) { - retVal |= SYNC_RECEIVED_WHILE_FROZEN;; - } - - if(asyncReceived) { - retVal |= ASYNC_RECEIVED_WHILE_FROZEN; - } + // bit 0 of sync_recv goes to bit 0 of retVal + retVal |= syncReceived & SYNC_RECEIVED_WHILE_FROZEN; + // bit 0 of async_recv goes to bit 1 of retVal + retVal |= (asyncReceived << 1) & ASYNC_RECEIVED_WHILE_FROZEN; + // bit 1 of sync_recv goes to bit 2 of retVal + retVal |= (syncReceived << 1) & TXNS_PENDING_WHILE_FROZEN; return retVal; } @@ -278,7 +281,7 @@ static const JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem}, {"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess}, - {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder}, + {"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder}, {"getBinderFreezeInfo", "(I)I", (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}, {"getFreezerCheckPath", "()Ljava/lang/String;", diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index a94ad4ad7ef2..bb9740b60f78 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -337,7 +337,7 @@ public: void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override; bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override; void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override; - void setPointerCapture(bool enabled) override; + void setPointerCapture(const PointerCaptureRequest& request) override; void notifyDropWindow(const sp<IBinder>& token, float x, float y) override; /* --- PointerControllerPolicyInterface implementation --- */ @@ -372,8 +372,8 @@ private: // Show touches feature enable/disable. bool showTouches; - // Pointer capture feature enable/disable. - bool pointerCapture; + // The latest request to enable or disable Pointer Capture. + PointerCaptureRequest pointerCaptureRequest; // Sprite controller singleton, created on first use. sp<SpriteController> spriteController; @@ -417,7 +417,6 @@ NativeInputManager::NativeInputManager(jobject contextObj, mLocked.pointerSpeed = 0; mLocked.pointerGesturesEnabled = true; mLocked.showTouches = false; - mLocked.pointerCapture = false; mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT; } mInteractive = true; @@ -446,7 +445,9 @@ void NativeInputManager::dump(std::string& dump) { dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n", toString(mLocked.pointerGesturesEnabled)); dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches)); - dump += StringPrintf(INDENT "Pointer Capture Enabled: %s\n", toString(mLocked.pointerCapture)); + dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n", + mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled", + mLocked.pointerCaptureRequest.seq); } dump += "\n"; @@ -634,7 +635,7 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon outConfig->showTouches = mLocked.showTouches; - outConfig->pointerCapture = mLocked.pointerCapture; + outConfig->pointerCaptureRequest = mLocked.pointerCaptureRequest; outConfig->setDisplayViewports(mLocked.viewports); @@ -1383,16 +1384,16 @@ void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedTok checkAndClearExceptionFromCallback(env, "onPointerDownOutsideFocus"); } -void NativeInputManager::setPointerCapture(bool enabled) { +void NativeInputManager::setPointerCapture(const PointerCaptureRequest& request) { { // acquire lock AutoMutex _l(mLock); - if (mLocked.pointerCapture == enabled) { + if (mLocked.pointerCaptureRequest == request) { return; } - ALOGV("%s pointer capture.", enabled ? "Enabling" : "Disabling"); - mLocked.pointerCapture = enabled; + ALOGV("%s pointer capture.", request.enable ? "Enabling" : "Disabling"); + mLocked.pointerCaptureRequest = request; } // release lock mInputManager->getReader()->requestRefreshConfiguration( diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java index 8ea21ec74ad6..a3017990543f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java @@ -46,6 +46,12 @@ public class DevicePolicyCacheImpl extends DevicePolicyCache { @GuardedBy("mLock") private final SparseIntArray mPermissionPolicy = new SparseIntArray(); + /** Maps to {@code ActiveAdmin.mAdminCanGrantSensorsPermissions}. + * + * <p>For users affiliated with the device, they inherit the policy from {@code DO} so + * it will map to the {@code DO}'s policy. Otherwise it will map to the admin of the requesting + * user. + */ @GuardedBy("mLock") private final SparseBooleanArray mCanGrantSensorsPermissions = new SparseBooleanArray(); @@ -102,17 +108,16 @@ public class DevicePolicyCacheImpl extends DevicePolicyCache { } @Override - public boolean canAdminGrantSensorsPermissionsForUser(@UserIdInt int userHandle) { + public boolean canAdminGrantSensorsPermissionsForUser(@UserIdInt int userId) { synchronized (mLock) { - return mCanGrantSensorsPermissions.get(userHandle, false); + return mCanGrantSensorsPermissions.get(userId, false); } } /** Sets ahmin control over permission grants for user. */ - public void setAdminCanGrantSensorsPermissions(@UserIdInt int userHandle, - boolean canGrant) { + public void setAdminCanGrantSensorsPermissions(@UserIdInt int userId, boolean canGrant) { synchronized (mLock) { - mCanGrantSensorsPermissions.put(userHandle, canGrant); + mCanGrantSensorsPermissions.put(userId, canGrant); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 70219d2503a3..6b4b0c94f657 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -9144,9 +9144,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } /** - * Returns the ActiveAdmin associated wit the PO or DO on the given user. - * @param userHandle - * @return + * Returns the ActiveAdmin associated with the PO or DO on the given user. */ private @Nullable ActiveAdmin getDeviceOrProfileOwnerAdminLocked(int userHandle) { ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle); @@ -14305,6 +14303,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { maybePauseDeviceWideLoggingLocked(); maybeResumeDeviceWideLoggingLocked(); maybeClearLockTaskPolicyLocked(); + updateAdminCanGrantSensorsPermissionCache(callingUserId); } } @@ -16968,6 +16967,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public void clearOrganizationIdForUser(int userHandle) { + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + + synchronized (getLockObject()) { + final ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userHandle); + owner.mOrganizationId = null; + owner.mEnrollmentSpecificId = null; + saveSettingsLocked(userHandle); + } + } + + @Override public UserHandle createAndProvisionManagedProfile( @NonNull ManagedProfileProvisioningParams provisioningParams, @NonNull String callerPackage) { @@ -17469,7 +17481,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { }); } - private void setAdminCanGrantSensorsPermissionForUserUnchecked(int userId, boolean canGrant) { + private void setAdminCanGrantSensorsPermissionForUserUnchecked(@UserIdInt int userId, + boolean canGrant) { + Slogf.d(LOG_TAG, "setAdminCanGrantSensorsPermissionForUserUnchecked(%d, %b)", + userId, canGrant); synchronized (getLockObject()) { ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId); @@ -17483,10 +17498,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void updateAdminCanGrantSensorsPermissionCache(int userId) { + private void updateAdminCanGrantSensorsPermissionCache(@UserIdInt int userId) { synchronized (getLockObject()) { - ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId); - final boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false; + + ActiveAdmin owner; + // If the user is affiliated the device (either a DO itself, or an affiliated PO), + // use mAdminCanGrantSensorsPermissions from the DO + if (isUserAffiliatedWithDeviceLocked(userId)) { + owner = getDeviceOwnerAdminLocked(); + } else { + owner = getDeviceOrProfileOwnerAdminLocked(userId); + } + boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false; mPolicyCache.setAdminCanGrantSensorsPermissions(userId, canGrant); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java new file mode 100644 index 000000000000..dc39b6d573db --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import android.hardware.biometrics.BiometricOverlayConstants; +import android.hardware.fingerprint.ISidefpsController; +import android.hardware.fingerprint.IUdfpsOverlayController; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; + +@Presubmit +@SmallTest +public class SensorOverlaysTest { + + private static final int SENSOR_ID = 11; + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock private IUdfpsOverlayController mUdfpsOverlayController; + @Mock private ISidefpsController mSidefpsController; + @Mock private AcquisitionClient<?> mAcquisitionClient; + + @Test + public void noopWhenBothNull() { + final SensorOverlays useless = new SensorOverlays(null, null); + useless.show(SENSOR_ID, 2, null); + useless.hide(SENSOR_ID); + } + + @Test + public void testProvidesUdfps() { + final List<IUdfpsOverlayController> udfps = new ArrayList<>(); + SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController); + + sensorOverlays.ifUdfps(udfps::add); + assertThat(udfps).isEmpty(); + + sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController); + sensorOverlays.ifUdfps(udfps::add); + assertThat(udfps).containsExactly(mUdfpsOverlayController); + } + + @Test + public void testShow() throws Exception { + testShow(mUdfpsOverlayController, mSidefpsController); + } + + @Test + public void testShowUdfps() throws Exception { + testShow(mUdfpsOverlayController, null); + } + + @Test + public void testShowSidefps() throws Exception { + testShow(null, mSidefpsController); + } + + private void testShow(IUdfpsOverlayController udfps, ISidefpsController sidefps) + throws Exception { + final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps); + final int reason = BiometricOverlayConstants.REASON_UNKNOWN; + sensorOverlays.show(SENSOR_ID, reason, mAcquisitionClient); + + if (udfps != null) { + verify(mUdfpsOverlayController).showUdfpsOverlay(eq(SENSOR_ID), eq(reason), any()); + } + if (sidefps != null) { + verify(mSidefpsController).show(eq(SENSOR_ID), eq(reason)); + } + } + + @Test + public void testHide() throws Exception { + testHide(mUdfpsOverlayController, mSidefpsController); + } + + @Test + public void testHideUdfps() throws Exception { + testHide(mUdfpsOverlayController, null); + } + + @Test + public void testHideSidefps() throws Exception { + testHide(null, mSidefpsController); + } + + private void testHide(IUdfpsOverlayController udfps, ISidefpsController sidefps) + throws Exception { + final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps); + sensorOverlays.hide(SENSOR_ID); + + if (udfps != null) { + verify(mUdfpsOverlayController).hideUdfpsOverlay(eq(SENSOR_ID)); + } + if (sidefps != null) { + verify(mSidefpsController).hide(eq(SENSOR_ID)); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java index b51918e24b13..8b7c90d985b5 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java @@ -19,10 +19,13 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; import android.hardware.biometrics.common.CommonProps; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.SensorLocation; @@ -56,6 +59,8 @@ public class FingerprintProviderTest { @Mock private Context mContext; @Mock + private Resources mResources; + @Mock private UserManager mUserManager; @Mock private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; @@ -74,19 +79,21 @@ public class FingerprintProviderTest { public void setUp() { MockitoAnnotations.initMocks(this); + when(mContext.getResources()).thenReturn(mResources); + when(mResources.obtainTypedArray(anyInt())).thenReturn(mock(TypedArray.class)); when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>()); final SensorProps sensor1 = new SensorProps(); sensor1.commonProps = new CommonProps(); sensor1.commonProps.sensorId = 0; - sensor1.sensorLocations = new SensorLocation[] {new SensorLocation()}; + sensor1.sensorLocations = new SensorLocation[]{new SensorLocation()}; final SensorProps sensor2 = new SensorProps(); sensor2.commonProps = new CommonProps(); sensor2.commonProps.sensorId = 1; - sensor2.sensorLocations = new SensorLocation[] {new SensorLocation()}; + sensor2.sensorLocations = new SensorLocation[]{new SensorLocation()}; - mSensorProps = new SensorProps[] {sensor1, sensor2}; + mSensorProps = new SensorProps[]{sensor1, sensor2}; mLockoutResetDispatcher = new LockoutResetDispatcher(mContext); diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index b1b6e5341f38..2d2c6a34f475 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -18,6 +18,9 @@ package com.android.server.devicestate; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE; +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @@ -36,8 +39,6 @@ import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; -import static org.mockito.Mockito.mock; - import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowProcessController; @@ -181,8 +182,10 @@ public final class DeviceStateManagerServiceTest { assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); + assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE, + OTHER_DEVICE_STATE); - mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE }); + mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE}); flushHandler(); // The current committed and requests states do not change because the current state remains @@ -190,9 +193,10 @@ public final class DeviceStateManagerServiceTest { assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); + assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE); assertArrayEquals(callback.getLastNotifiedInfo().supportedStates, - new int[] { DEFAULT_DEVICE_STATE.getIdentifier() }); + new int[]{DEFAULT_DEVICE_STATE.getIdentifier()}); } @Test @@ -207,9 +211,11 @@ public final class DeviceStateManagerServiceTest { assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); + assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE, + OTHER_DEVICE_STATE); - mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE, - OTHER_DEVICE_STATE }); + mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE, + OTHER_DEVICE_STATE}); flushHandler(); // The current committed and requests states do not change because the current state remains @@ -217,6 +223,8 @@ public final class DeviceStateManagerServiceTest { assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); + assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE, + OTHER_DEVICE_STATE); // The callback wasn't notified about a change in supported states as the states have not // changed. diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java index 8279624f6b97..fbc1952b0faf 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -16,12 +16,17 @@ package com.android.server.display; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.DEFAULT_DISPLAY_GROUP; + import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED; import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED; import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED; import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.mockito.ArgumentMatchers.eq; @@ -55,6 +60,7 @@ import org.mockito.MockitoAnnotations; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; +import java.util.Set; @SmallTest @Presubmit @@ -123,14 +129,14 @@ public class LogicalDisplayMapperTest { // add LogicalDisplay displayAdded = add(device); assertEquals(info(displayAdded).address, info(device).address); - assertEquals(Display.DEFAULT_DISPLAY, id(displayAdded)); + assertEquals(DEFAULT_DISPLAY, id(displayAdded)); // remove mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED); verify(mListenerMock).onLogicalDisplayEventLocked( mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED)); LogicalDisplay displayRemoved = mDisplayCaptor.getValue(); - assertEquals(Display.DEFAULT_DISPLAY, id(displayRemoved)); + assertEquals(DEFAULT_DISPLAY, id(displayRemoved)); assertEquals(displayAdded, displayRemoved); } @@ -155,11 +161,11 @@ public class LogicalDisplayMapperTest { LogicalDisplay display1 = add(device1); assertEquals(info(display1).address, info(device1).address); - assertNotEquals(Display.DEFAULT_DISPLAY, id(display1)); + assertNotEquals(DEFAULT_DISPLAY, id(display1)); LogicalDisplay display2 = add(device2); assertEquals(info(display2).address, info(device2).address); - assertEquals(Display.DEFAULT_DISPLAY, id(display2)); + assertEquals(DEFAULT_DISPLAY, id(display2)); } @Test @@ -171,12 +177,12 @@ public class LogicalDisplayMapperTest { LogicalDisplay display1 = add(device1); assertEquals(info(display1).address, info(device1).address); - assertEquals(Display.DEFAULT_DISPLAY, id(display1)); + assertEquals(DEFAULT_DISPLAY, id(display1)); LogicalDisplay display2 = add(device2); assertEquals(info(display2).address, info(device2).address); // Despite the flags, we can only have one default display - assertNotEquals(Display.DEFAULT_DISPLAY, id(display2)); + assertNotEquals(DEFAULT_DISPLAY, id(display2)); } @Test @@ -189,7 +195,67 @@ public class LogicalDisplayMapperTest { int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID); assertEquals(3, ids.length); Arrays.sort(ids); - assertEquals(Display.DEFAULT_DISPLAY, ids[0]); + assertEquals(DEFAULT_DISPLAY, ids[0]); + } + + @Test + public void testGetDisplayInfoForStateLocked_oneDisplayGroup_internalType() { + add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + add(createDisplayDevice(Display.TYPE_INTERNAL, 700, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + + Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked( + DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP); + assertThat(displayInfos.size()).isEqualTo(3); + for (DisplayInfo displayInfo : displayInfos) { + assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY); + assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP); + assertThat(displayInfo.logicalWidth).isAnyOf(600, 200, 700); + assertThat(displayInfo.logicalHeight).isEqualTo(800); + } + } + + @Test + public void testGetDisplayInfoForStateLocked_oneDisplayGroup_differentTypes() { + add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + add(createDisplayDevice(Display.TYPE_EXTERNAL, 700, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + + Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked( + DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP); + assertThat(displayInfos.size()).isEqualTo(2); + for (DisplayInfo displayInfo : displayInfos) { + assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY); + assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP); + assertThat(displayInfo.logicalWidth).isAnyOf(600, 200); + assertThat(displayInfo.logicalHeight).isEqualTo(800); + } + } + + @Test + public void testGetDisplayInfoForStateLocked_multipleDisplayGroups_defaultGroup() { + add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800, + DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY)); + add(createDisplayDevice(Display.TYPE_VIRTUAL, 700, 800, + DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP)); + + Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked( + DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP); + assertThat(displayInfos.size()).isEqualTo(2); + for (DisplayInfo displayInfo : displayInfos) { + assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY); + assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP); + assertThat(displayInfo.logicalWidth).isAnyOf(600, 200); + assertThat(displayInfo.logicalHeight).isEqualTo(800); + } } @Test @@ -199,11 +265,11 @@ public class LogicalDisplayMapperTest { LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0)); LogicalDisplay display3 = add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0)); - assertEquals(Display.DEFAULT_DISPLAY_GROUP, + assertEquals(DEFAULT_DISPLAY_GROUP, mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1))); - assertEquals(Display.DEFAULT_DISPLAY_GROUP, + assertEquals(DEFAULT_DISPLAY_GROUP, mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2))); - assertEquals(Display.DEFAULT_DISPLAY_GROUP, + assertEquals(DEFAULT_DISPLAY_GROUP, mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3))); } @@ -218,11 +284,11 @@ public class LogicalDisplayMapperTest { DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP); LogicalDisplay display3 = add(device3); - assertEquals(Display.DEFAULT_DISPLAY_GROUP, + assertEquals(DEFAULT_DISPLAY_GROUP, mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1))); - assertEquals(Display.DEFAULT_DISPLAY_GROUP, + assertEquals(DEFAULT_DISPLAY_GROUP, mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display2))); - assertNotEquals(Display.DEFAULT_DISPLAY_GROUP, + assertNotEquals(DEFAULT_DISPLAY_GROUP, mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3))); // Now switch it back to the default group by removing the flag and issuing an update @@ -231,7 +297,7 @@ public class LogicalDisplayMapperTest { mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED); // Verify the new group is correct. - assertEquals(Display.DEFAULT_DISPLAY_GROUP, + assertEquals(DEFAULT_DISPLAY_GROUP, mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display3))); } @@ -287,14 +353,14 @@ public class LogicalDisplayMapperTest { // add LogicalDisplay displayAdded = add(device); assertEquals(info(displayAdded).address, info(device).address); - assertNotEquals(Display.DEFAULT_DISPLAY, id(displayAdded)); + assertNotEquals(DEFAULT_DISPLAY, id(displayAdded)); // remove mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED); verify(mListenerMock).onLogicalDisplayEventLocked( mDisplayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_REMOVED)); LogicalDisplay displayRemoved = mDisplayCaptor.getValue(); - assertNotEquals(Display.DEFAULT_DISPLAY, id(displayRemoved)); + assertNotEquals(DEFAULT_DISPLAY, id(displayRemoved)); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 9ad479a261ef..9a14e7d94e31 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -2724,9 +2724,11 @@ public class ActivityRecordTests extends WindowTestsBase { mAtm, null /* fragmentToken */, false /* createdByOrganizer */); fragmentSetup.accept(taskFragment2, new Rect(width / 2, 0, width, height)); task.addChild(taskFragment2, POSITION_TOP); - final ActivityRecord activity2 = new ActivityBuilder(mAtm).build(); + final ActivityRecord activity2 = new ActivityBuilder(mAtm) + .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE).build(); activity2.mVisibleRequested = true; taskFragment2.addChild(activity2); + assertTrue(activity2.isResizeable()); activity1.reparent(taskFragment1, POSITION_TOP); assertEquals(task, activity1.mStartingData.mAssociatedTask); 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 b95d56b58d06..764f63dcd013 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -43,13 +43,17 @@ import static org.mockito.Mockito.when; import android.app.Activity; import android.app.ActivityManager; +import android.app.IApplicationThread; import android.app.PictureInPictureParams; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.EnterPipRequestedItem; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.Binder; import android.os.IBinder; +import android.os.LocaleList; import android.os.PowerManager; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; @@ -61,6 +65,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import org.mockito.MockitoSession; import java.util.ArrayList; @@ -80,6 +85,9 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { private final ArgumentCaptor<ClientTransaction> mClientTransactionCaptor = ArgumentCaptor.forClass(ClientTransaction.class); + private static final String DEFAULT_PACKAGE_NAME = "my.application.package"; + private static final int DEFAULT_USER_ID = 100; + @Before public void setUp() throws Exception { setBooted(mAtm); @@ -301,6 +309,15 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { // The top app should not change while sleeping. assertEquals(topActivity.app, mAtm.mInternal.getTopApp()); + mAtm.startLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY + | ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY); + assertEquals(ActivityManager.PROCESS_STATE_TOP, mAtm.mInternal.getTopProcessState()); + // Because there is no unknown visibility record, the state will be restored if other + // reasons are all done. + mAtm.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY); + assertEquals(ActivityManager.PROCESS_STATE_TOP_SLEEPING, + mAtm.mInternal.getTopProcessState()); + // If all activities are stopped, the sleep wake lock must be released. final Task topRootTask = topActivity.getRootTask(); doReturn(true).when(rootHomeTask).goToSleepIfPossible(anyBoolean()); @@ -480,5 +497,269 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { assertTrue(activity.supportsMultiWindow()); assertTrue(task.supportsMultiWindow()); } + + @Test + public void testPackageConfigUpdate_locales_successfullyApplied() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID)); + + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit(); + + WindowProcessController wpcAfterConfigChange = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange.getConfiguration().getLocales()); + assertFalse(wpcAfterConfigChange.getConfiguration().isNightModeActive()); + } + + @Test + public void testPackageConfigUpdate_nightMode_successfullyApplied() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID)); + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + WindowProcessController wpcAfterConfigChange = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertTrue(wpcAfterConfigChange.getConfiguration().isNightModeActive()); + assertEquals(LocaleList.forLanguageTags("en-XC"), + wpcAfterConfigChange.getConfiguration().getLocales()); + } + + @Test + public void testPackageConfigUpdate_multipleLocaleUpdates_successfullyApplied() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + WindowProcessController wpc = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), wpc); + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) + .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + WindowProcessController wpcAfterConfigChange1 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange1.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpc.getConfiguration().getLocales()); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("ja-XC,en-XC")).commit(); + + WindowProcessController wpcAfterConfigChange2 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + + assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"), + wpcAfterConfigChange2.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"), + wpc.getConfiguration().getLocales()); + } + + @Test + public void testPackageConfigUpdate_multipleNightModeUpdates_successfullyApplied() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID)); + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) + .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + WindowProcessController wpcAfterConfigChange1 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange1.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + + packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_NO).commit(); + + WindowProcessController wpcAfterConfigChange2 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange2.getConfiguration().getLocales()); + assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive()); + } + + @Test + public void testPackageConfigUpdate_onPackageUninstall_configShouldNotApply() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID)); + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) + .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + WindowProcessController wpcAfterConfigChange1 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange1.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + + mAtm.mInternal.onPackageUninstalled(DEFAULT_PACKAGE_NAME); + + WindowProcessController wpcAfterConfigChange2 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XC"), + wpcAfterConfigChange2.getConfiguration().getLocales()); + assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive()); + } + + @Test + public void testPackageConfigUpdate_LocalesEmptyAndNightModeUndefined_configShouldNotApply() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + WindowProcessController wpc = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), wpc); + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) + .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + WindowProcessController wpcAfterConfigChange1 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange1.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpc.getConfiguration().getLocales()); + + packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()) + .setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit(); + + WindowProcessController wpcAfterConfigChange2 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XC"), + wpcAfterConfigChange2.getConfiguration().getLocales()); + assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive()); + assertEquals(LocaleList.forLanguageTags("en-XC"), + wpc.getConfiguration().getLocales()); + } + + @Test + public void testPackageConfigUpdate_WhenUserRemoved_configShouldNotApply() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID)); + + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) + .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + WindowProcessController wpcAfterConfigChange1 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange1.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + + mAtm.mInternal.removeUser(DEFAULT_USER_ID); + + WindowProcessController wpcAfterConfigChange2 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XC"), + wpcAfterConfigChange2.getConfiguration().getLocales()); + assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive()); + } + + @Test + public void testPackageConfigUpdate_setLocaleListToEmpty_doesNotOverlayLocaleListInWpc() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID)); + + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) + .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + WindowProcessController wpcAfterConfigChange1 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange1.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + + packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()).commit(); + + WindowProcessController wpcAfterConfigChange2 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XC"), + wpcAfterConfigChange2.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange2.getConfiguration().isNightModeActive()); + } + + @Test + public void testPackageConfigUpdate_resetNightMode_doesNotOverrideNightModeInWpc() { + Configuration config = mAtm.getGlobalConfiguration(); + config.setLocales(LocaleList.forLanguageTags("en-XC")); + mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID); + mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID)); + + ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater = + mAtm.mInternal.createPackageConfigurationUpdater(); + + packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")) + .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit(); + + WindowProcessController wpcAfterConfigChange1 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange1.getConfiguration().getLocales()); + assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive()); + + packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit(); + + WindowProcessController wpcAfterConfigChange2 = createWindowProcessController( + DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"), + wpcAfterConfigChange2.getConfiguration().getLocales()); + assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive()); + } + + private WindowProcessController createWindowProcessController(String packageName, + int userId) { + WindowProcessListener mMockListener = Mockito.mock(WindowProcessListener.class); + ApplicationInfo info = mock(ApplicationInfo.class); + info.packageName = packageName; + WindowProcessController wpc = new WindowProcessController( + mAtm, info, packageName, 0, userId, null, mMockListener); + wpc.setThread(mock(IApplicationThread.class)); + return wpc; + } + } + + diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 53bae4156d6b..0d0cec7f5dce 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -103,6 +103,20 @@ public class AppTransitionControllerTest extends WindowTestsBase { } @Test + public void testClearTaskSkipAppExecuteTransition() { + final ActivityRecord behind = createActivityRecord(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final Task task = behind.getTask(); + final ActivityRecord top = createActivityRecord(task); + top.setState(ActivityRecord.State.RESUMED, "test"); + behind.setState(ActivityRecord.State.STARTED, "test"); + behind.mVisibleRequested = true; + + task.performClearTask("test"); + assertFalse(mDisplayContent.mAppTransition.isReady()); + } + + @Test public void testTranslucentOpen() { final ActivityRecord behind = createActivityRecord(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index a0a3ce73265c..405d714256ab 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -43,6 +43,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -54,6 +55,8 @@ import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.WindowManager; +import android.window.ITaskFragmentOrganizer; +import android.window.TaskFragmentOrganizer; import androidx.test.filters.SmallTest; @@ -391,6 +394,35 @@ public class AppTransitionTests extends WindowTestsBase { mDc.mAppTransition.getAnimationStyleResId(attrs)); } + @Test + public void testActivityRecordReparentToTaskFragment() { + final ActivityRecord activity = createActivityRecord(mDc); + activity.setVisibility(true); + final Task task = activity.getTask(); + + // Add a TaskFragment of half of the Task size. + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final ITaskFragmentOrganizer iOrganizer = + ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()); + mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .setOrganizer(organizer) + .build(); + final Rect taskBounds = new Rect(); + task.getBounds(taskBounds); + taskFragment.setBounds(0, 0, taskBounds.right / 2, taskBounds.bottom); + + assertTrue(mDc.mChangingContainers.isEmpty()); + assertFalse(mDc.mAppTransition.isTransitionSet()); + + // Schedule app transition when reparent activity to a TaskFragment of different size. + activity.reparent(taskFragment, POSITION_TOP); + + assertTrue(mDc.mChangingContainers.contains(activity)); + assertTrue(mDc.mAppTransition.containsTransitRequest(TRANSIT_CHANGE)); + } + private class TestRemoteAnimationRunner implements IRemoteAnimationRunner { boolean mCancelled = false; @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 4ee0c6090504..e3402177140d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1572,6 +1572,12 @@ public class DisplayContentTests extends WindowTestsBase { mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation(); assertTrue(displayRotation.updateRotationUnchecked(false)); + // Rotation can be updated if the policy is not ok to animate (e.g. going to sleep). + mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity); + displayRotation.setRotation((displayRotation.getRotation() + 1) % 4); + ((TestWindowManagerPolicy) mWm.mPolicy).mOkToAnimate = false; + assertTrue(displayRotation.updateRotationUnchecked(false)); + // Rotation can be updated if the recents animation is animating but it is not on top, e.g. // switching activities in different orientations by quickstep gesture. mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity); @@ -2173,6 +2179,21 @@ public class DisplayContentTests extends WindowTestsBase { } @Test + public void testKeyguardGoingAwayWhileAodShown() { + mDisplayContent.getDisplayPolicy().setAwake(true); + + final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin"); + final ActivityRecord activity = appWin.mActivityRecord; + + mAtm.mKeyguardController.setKeyguardShown(true /* keyguardShowing */, + true /* aodShowing */); + assertFalse(activity.isVisibleRequested()); + + mAtm.mKeyguardController.keyguardGoingAway(0 /* flags */); + assertTrue(activity.isVisibleRequested()); + } + + @Test public void testRemoveRootTaskInWindowingModes() { removeRootTaskTests(() -> mRootWindowContainer.removeRootTasksInWindowingModes( WINDOWING_MODE_FULLSCREEN)); @@ -2319,11 +2340,10 @@ public class DisplayContentTests extends WindowTestsBase { // mirror. setUpDefaultTaskDisplayAreaWindowToken(); - // GIVEN SurfaceControl can successfully mirror the provided surface. + // GIVEN SurfaceControl does not mirror a null surface. Point surfaceSize = new Point( mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(), mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height()); - surfaceControlMirrors(surfaceSize); // GIVEN a new VirtualDisplay with an associated surface. final VirtualDisplay display = createVirtualDisplay(surfaceSize, null /* surface */); diff --git a/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java new file mode 100644 index 000000000000..6e0056821aab --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/PossibleDisplayInfoMapperTests.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.FLAG_PRESENTATION; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; +import android.view.DisplayInfo; + +import androidx.test.filters.MediumTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Set; + + +/** + * Tests for {@link PossibleDisplayInfoMapper}. + * + * Build/Install/Run: + * atest WmTests:PossibleDisplayInfoMapperTests + */ +@MediumTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class PossibleDisplayInfoMapperTests extends WindowTestsBase { + + private PossibleDisplayInfoMapper mDisplayInfoMapper; + private final Set<DisplayInfo> mPossibleDisplayInfo = new ArraySet<>(); + private DisplayInfo mDefaultDisplayInfo; + private DisplayInfo mSecondDisplayInfo; + + @Before + public void setUp() throws Exception { + mDisplayInfoMapper = mWm.mPossibleDisplayInfoMapper; + final DisplayInfo baseDisplayInfo = mWm.mRoot.getDisplayContent( + DEFAULT_DISPLAY).getDisplayInfo(); + when(mWm.mDisplayManagerInternal.getPossibleDisplayInfo(anyInt())).thenReturn( + mPossibleDisplayInfo); + + mDefaultDisplayInfo = new DisplayInfo(baseDisplayInfo); + initializeDisplayInfo(mDefaultDisplayInfo, DEFAULT_DISPLAY, new Rect(0, 0, 500, 800)); + mSecondDisplayInfo = new DisplayInfo(baseDisplayInfo); + // Use the same display id for any display in the same group, due to the assumption that + // any display in the same grouped can be swapped out for each other (while maintaining the + // display id). + initializeDisplayInfo(mSecondDisplayInfo, DEFAULT_DISPLAY, new Rect(0, 0, 600, 1600)); + mSecondDisplayInfo.flags |= FLAG_PRESENTATION; + } + + @Test + public void testInitialization_isEmpty() { + // Empty after initializing. + assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY)).isEmpty(); + + // Still empty after updating. + mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); + assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY)).isEmpty(); + } + + @Test + public void testUpdatePossibleDisplayInfos_singleDisplay() { + mPossibleDisplayInfo.add(mDefaultDisplayInfo); + mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); + + Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY); + // An entry for each possible rotation, for a display that can be in a single state. + assertThat(displayInfos.size()).isEqualTo(4); + assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo); + } + + @Test + public void testUpdatePossibleDisplayInfos_secondDisplayAdded_sameGroup() { + mPossibleDisplayInfo.add(mDefaultDisplayInfo); + mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); + + assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(4); + + // Add another display layout to the set of supported states. + mPossibleDisplayInfo.add(mSecondDisplayInfo); + mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); + + Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY); + Set<DisplayInfo> defaultDisplayInfos = new ArraySet<>(); + Set<DisplayInfo> secondDisplayInfos = new ArraySet<>(); + for (DisplayInfo di : displayInfos) { + if ((di.flags & FLAG_PRESENTATION) != 0) { + secondDisplayInfos.add(di); + } else { + defaultDisplayInfos.add(di); + } + } + // An entry for each possible rotation, for the default display. + assertThat(defaultDisplayInfos).hasSize(4); + assertPossibleDisplayInfoEntries(defaultDisplayInfos, mDefaultDisplayInfo); + + // An entry for each possible rotation, for the second display. + assertThat(secondDisplayInfos).hasSize(4); + assertPossibleDisplayInfoEntries(secondDisplayInfos, mSecondDisplayInfo); + } + + @Test + public void testUpdatePossibleDisplayInfos_secondDisplayAdded_differentGroup() { + mPossibleDisplayInfo.add(mDefaultDisplayInfo); + mDisplayInfoMapper.updatePossibleDisplayInfos(DEFAULT_DISPLAY); + + assertThat(mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY).size()).isEqualTo(4); + + // Add another display to a different group. + mSecondDisplayInfo.displayId = DEFAULT_DISPLAY + 1; + mSecondDisplayInfo.displayGroupId = mDefaultDisplayInfo.displayGroupId + 1; + mPossibleDisplayInfo.add(mSecondDisplayInfo); + mDisplayInfoMapper.updatePossibleDisplayInfos(mSecondDisplayInfo.displayId); + + Set<DisplayInfo> displayInfos = mDisplayInfoMapper.getPossibleDisplayInfos(DEFAULT_DISPLAY); + // An entry for each possible rotation, for the default display. + assertThat(displayInfos).hasSize(4); + assertPossibleDisplayInfoEntries(displayInfos, mDefaultDisplayInfo); + + Set<DisplayInfo> secondStateEntries = + mDisplayInfoMapper.getPossibleDisplayInfos(mSecondDisplayInfo.displayId); + // An entry for each possible rotation, for the second display. + assertThat(secondStateEntries).hasSize(4); + assertPossibleDisplayInfoEntries(secondStateEntries, mSecondDisplayInfo); + } + + private static void initializeDisplayInfo(DisplayInfo outDisplayInfo, int displayId, + Rect logicalBounds) { + outDisplayInfo.displayId = displayId; + outDisplayInfo.rotation = ROTATION_0; + outDisplayInfo.logicalWidth = logicalBounds.width(); + outDisplayInfo.logicalHeight = logicalBounds.height(); + } + + private static void assertPossibleDisplayInfoEntries(Set<DisplayInfo> displayInfos, + DisplayInfo expectedDisplayInfo) { + boolean[] seenEveryRotation = new boolean[4]; + for (DisplayInfo displayInfo : displayInfos) { + final int rotation = displayInfo.rotation; + seenEveryRotation[rotation] = true; + assertThat(displayInfo.displayId).isEqualTo(expectedDisplayInfo.displayId); + assertEqualsRotatedDisplayInfo(displayInfo, expectedDisplayInfo); + } + assertThat(seenEveryRotation).isEqualTo(new boolean[]{true, true, true, true}); + } + + private static void assertEqualsRotatedDisplayInfo(DisplayInfo actual, DisplayInfo expected) { + if (actual.rotation == ROTATION_0 || actual.rotation == ROTATION_180) { + assertThat(actual.logicalWidth).isEqualTo(expected.logicalWidth); + assertThat(actual.logicalHeight).isEqualTo(expected.logicalHeight); + } else { + assertThat(actual.logicalWidth).isEqualTo(expected.logicalHeight); + assertThat(actual.logicalHeight).isEqualTo(expected.logicalWidth); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 9160109e7e7f..17ae2e8f4809 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -797,6 +797,9 @@ public class WindowOrganizerTests extends WindowTestsBase { public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { } @Override + public void onImeDrawnOnTask(int taskId) throws RemoteException { + } + @Override public void onAppSplashScreenViewRemoved(int taskId) { } }; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java index d3f2d1407a46..c56b6141a652 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -34,6 +34,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -49,6 +50,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.ServiceInfo; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.LocaleList; import android.platform.test.annotations.Presubmit; import org.junit.Before; @@ -371,8 +373,9 @@ public class WindowProcessControllerTests extends WindowTestsBase { public void testTopActivityUiModeChangeScheduleConfigChange() { final ActivityRecord activity = createActivityRecord(mWpc); activity.mVisibleRequested = true; - doReturn(true).when(activity).setOverrideNightMode(anyInt()); - mWpc.updateNightModeForAllActivities(Configuration.UI_MODE_NIGHT_YES); + doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any()); + mWpc.updateAppSpecificSettingsForAllActivities(Configuration.UI_MODE_NIGHT_YES, + LocaleList.forLanguageTags("en-XA")); verify(activity).ensureActivityConfiguration(anyInt(), anyBoolean()); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 734172fc1549..f05dd636aa22 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -272,13 +272,11 @@ final class HotwordDetectionConnection { Slog.v(TAG, "cancelLocked"); clearDebugHotwordLoggingTimeoutLocked(); mDebugHotwordLogging = false; - if (mRemoteHotwordDetectionService.isBound()) { - mRemoteHotwordDetectionService.unbind(); - LocalServices.getService(PermissionManagerServiceInternal.class) - .setHotwordDetectionServiceProvider(null); - mIdentity = null; - updateServiceUidForAudioPolicy(Process.INVALID_UID); - } + mRemoteHotwordDetectionService.unbind(); + LocalServices.getService(PermissionManagerServiceInternal.class) + .setHotwordDetectionServiceProvider(null); + mIdentity = null; + updateServiceUidForAudioPolicy(Process.INVALID_UID); mCancellationTaskFuture.cancel(/* may interrupt */ true); if (mAudioFlinger != null) { mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index abab426b3252..d5be4f36145a 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -14302,7 +14302,8 @@ public class TelephonyManager { if (callForwardingInfo.getNumber() == null) { throw new IllegalArgumentException("callForwarding number is null"); } - if (callForwardingInfo.getTimeoutSeconds() <= 0) { + if (callForwardingReason == CallForwardingInfo.REASON_NO_REPLY + && callForwardingInfo.getTimeoutSeconds() <= 0) { throw new IllegalArgumentException("callForwarding timeout isn't positive"); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index 0f84f6ebe522..c9a8947ab5ef 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -322,6 +322,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID); verify(mSafeModeTimeoutAlarm).cancel(); assertFalse(mGatewayConnection.isInSafeMode()); + verifySafeModeStateAndCallbackFired(1 /* invocationCount */, false /* isInSafeMode */); } @Test @@ -391,6 +392,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID); + verifySafeModeStateAndCallbackFired(2 /* invocationCount */, false /* isInSafeMode */); assertFalse(mGatewayConnection.isInSafeMode()); } @@ -400,7 +402,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection mTestLooper.dispatchAll(); triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID); - assertFalse(mGatewayConnection.isInSafeMode()); + verifySafeModeStateAndCallbackFired(1 /* invocationCount */, false /* isInSafeMode */); // Trigger a failed validation, and the subsequent safemode timeout. triggerValidation(NetworkAgent.VALIDATION_STATUS_NOT_VALID); @@ -416,7 +418,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection runnableCaptor.getValue().run(); mTestLooper.dispatchAll(); - assertTrue(mGatewayConnection.isInSafeMode()); + verifySafeModeStateAndCallbackFired(2 /* invocationCount */, true /* isInSafeMode */); } private Consumer<VcnNetworkAgent> setupNetworkAndGetUnwantedCallback() { diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index a696b3ae28f7..64d0bca15ce9 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -23,7 +23,6 @@ import static com.android.server.vcn.VcnTestUtils.setupIpSecManager; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.CALLS_REAL_METHODS; @@ -301,6 +300,11 @@ public class VcnGatewayConnectionTestBase { expectCanceled); } + protected void verifySafeModeStateAndCallbackFired(int invocationCount, boolean isInSafeMode) { + verify(mGatewayStatusCallback, times(invocationCount)).onSafeModeStatusChanged(); + assertEquals(isInSafeMode, mGatewayConnection.isInSafeMode()); + } + protected void verifySafeModeTimeoutNotifiesCallbackAndUnregistersNetworkAgent( @NonNull State expectedState) { // Set a VcnNetworkAgent, and expect it to be unregistered and cleared @@ -314,9 +318,8 @@ public class VcnGatewayConnectionTestBase { delayedEvent.run(); mTestLooper.dispatchAll(); - verify(mGatewayStatusCallback).onSafeModeStatusChanged(); assertEquals(expectedState, mGatewayConnection.getCurrentState()); - assertTrue(mGatewayConnection.isInSafeMode()); + verifySafeModeStateAndCallbackFired(1, true); verify(mockNetworkAgent).unregister(); assertNull(mGatewayConnection.getNetworkAgent()); |