diff options
647 files changed, 19272 insertions, 5799 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 b49bbc5fca89..d4e32396187d 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -18,7 +18,6 @@ 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; @@ -332,7 +331,6 @@ 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; @@ -345,7 +343,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); List<AppSearchSchema> schemas = new ArrayList<>(schemaBundles.size()); @@ -424,7 +422,6 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); Objects.requireNonNull(callback); - int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -433,7 +430,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -460,7 +457,6 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); Objects.requireNonNull(callback); - int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -469,7 +465,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -499,7 +495,6 @@ 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; @@ -512,7 +507,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); AppSearchBatchResult.Builder<String, Void> resultBuilder = @@ -589,7 +584,6 @@ 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; @@ -602,7 +596,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); AppSearchBatchResult.Builder<String, Bundle> resultBuilder = @@ -674,7 +668,6 @@ 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; @@ -687,7 +680,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); instance = mAppSearchUserInstanceManager.getUserInstance(targetUser); @@ -744,7 +737,6 @@ 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; @@ -757,7 +749,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); instance = mAppSearchUserInstanceManager.getUserInstance(targetUser); @@ -813,7 +805,6 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); Objects.requireNonNull(callback); - int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -822,7 +813,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -846,7 +837,6 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(packageName); Objects.requireNonNull(userHandle); - int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -855,7 +845,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -884,7 +874,6 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); Objects.requireNonNull(callback); - int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -893,7 +882,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -940,7 +929,6 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); Objects.requireNonNull(callback); - int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -949,7 +937,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -1006,7 +994,6 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); Objects.requireNonNull(callback); - int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -1015,7 +1002,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -1057,7 +1044,6 @@ 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; @@ -1070,7 +1056,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); AppSearchBatchResult.Builder<String, Void> resultBuilder = @@ -1147,7 +1133,6 @@ 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; @@ -1160,7 +1145,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); instance = mAppSearchUserInstanceManager.getUserInstance(targetUser); @@ -1215,7 +1200,6 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(userHandle); Objects.requireNonNull(callback); - int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { try { @@ -1224,7 +1208,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); AppSearchUserInstance instance = @@ -1249,7 +1233,6 @@ 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; @@ -1262,7 +1245,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); instance = mAppSearchUserInstanceManager.getUserInstance(targetUser); @@ -1305,7 +1288,6 @@ public class AppSearchManagerService extends SystemService { Objects.requireNonNull(callback); long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime(); - int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); EXECUTOR.execute(() -> { @@ -1319,7 +1301,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, callingPid, callingUid); + UserHandle targetUser = handleIncomingUser(userHandle, callingUid); verifyUserUnlocked(targetUser); Context targetUserContext = mContext.createContextAsUser(targetUser, @@ -1407,22 +1389,12 @@ 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 callingPid, - int callingUid) { + private UserHandle handleIncomingUser(@NonNull UserHandle targetUserHandle, int callingUid) { UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid); if (callingUserHandle.equals(targetUserHandle)) { return targetUserHandle; @@ -1434,16 +1406,9 @@ 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( - "Permission denied while calling from uid " + callingUid - + " with " + targetUserHandle + "; Requires permission: " - + Manifest.permission.INTERACT_ACROSS_USERS_FULL); + "Requested user, " + targetUserHandle + ", is not the same as the calling user, " + + callingUserHandle + "."); } /** diff --git a/core/api/current.txt b/core/api/current.txt index 1d03370e698d..e015169cf69a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -99,7 +99,7 @@ package android { field public static final String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES"; field public static final String INTERNET = "android.permission.INTERNET"; field public static final String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES"; - field public static final String LAUNCH_TWO_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_TWO_PANE_SETTINGS_DEEP_LINK"; + field public static final String LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK"; field public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS"; field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE"; field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS"; @@ -1298,7 +1298,7 @@ package android { field public static final int shortcutLongLabel = 16844074; // 0x101052a field public static final int shortcutShortLabel = 16844073; // 0x1010529 field public static final int shouldDisableView = 16843246; // 0x10101ee - field public static final int shouldUseDefaultDisplayStateChangeTransition; + field public static final int shouldUseDefaultUnfoldTransition; field public static final int showAsAction = 16843481; // 0x10102d9 field public static final int showDefault = 16843258; // 0x10101fa field public static final int showDividers = 16843561; // 0x1010329 @@ -6712,6 +6712,7 @@ package android.app { } public class TaskInfo { + method public boolean isVisible(); field @Nullable public android.content.ComponentName baseActivity; field @NonNull public android.content.Intent baseIntent; field public boolean isRunning; @@ -6927,7 +6928,7 @@ package android.app { method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager); method public CharSequence loadLabel(android.content.pm.PackageManager); method public android.graphics.drawable.Drawable loadThumbnail(android.content.pm.PackageManager); - method public boolean shouldUseDefaultDisplayStateChangeTransition(); + method public boolean shouldUseDefaultUnfoldTransition(); method public boolean supportsMultipleDisplays(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.WallpaperInfo> CREATOR; @@ -17949,6 +17950,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> EDGE_AVAILABLE_EDGE_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES; + field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SUPPORTED_HARDWARE_LEVEL; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.String> INFO_VERSION; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES; @@ -18643,6 +18645,12 @@ package android.hardware.camera2.params { method public android.util.Rational getElement(int, int); } + public final class DeviceStateSensorOrientationMap { + method public int getSensorOrientation(long); + field public static final long FOLDED = 4L; // 0x4L + field public static final long NORMAL = 0L; // 0x0L + } + public final class ExtensionSessionConfiguration { ctor public ExtensionSessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.StateCallback); method @NonNull public java.util.concurrent.Executor getExecutor(); @@ -22574,6 +22582,7 @@ package android.media { field public static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder"; field public static final String KEY_MAX_HEIGHT = "max-height"; field public static final String KEY_MAX_INPUT_SIZE = "max-input-size"; + field public static final String KEY_MAX_OUTPUT_CHANNEL_COUNT = "max-output-channel_count"; field public static final String KEY_MAX_PTS_GAP_TO_ENCODER = "max-pts-gap-to-encoder"; field public static final String KEY_MAX_WIDTH = "max-width"; field public static final String KEY_MIME = "mime"; @@ -35233,7 +35242,7 @@ package android.provider { field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS"; field public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS"; field public static final String ACTION_SETTINGS = "android.settings.SETTINGS"; - field public static final String ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK = "android.settings.SETTINGS_LARGE_SCREEN_DEEP_LINK"; + field public static final String ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY = "android.settings.SETTINGS_EMBED_DEEP_LINK_ACTIVITY"; field public static final String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO"; field public static final String ACTION_SHOW_WORK_POLICY_INFO = "android.settings.SHOW_WORK_POLICY_INFO"; field public static final String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS"; @@ -35274,8 +35283,8 @@ package android.provider { field public static final String EXTRA_EASY_CONNECT_ERROR_CODE = "android.provider.extra.EASY_CONNECT_ERROR_CODE"; field public static final String EXTRA_INPUT_METHOD_ID = "input_method_id"; field public static final String EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME = "android.provider.extra.NOTIFICATION_LISTENER_COMPONENT_NAME"; - field public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI = "android.provider.extra.SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI"; - field public static final String EXTRA_SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY = "android.provider.extra.SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY"; + field public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY = "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY"; + field public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI = "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI"; field public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID"; field public static final String EXTRA_WIFI_NETWORK_LIST = "android.provider.extra.WIFI_NETWORK_LIST"; field public static final String EXTRA_WIFI_NETWORK_RESULT_LIST = "android.provider.extra.WIFI_NETWORK_RESULT_LIST"; @@ -46905,15 +46914,15 @@ package android.view { } @UiThread public interface AttachedSurfaceControl { - method public default void addOnSurfaceTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnSurfaceTransformHintChangedListener); + method public default void addOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener); method public boolean applyTransactionOnDraw(@NonNull android.view.SurfaceControl.Transaction); method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl); - method public default int getSurfaceTransformHint(); - method public default void removeOnSurfaceTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnSurfaceTransformHintChangedListener); + method public default int getBufferTransformHint(); + method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener); } - @UiThread public static interface AttachedSurfaceControl.OnSurfaceTransformHintChangedListener { - method public void onSurfaceTransformHintChanged(int); + @UiThread public static interface AttachedSurfaceControl.OnBufferTransformHintChangedListener { + method public void onBufferTransformHintChanged(int); } public final class Choreographer { @@ -48403,6 +48412,12 @@ package android.view { method public void readFromParcel(android.os.Parcel); method public void release(); method public void writeToParcel(android.os.Parcel, int); + field public static final int BUFFER_TRANSFORM_IDENTITY = 0; // 0x0 + field public static final int BUFFER_TRANSFORM_MIRROR_HORIZONTAL = 1; // 0x1 + field public static final int BUFFER_TRANSFORM_MIRROR_VERTICAL = 2; // 0x2 + field public static final int BUFFER_TRANSFORM_ROTATE_180 = 3; // 0x3 + field public static final int BUFFER_TRANSFORM_ROTATE_270 = 7; // 0x7 + field public static final int BUFFER_TRANSFORM_ROTATE_90 = 4; // 0x4 field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl> CREATOR; } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 1d406a5fdb28..47ccf10c35d1 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -26,7 +26,7 @@ package android { field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"; field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE"; field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK"; - field public static final String ALLOW_PLACE_IN_TWO_PANE_SETTINGS = "android.permission.ALLOW_PLACE_IN_TWO_PANE_SETTINGS"; + field public static final String ALLOW_PLACE_IN_MULTI_PANE_SETTINGS = "android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS"; field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER"; field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; field public static final String ASSOCIATE_COMPANION_DEVICES = "android.permission.ASSOCIATE_COMPANION_DEVICES"; @@ -5377,10 +5377,12 @@ package android.media { 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 @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnSpatializerOutputChangedListener(); 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 void getEffectParameter(int, @NonNull byte[]); method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getHeadTrackingMode(); + method @IntRange(from=0) @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getOutput(); 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); @@ -5390,6 +5392,7 @@ package android.media { 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); + method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnSpatializerOutputChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerOutputChangedListener); 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 @@ -5406,6 +5409,10 @@ package android.media { method public void onHeadTrackingModeChanged(@NonNull android.media.Spatializer, int); } + public static interface Spatializer.OnSpatializerOutputChangedListener { + method public void onSpatializerOutputChanged(@NonNull android.media.Spatializer, @IntRange(from=0) int); + } + } package android.media.audiofx { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 22091fcde86f..f453ba16043c 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -77,7 +77,6 @@ import android.os.IBinder; import android.os.Looper; import android.os.Parcelable; import android.os.PersistableBundle; -import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager.ServiceNotFoundException; @@ -141,7 +140,6 @@ import android.widget.AdapterView; import android.widget.Toast; import android.widget.Toolbar; import android.window.SplashScreen; -import android.window.SplashScreenView; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -970,7 +968,6 @@ public class Activity extends ContextThemeWrapper private UiTranslationController mUiTranslationController; private SplashScreen mSplashScreen; - private SplashScreenView mSplashScreenView; private final WindowControllerCallback mWindowControllerCallback = new WindowControllerCallback() { @@ -1641,16 +1638,6 @@ public class Activity extends ContextThemeWrapper } } - /** @hide */ - public void setSplashScreenView(SplashScreenView v) { - mSplashScreenView = v; - } - - /** @hide */ - SplashScreenView getSplashScreenView() { - return mSplashScreenView; - } - /** * Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with * the attribute {@link android.R.attr#persistableMode} set to @@ -8804,9 +8791,7 @@ public class Activity extends ContextThemeWrapper * the activity is visible after the screen is turned on when the lockscreen is up. In addition, * if this flag is set and the activity calls {@link * KeyguardManager#requestDismissKeyguard(Activity, KeyguardManager.KeyguardDismissCallback)} - * the screen will turn on. If the screen is off and device is not secured, this flag can turn - * screen on and dismiss keyguard to make this activity visible and resume, which can be used to - * replace {@link PowerManager#ACQUIRE_CAUSES_WAKEUP} + * the screen will turn on. * * @param turnScreenOn {@code true} to turn on the screen; {@code false} otherwise. * diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index d0680f8c9268..431755e092e3 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -166,6 +166,7 @@ import android.view.Choreographer; import android.view.Display; import android.view.DisplayAdjustments; import android.view.DisplayAdjustments.FixedRotationAdjustments; +import android.view.SurfaceControl; import android.view.ThreadedRenderer; import android.view.View; import android.view.ViewDebug; @@ -235,7 +236,6 @@ import java.util.Map; import java.util.Objects; import java.util.TimeZone; import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; /** @@ -4074,10 +4074,11 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r, - @Nullable SplashScreenView.SplashScreenViewParcelable parcelable) { + @Nullable SplashScreenView.SplashScreenViewParcelable parcelable, + @NonNull SurfaceControl startingWindowLeash) { final DecorView decorView = (DecorView) r.window.peekDecorView(); if (parcelable != null && decorView != null) { - createSplashScreen(r, decorView, parcelable); + createSplashScreen(r, decorView, parcelable, startingWindowLeash); } else { // shouldn't happen! Slog.e(TAG, "handleAttachSplashScreenView failed, unable to attach"); @@ -4085,63 +4086,52 @@ public final class ActivityThread extends ClientTransactionHandler } private void createSplashScreen(ActivityClientRecord r, DecorView decorView, - SplashScreenView.SplashScreenViewParcelable parcelable) { + SplashScreenView.SplashScreenViewParcelable parcelable, + @NonNull SurfaceControl startingWindowLeash) { final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity); final SplashScreenView view = builder.createFromParcel(parcelable).build(); decorView.addView(view); view.attachHostActivityAndSetSystemUIColors(r.activity, r.window); view.requestLayout(); - // Ensure splash screen view is shown before remove the splash screen window. - final ViewRootImpl impl = decorView.getViewRootImpl(); - final boolean hardwareEnabled = impl != null && impl.isHardwareEnabled(); - final AtomicBoolean notified = new AtomicBoolean(); - if (hardwareEnabled) { - final Runnable frameCommit = new Runnable() { - @Override - public void run() { - view.post(() -> { - if (!notified.get()) { - view.getViewTreeObserver().unregisterFrameCommitCallback(this); - ActivityClient.getInstance().reportSplashScreenAttached( - r.token); - notified.set(true); - } - }); - } - }; - view.getViewTreeObserver().registerFrameCommitCallback(frameCommit); - } else { - final ViewTreeObserver.OnDrawListener onDrawListener = - new ViewTreeObserver.OnDrawListener() { - @Override - public void onDraw() { - view.post(() -> { - if (!notified.get()) { - view.getViewTreeObserver().removeOnDrawListener(this); - ActivityClient.getInstance().reportSplashScreenAttached( - r.token); - notified.set(true); - } - }); - } - }; - view.getViewTreeObserver().addOnDrawListener(onDrawListener); - } + + view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() { + @Override + public void onDraw() { + // Transfer the splash screen view from shell to client. + // Call syncTransferSplashscreenViewTransaction at the first onDraw so we can ensure + // the client view is ready to show and we can use applyTransactionOnDraw to make + // all transitions happen at the same frame. + syncTransferSplashscreenViewTransaction( + view, r.token, decorView, startingWindowLeash); + view.postOnAnimation(() -> view.getViewTreeObserver().removeOnDrawListener(this)); + } + }); } - @Override - public void handOverSplashScreenView(@NonNull ActivityClientRecord r) { - final SplashScreenView v = r.activity.getSplashScreenView(); - if (v == null) { - return; - } + private void reportSplashscreenViewShown(IBinder token, SplashScreenView view) { + ActivityClient.getInstance().reportSplashScreenAttached(token); synchronized (this) { if (mSplashScreenGlobal != null) { - mSplashScreenGlobal.handOverSplashScreenView(r.token, v); + mSplashScreenGlobal.handOverSplashScreenView(token, view); } } } + private void syncTransferSplashscreenViewTransaction(SplashScreenView view, IBinder token, + View decorView, @NonNull SurfaceControl startingWindowLeash) { + // Ensure splash screen view is shown before remove the splash screen window. + // Once the copied splash screen view is onDrawn on decor view, use applyTransactionOnDraw + // to ensure the transfer of surface view and hide starting window are happen at the same + // frame. + final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + transaction.hide(startingWindowLeash); + + decorView.getViewRootImpl().applyTransactionOnDraw(transaction); + view.syncTransferSurfaceOnDraw(); + // Tell server we can remove the starting window + decorView.postOnAnimation(() -> reportSplashscreenViewShown(token, view)); + } + /** * Cycle activity through onPause and onUserLeaveHint so that PIP is entered if supported, then * return to its previous state. This allows activities that rely on onUserLeaveHint instead of diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 115101c0bff6..c743f6572d5e 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -28,6 +28,7 @@ import android.content.res.Configuration; import android.os.IBinder; import android.util.MergedConfiguration; import android.view.DisplayAdjustments.FixedRotationAdjustments; +import android.view.SurfaceControl; import android.window.SplashScreenView.SplashScreenViewParcelable; import com.android.internal.annotations.VisibleForTesting; @@ -165,10 +166,8 @@ public abstract class ClientTransactionHandler { /** Attach a splash screen window view to the top of the activity */ public abstract void handleAttachSplashScreenView(@NonNull ActivityClientRecord r, - @NonNull SplashScreenViewParcelable parcelable); - - /** Hand over the splash screen window view to the activity */ - public abstract void handOverSplashScreenView(@NonNull ActivityClientRecord r); + @NonNull SplashScreenViewParcelable parcelable, + @NonNull SurfaceControl startingWindowLeash); /** Perform activity launch. */ public abstract Activity handleLaunchActivity(@NonNull ActivityClientRecord r, diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index cac763988d5e..bd9b6e952118 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -265,6 +265,13 @@ public class TaskInfo { } /** + * Whether this task is visible. + */ + public boolean isVisible() { + return isVisible; + } + + /** * @param isLowResolution * @return * @hide diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java index a969b10c15a3..99d406446dae 100644 --- a/core/java/android/app/WallpaperInfo.java +++ b/core/java/android/app/WallpaperInfo.java @@ -81,7 +81,7 @@ public final class WallpaperInfo implements Parcelable { final int mContextDescriptionResource; final boolean mShowMetadataInPreview; final boolean mSupportsAmbientMode; - final boolean mShouldUseDefaultDisplayStateChangeTransition; + final boolean mShouldUseDefaultUnfoldTransition; final String mSettingsSliceUri; final boolean mSupportMultipleDisplays; @@ -146,9 +146,9 @@ public final class WallpaperInfo implements Parcelable { mSupportsAmbientMode = sa.getBoolean( com.android.internal.R.styleable.Wallpaper_supportsAmbientMode, false); - mShouldUseDefaultDisplayStateChangeTransition = sa.getBoolean( + mShouldUseDefaultUnfoldTransition = sa.getBoolean( com.android.internal.R.styleable - .Wallpaper_shouldUseDefaultDisplayStateChangeTransition, true); + .Wallpaper_shouldUseDefaultUnfoldTransition, true); mSettingsSliceUri = sa.getString( com.android.internal.R.styleable.Wallpaper_settingsSliceUri); mSupportMultipleDisplays = sa.getBoolean( @@ -175,7 +175,7 @@ public final class WallpaperInfo implements Parcelable { mSupportsAmbientMode = source.readInt() != 0; mSettingsSliceUri = source.readString(); mSupportMultipleDisplays = source.readInt() != 0; - mShouldUseDefaultDisplayStateChangeTransition = source.readInt() != 0; + mShouldUseDefaultUnfoldTransition = source.readInt() != 0; mService = ResolveInfo.CREATOR.createFromParcel(source); } @@ -400,24 +400,24 @@ public final class WallpaperInfo implements Parcelable { /** * Returns whether this wallpaper should receive default zooming updates when the device - * changes its display state (e.g. when folding or unfolding a foldable device). + * changes its state (e.g. when folding or unfolding a foldable device). * If set to false the wallpaper will not receive zoom events when changing the device state, * so it can implement its own transition instead. * <p> * This corresponds to the value {@link - * android.R.styleable#Wallpaper_shouldUseDefaultDisplayStateChangeTransition} in the + * android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition} in the * XML description of the wallpaper. * <p> * The default value is {@code true}. * - * @see android.R.styleable#Wallpaper_shouldUseDefaultDisplayStateChangeTransition + * @see android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition * @return {@code true} if wallpaper should receive default device state change * transition updates * - * @attr ref android.R.styleable#Wallpaper_shouldUseDefaultDisplayStateChangeTransition + * @attr ref android.R.styleable#Wallpaper_shouldUseDefaultUnfoldTransition */ - public boolean shouldUseDefaultDisplayStateChangeTransition() { - return mShouldUseDefaultDisplayStateChangeTransition; + public boolean shouldUseDefaultUnfoldTransition() { + return mShouldUseDefaultUnfoldTransition; } public void dump(Printer pw, String prefix) { @@ -450,7 +450,7 @@ public final class WallpaperInfo implements Parcelable { dest.writeInt(mSupportsAmbientMode ? 1 : 0); dest.writeString(mSettingsSliceUri); dest.writeInt(mSupportMultipleDisplays ? 1 : 0); - dest.writeInt(mShouldUseDefaultDisplayStateChangeTransition ? 1 : 0); + dest.writeInt(mShouldUseDefaultUnfoldTransition ? 1 : 0); mService.writeToParcel(dest, flags); } diff --git a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java index 5374984d31d0..767fd28b8a2a 100644 --- a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java +++ b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java @@ -16,17 +16,14 @@ package android.app.servertransaction; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; import android.app.ClientTransactionHandler; import android.os.Parcel; +import android.view.SurfaceControl; import android.window.SplashScreenView.SplashScreenViewParcelable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Transfer a splash screen view to an Activity. * @hide @@ -34,31 +31,13 @@ import java.lang.annotation.RetentionPolicy; public class TransferSplashScreenViewStateItem extends ActivityTransactionItem { private SplashScreenViewParcelable mSplashScreenViewParcelable; - private @TransferRequest int mRequest; - - @IntDef(value = { - ATTACH_TO, - HANDOVER_TO - }) - @Retention(RetentionPolicy.SOURCE) - public @interface TransferRequest {} - // request client to attach the view on it. - public static final int ATTACH_TO = 0; - // tell client that you can handle the splash screen view. - public static final int HANDOVER_TO = 1; + private SurfaceControl mStartingWindowLeash; @Override public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityThread.ActivityClientRecord r, PendingTransactionActions pendingActions) { - switch (mRequest) { - case ATTACH_TO: - client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable); - break; - case HANDOVER_TO: - client.handOverSplashScreenView(r); - break; - } + client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable, mStartingWindowLeash); } @Override @@ -68,26 +47,27 @@ public class TransferSplashScreenViewStateItem extends ActivityTransactionItem { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRequest); dest.writeTypedObject(mSplashScreenViewParcelable, flags); + dest.writeTypedObject(mStartingWindowLeash, flags); } private TransferSplashScreenViewStateItem() {} private TransferSplashScreenViewStateItem(Parcel in) { - mRequest = in.readInt(); mSplashScreenViewParcelable = in.readTypedObject(SplashScreenViewParcelable.CREATOR); + mStartingWindowLeash = in.readTypedObject(SurfaceControl.CREATOR); } /** Obtain an instance initialized with provided params. */ - public static TransferSplashScreenViewStateItem obtain(@TransferRequest int state, - @Nullable SplashScreenViewParcelable parcelable) { + public static TransferSplashScreenViewStateItem obtain( + @Nullable SplashScreenViewParcelable parcelable, + @Nullable SurfaceControl startingWindowLeash) { TransferSplashScreenViewStateItem instance = ObjectPool.obtain(TransferSplashScreenViewStateItem.class); if (instance == null) { instance = new TransferSplashScreenViewStateItem(); } - instance.mRequest = state; instance.mSplashScreenViewParcelable = parcelable; + instance.mStartingWindowLeash = startingWindowLeash; return instance; } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index e0138c5db178..9f77a7e72e70 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -22,15 +22,18 @@ import android.compat.annotation.UnsupportedAppUsage; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; +import android.hardware.camera2.params.DeviceStateSensorOrientationMap; import android.hardware.camera2.params.RecommendedStreamConfigurationMap; import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.utils.TypeReference; import android.os.Build; +import android.util.Log; import android.util.Rational; +import com.android.internal.annotations.GuardedBy; + import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Set; @@ -202,8 +205,25 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri private List<CaptureResult.Key<?>> mAvailableResultKeys; private ArrayList<RecommendedStreamConfigurationMap> mRecommendedConfigurations; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private boolean mFoldedDeviceState; + + private final CameraManager.DeviceStateListener mFoldStateListener = + new CameraManager.DeviceStateListener() { + @Override + public final void onDeviceStateChanged(boolean folded) { + synchronized (mLock) { + mFoldedDeviceState = folded; + } + }}; + + private static final String TAG = "CameraCharacteristics"; + /** * Takes ownership of the passed-in properties object + * + * @param properties Camera properties. * @hide */ public CameraCharacteristics(CameraMetadataNative properties) { @@ -220,6 +240,42 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri } /** + * Return the device state listener for this Camera characteristics instance + */ + CameraManager.DeviceStateListener getDeviceStateListener() { return mFoldStateListener; } + + /** + * Overrides the property value + * + * <p>Check whether a given property value needs to be overridden in some specific + * case.</p> + * + * @param key The characteristics field to override. + * @return The value of overridden property, or {@code null} if the property doesn't need an + * override. + */ + @Nullable + private <T> T overrideProperty(Key<T> key) { + if (CameraCharacteristics.SENSOR_ORIENTATION.equals(key) && (mFoldStateListener != null) && + (mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS) != null)) { + DeviceStateSensorOrientationMap deviceStateSensorOrientationMap = + mProperties.get(CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP); + synchronized (mLock) { + Integer ret = deviceStateSensorOrientationMap.getSensorOrientation( + mFoldedDeviceState ? DeviceStateSensorOrientationMap.FOLDED : + DeviceStateSensorOrientationMap.NORMAL); + if (ret >= 0) { + return (T) ret; + } else { + Log.w(TAG, "No valid device state to orientation mapping! Using default!"); + } + } + } + + return null; + } + + /** * Get a camera characteristics field value. * * <p>The field definitions can be @@ -235,7 +291,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri */ @Nullable public <T> T get(Key<T> key) { - return mProperties.get(key); + T propertyOverride = overrideProperty(key); + return (propertyOverride != null) ? propertyOverride : mProperties.get(key); } /** @@ -3993,11 +4050,26 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * upright on the device screen in its native orientation.</p> * <p>Also defines the direction of rolling shutter readout, which is from top to bottom in * the sensor's coordinate system.</p> + * <p>Starting with Android API level 32, camera clients that query the orientation via + * {@link android.hardware.camera2.CameraCharacteristics#get } on foldable devices which + * include logical cameras can receive a value that can dynamically change depending on the + * device/fold state. + * Clients are advised to not cache or store the orientation value of such logical sensors. + * In case repeated queries to CameraCharacteristics are not preferred, then clients can + * also access the entire mapping from device state to sensor orientation in + * {@link android.hardware.camera2.params.DeviceStateSensorOrientationMap }. + * Do note that a dynamically changing sensor orientation value in camera characteristics + * will not be the best way to establish the orientation per frame. Clients that want to + * know the sensor orientation of a particular captured frame should query the + * {@link CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID android.logicalMultiCamera.activePhysicalId} from the corresponding capture result and + * check the respective physical camera orientation.</p> * <p><b>Units</b>: Degrees of clockwise rotation; always a multiple of * 90</p> * <p><b>Range of valid values:</b><br> * 0, 90, 180, 270</p> * <p>This key is available on all devices.</p> + * + * @see CaptureResult#LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID */ @PublicKey @NonNull @@ -4307,6 +4379,46 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<String>("android.info.version", String.class); /** + * <p>This lists the mapping between a device folding state and + * specific camera sensor orientation for logical cameras on a foldable device.</p> + * <p>Logical cameras on foldable devices can support sensors with different orientation + * values. The orientation value may need to change depending on the specific folding + * state. Information about the mapping between the device folding state and the + * sensor orientation can be obtained in + * {@link android.hardware.camera2.params.DeviceStateSensorOrientationMap }. + * Device state orientation maps are optional and maybe present on devices that support + * {@link CaptureRequest#SCALER_ROTATE_AND_CROP android.scaler.rotateAndCrop}.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @see CaptureRequest#SCALER_ROTATE_AND_CROP + */ + @PublicKey + @NonNull + @SyntheticKey + public static final Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP = + new Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap>("android.info.deviceStateSensorOrientationMap", android.hardware.camera2.params.DeviceStateSensorOrientationMap.class); + + /** + * <p>HAL must populate the array with + * (hardware::camera::provider::V2_5::DeviceState, sensorOrientation) pairs for each + * supported device state bitwise combination.</p> + * <p><b>Units</b>: (device fold state, sensor orientation) x n</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * <p><b>Limited capability</b> - + * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the + * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p> + * + * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL + * @hide + */ + public static final Key<long[]> INFO_DEVICE_STATE_ORIENTATIONS = + new Key<long[]>("android.info.deviceStateOrientations", long[].class); + + /** * <p>The maximum number of frames that can occur after a request * (different than the previous) has been submitted, and before the * result's state becomes synchronized.</p> diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 5833b3dbef86..b7c5644df107 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -35,10 +35,13 @@ import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.params.StreamConfiguration; import android.hardware.camera2.utils.CameraIdAndSessionConfiguration; import android.hardware.camera2.utils.ConcurrentCameraIdCombination; +import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.DisplayManager; import android.os.Binder; import android.os.DeadObjectException; import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -50,6 +53,10 @@ import android.util.Log; import android.util.Size; import android.view.Display; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; + +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -97,6 +104,90 @@ public final class CameraManager { synchronized(mLock) { mContext = context; } + + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + mFoldStateListener = new FoldStateListener(context); + try { + context.getSystemService(DeviceStateManager.class) + .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener); + } catch (IllegalStateException e) { + Log.v(TAG, "Failed to register device state listener!"); + Log.v(TAG, "Device state dependent characteristics updates will not be functional!"); + mHandlerThread.quitSafely(); + mHandler = null; + mFoldStateListener = null; + } + } + + private HandlerThread mHandlerThread; + private Handler mHandler; + private FoldStateListener mFoldStateListener; + @GuardedBy("mLock") + private ArrayList<WeakReference<DeviceStateListener>> mDeviceStateListeners = new ArrayList<>(); + private boolean mFoldedDeviceState; + + /** + * @hide + */ + public interface DeviceStateListener { + void onDeviceStateChanged(boolean folded); + } + + private final class FoldStateListener implements DeviceStateManager.DeviceStateCallback { + private final int[] mFoldedDeviceStates; + + public FoldStateListener(Context context) { + mFoldedDeviceStates = context.getResources().getIntArray( + com.android.internal.R.array.config_foldedDeviceStates); + } + + private void handleStateChange(int state) { + boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state); + synchronized (mLock) { + mFoldedDeviceState = folded; + ArrayList<WeakReference<DeviceStateListener>> invalidListeners = new ArrayList<>(); + for (WeakReference<DeviceStateListener> listener : mDeviceStateListeners) { + DeviceStateListener callback = listener.get(); + if (callback != null) { + callback.onDeviceStateChanged(folded); + } else { + invalidListeners.add(listener); + } + } + if (!invalidListeners.isEmpty()) { + mDeviceStateListeners.removeAll(invalidListeners); + } + } + } + + @Override + public final void onBaseStateChanged(int state) { + handleStateChange(state); + } + + @Override + public final void onStateChanged(int state) { + handleStateChange(state); + } + } + + /** + * Register a {@link CameraCharacteristics} device state listener + * + * @param chars Camera characteristics that need to receive device state updates + * + * @hide + */ + public void registerDeviceStateListener(@NonNull CameraCharacteristics chars) { + synchronized (mLock) { + DeviceStateListener listener = chars.getDeviceStateListener(); + listener.onDeviceStateChanged(mFoldedDeviceState); + if (mFoldStateListener != null) { + mDeviceStateListeners.add(new WeakReference<>(listener)); + } + } } /** @@ -504,6 +595,7 @@ public final class CameraManager { "Camera service is currently unavailable", e); } } + registerDeviceStateListener(characteristics); return characteristics; } @@ -1327,8 +1419,7 @@ public final class CameraManager { private ICameraService mCameraService; // Singleton, don't allow construction - private CameraManagerGlobal() { - } + private CameraManagerGlobal() { } public static final boolean sCameraServiceDisabled = SystemProperties.getBoolean("config.disable_cameraservice", false); diff --git a/core/java/android/hardware/camera2/CaptureFailure.java b/core/java/android/hardware/camera2/CaptureFailure.java index 20ca4a338f01..032ed7e4db62 100644 --- a/core/java/android/hardware/camera2/CaptureFailure.java +++ b/core/java/android/hardware/camera2/CaptureFailure.java @@ -59,7 +59,7 @@ public class CaptureFailure { private final CaptureRequest mRequest; private final int mReason; - private final boolean mDropped; + private final boolean mWasImageCaptured; private final int mSequenceId; private final long mFrameNumber; private final String mErrorPhysicalCameraId; @@ -68,10 +68,11 @@ public class CaptureFailure { * @hide */ public CaptureFailure(CaptureRequest request, int reason, - boolean dropped, int sequenceId, long frameNumber, String errorPhysicalCameraId) { + boolean wasImageCaptured, int sequenceId, long frameNumber, + String errorPhysicalCameraId) { mRequest = request; mReason = reason; - mDropped = dropped; + mWasImageCaptured = wasImageCaptured; mSequenceId = sequenceId; mFrameNumber = frameNumber; mErrorPhysicalCameraId = errorPhysicalCameraId; @@ -141,7 +142,7 @@ public class CaptureFailure { * @return boolean True if the image was captured, false otherwise. */ public boolean wasImageCaptured() { - return !mDropped; + return mWasImageCaptured; } /** diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index 8da6551f3d15..b8443fb6d14b 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -873,21 +873,19 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes @Override public int submitBurst(List<Request> requests, IRequestCallback callback) { int seqId = -1; - synchronized (mInterfaceLock) { - try { - CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback); - ArrayList<CaptureRequest> captureRequests = new ArrayList<>(); - for (Request request : requests) { - captureRequests.add(initializeCaptureRequest(mCameraDevice, request, - mCameraConfigMap)); - } - seqId = mCaptureSession.captureBurstRequests(captureRequests, - new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback); - } catch (CameraAccessException e) { - Log.e(TAG, "Failed to submit capture requests!"); - } catch (IllegalStateException e) { - Log.e(TAG, "Capture session closed!"); + try { + CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback); + ArrayList<CaptureRequest> captureRequests = new ArrayList<>(); + for (Request request : requests) { + captureRequests.add(initializeCaptureRequest(mCameraDevice, request, + mCameraConfigMap)); } + seqId = mCaptureSession.captureBurstRequests(captureRequests, + new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed to submit capture requests!"); + } catch (IllegalStateException e) { + Log.e(TAG, "Capture session closed!"); } return seqId; @@ -896,18 +894,16 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes @Override public int setRepeating(Request request, IRequestCallback callback) { int seqId = -1; - synchronized (mInterfaceLock) { - try { - CaptureRequest repeatingRequest = initializeCaptureRequest(mCameraDevice, - request, mCameraConfigMap); - CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback); - seqId = mCaptureSession.setSingleRepeatingRequest(repeatingRequest, - new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback); - } catch (CameraAccessException e) { - Log.e(TAG, "Failed to enable repeating request!"); - } catch (IllegalStateException e) { - Log.e(TAG, "Capture session closed!"); - } + try { + CaptureRequest repeatingRequest = initializeCaptureRequest(mCameraDevice, + request, mCameraConfigMap); + CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback); + seqId = mCaptureSession.setSingleRepeatingRequest(repeatingRequest, + new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed to enable repeating request!"); + } catch (IllegalStateException e) { + Log.e(TAG, "Capture session closed!"); } return seqId; @@ -915,27 +911,23 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes @Override public void abortCaptures() { - synchronized (mInterfaceLock) { - try { - mCaptureSession.abortCaptures(); - } catch (CameraAccessException e) { - Log.e(TAG, "Failed during capture abort!"); - } catch (IllegalStateException e) { - Log.e(TAG, "Capture session closed!"); - } + try { + mCaptureSession.abortCaptures(); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed during capture abort!"); + } catch (IllegalStateException e) { + Log.e(TAG, "Capture session closed!"); } } @Override public void stopRepeating() { - synchronized (mInterfaceLock) { - try { - mCaptureSession.stopRepeating(); - } catch (CameraAccessException e) { - Log.e(TAG, "Failed during repeating capture stop!"); - } catch (IllegalStateException e) { - Log.e(TAG, "Capture session closed!"); - } + try { + mCaptureSession.stopRepeating(); + } catch (CameraAccessException e) { + Log.e(TAG, "Failed during repeating capture stop!"); + } catch (IllegalStateException e) { + Log.e(TAG, "Capture session closed!"); } } } diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 4708f3e0664f..88649392c23c 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -1867,7 +1867,7 @@ public class CameraDeviceImpl extends CameraDevice final CaptureFailure failure = new CaptureFailure( request, reason, - /*dropped*/ mayHaveBuffers, + mayHaveBuffers, requestId, frameNumber, errorPhysicalCameraId); diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 196134b397cb..e393a66eb733 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -50,10 +50,10 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfiguration import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfigurationDuration; import android.hardware.camera2.marshal.impl.MarshalQueryableString; import android.hardware.camera2.params.Capability; +import android.hardware.camera2.params.DeviceStateSensorOrientationMap; import android.hardware.camera2.params.Face; import android.hardware.camera2.params.HighSpeedVideoConfiguration; import android.hardware.camera2.params.LensShadingMap; -import android.hardware.camera2.params.MeteringRectangle; import android.hardware.camera2.params.MandatoryStreamCombination; import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap; import android.hardware.camera2.params.OisSample; @@ -754,7 +754,7 @@ public class CameraMetadataNative implements Parcelable { }); sGetCommandMap.put( CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP.getNativeKey(), - new GetCommand() { + new GetCommand() { @Override @SuppressWarnings("unchecked") public <T> T getValue(CameraMetadataNative metadata, Key<T> key) { @@ -762,6 +762,15 @@ public class CameraMetadataNative implements Parcelable { } }); sGetCommandMap.put( + CameraCharacteristics.INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP.getNativeKey(), + new GetCommand() { + @Override + @SuppressWarnings("unchecked") + public <T> T getValue(CameraMetadataNative metadata, Key<T> key) { + return (T) metadata.getDeviceStateOrientationMap(); + } + }); + sGetCommandMap.put( CaptureResult.STATISTICS_OIS_SAMPLES.getNativeKey(), new GetCommand() { @Override @@ -994,6 +1003,18 @@ public class CameraMetadataNative implements Parcelable { return map; } + private DeviceStateSensorOrientationMap getDeviceStateOrientationMap() { + long[] mapArray = getBase(CameraCharacteristics.INFO_DEVICE_STATE_ORIENTATIONS); + + // Do not warn if map is null while s is not. This is valid. + if (mapArray == null) { + return null; + } + + DeviceStateSensorOrientationMap map = new DeviceStateSensorOrientationMap(mapArray); + return map; + } + private Location getGpsLocation() { String processingMethod = get(CaptureResult.JPEG_GPS_PROCESSING_METHOD); double[] coords = get(CaptureResult.JPEG_GPS_COORDINATES); diff --git a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java new file mode 100644 index 000000000000..200409e9e000 --- /dev/null +++ b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java @@ -0,0 +1,156 @@ +/* + * 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.camera2.params; + +import android.annotation.LongDef; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.utils.HashCodeHelpers; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Objects; + +/** + * Immutable class that maps the device fold state to sensor orientation. + * + * <p>Some {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA logical} + * cameras on foldables can include physical sensors with different sensor orientation + * values. As a result, the values of the logical camera device can potentially change depending + * on the device fold state.</p> + * + * <p>The device fold state to sensor orientation map will contain information about the + * respective logical camera sensor orientation given a device state. Clients + * can query the mapping for all possible supported folded states. + * + * @see CameraCharacteristics#SENSOR_ORIENTATION + */ +public final class DeviceStateSensorOrientationMap { + /** + * Needs to be kept in sync with the HIDL/AIDL DeviceState + */ + + /** + * The device is in its normal physical configuration. This is the default if the + * device does not support multiple different states. + */ + public static final long NORMAL = 0; + + /** + * The device is folded. If not set, the device is unfolded or does not + * support folding. + * + * The exact point when this status change happens during the folding + * operation is device-specific. + */ + public static final long FOLDED = 1 << 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @LongDef(prefix = {"DEVICE_STATE"}, value = + {NORMAL, + FOLDED }) + public @interface DeviceState {}; + + private final HashMap<Long, Integer> mDeviceStateOrientationMap = new HashMap<>(); + + /** + * Create a new immutable DeviceStateOrientationMap instance. + * + * <p>This constructor takes over the array; do not write to the array afterwards.</p> + * + * @param elements + * An array of elements describing the map + * + * @throws IllegalArgumentException + * if the {@code elements} array length is invalid, not divisible by 2 or contains + * invalid element values + * @throws NullPointerException + * if {@code elements} is {@code null} + * + * @hide + */ + public DeviceStateSensorOrientationMap(final long[] elements) { + mElements = Objects.requireNonNull(elements, "elements must not be null"); + if ((elements.length % 2) != 0) { + throw new IllegalArgumentException("Device state sensor orientation map length " + + elements.length + " is not even!"); + } + + for (int i = 0; i < elements.length; i += 2) { + if ((elements[i+1] % 90) != 0) { + throw new IllegalArgumentException("Sensor orientation not divisible by 90: " + + elements[i+1]); + } + + mDeviceStateOrientationMap.put(elements[i], Math.toIntExact(elements[i + 1])); + } + } + + /** + * Return the logical camera sensor orientation given a specific device fold state. + * + * @param deviceState Device fold state + * + * @return Valid {@link android.hardware.camera2.CameraCharacteristics#SENSOR_ORIENTATION} for + * any supported device fold state + * + * @throws IllegalArgumentException if the given device state is invalid + */ + public int getSensorOrientation(@DeviceState long deviceState) { + if (!mDeviceStateOrientationMap.containsKey(deviceState)) { + throw new IllegalArgumentException("Invalid device state: " + deviceState); + } + + return mDeviceStateOrientationMap.get(deviceState); + } + + /** + * Check if this DeviceStateSensorOrientationMap is equal to another + * DeviceStateSensorOrientationMap. + * + * <p>Two device state orientation maps are equal if and only if all of their elements are + * {@link Object#equals equal}.</p> + * + * @return {@code true} if the objects were equal, {@code false} otherwise + */ + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (obj instanceof DeviceStateSensorOrientationMap) { + final DeviceStateSensorOrientationMap other = (DeviceStateSensorOrientationMap) obj; + return Arrays.equals(mElements, other.mElements); + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return HashCodeHelpers.hashCodeGeneric(mElements); + } + + private final long[] mElements; +} diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java index f5b2ac586bd1..2b52e967ce54 100644 --- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java +++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java @@ -22,11 +22,9 @@ import android.os.Build; import android.os.SystemProperties; import android.provider.Settings; import android.text.TextUtils; -import android.util.Log; - -import java.util.Arrays; import com.android.internal.R; +import com.android.internal.util.ArrayUtils; /** * AmbientDisplayConfiguration encapsulates reading access to the configuration of ambient display. @@ -90,7 +88,12 @@ public class AmbientDisplayConfiguration { /** {@hide} */ public boolean tapSensorAvailable() { - return !TextUtils.isEmpty(tapSensorType()); + for (String tapType : tapSensorTypeMapping()) { + if (!TextUtils.isEmpty(tapType)) { + return true; + } + } + return false; } /** {@hide} */ @@ -143,18 +146,18 @@ public class AmbientDisplayConfiguration { return mContext.getResources().getString(R.string.config_dozeDoubleTapSensorType); } - /** {@hide} */ - private String tapSensorType() { - return mContext.getResources().getString(R.string.config_dozeTapSensorType); - } - - /** {@hide} */ - public String tapSensorType(int posture) { - return getSensorFromPostureMapping( - mContext.getResources().getStringArray(R.array.config_dozeTapSensorPostureMapping), - tapSensorType(), - posture - ); + /** {@hide} + * May support multiple postures. + */ + public String[] tapSensorTypeMapping() { + String[] postureMapping = + mContext.getResources().getStringArray(R.array.config_dozeTapSensorPostureMapping); + if (ArrayUtils.isEmpty(postureMapping)) { + return new String[] { + mContext.getResources().getString(R.string.config_dozeTapSensorType) + }; + } + return postureMapping; } /** {@hide} */ @@ -253,20 +256,4 @@ public class AmbientDisplayConfiguration { private boolean boolSetting(String name, int user, int def) { return Settings.Secure.getIntForUser(mContext.getContentResolver(), name, def, user) != 0; } - - /** {@hide} */ - public static String getSensorFromPostureMapping( - String[] postureMapping, - String defaultValue, - int posture) { - String sensorType = defaultValue; - if (postureMapping != null && posture < postureMapping.length) { - sensorType = postureMapping[posture]; - } else { - Log.e(TAG, "Unsupported doze posture " + posture - + " postureMapping=" + Arrays.toString(postureMapping)); - } - - return TextUtils.isEmpty(sensorType) ? defaultValue : sensorType; - } } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index c0f008122b4f..9721279bcd24 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -790,11 +790,6 @@ public class InputMethodService extends AbstractInputMethodService { return; } - if (Trace.isEnabled()) { - Binder.enableTracing(); - } else { - Binder.disableTracing(); - } Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showSoftInput"); ImeTracing.getInstance().triggerServiceDump( "InputMethodService.InputMethodImpl#showSoftInput", InputMethodService.this, diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index fb9911811da9..79ec55482c50 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -2303,6 +2303,38 @@ public abstract class BatteryStats implements Parcelable { public abstract Timer getScreenBrightnessTimer(int brightnessBin); /** + * Returns the number of physical displays on the device. + * + * {@hide} + */ + public abstract int getDisplayCount(); + + /** + * Returns the time in microseconds that the screen has been on for a display while the + * device was running on battery. + * + * {@hide} + */ + public abstract long getDisplayScreenOnTime(int display, long elapsedRealtimeUs); + + /** + * Returns the time in microseconds that a display has been dozing while the device was + * running on battery. + * + * {@hide} + */ + public abstract long getDisplayScreenDozeTime(int display, long elapsedRealtimeUs); + + /** + * Returns the time in microseconds that a display has been on with the given brightness + * level while the device was running on battery. + * + * {@hide} + */ + public abstract long getDisplayScreenBrightnessTime(int display, int brightnessBin, + long elapsedRealtimeUs); + + /** * Returns the time in microseconds that power save mode has been enabled while the device was * running on battery. * @@ -5038,6 +5070,71 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } + final int numDisplays = getDisplayCount(); + if (numDisplays > 1) { + pw.println(""); + pw.print(prefix); + sb.setLength(0); + sb.append(prefix); + sb.append(" MULTI-DISPLAY POWER SUMMARY START"); + pw.println(sb.toString()); + + for (int display = 0; display < numDisplays; display++) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Display "); + sb.append(display); + sb.append(" Statistics:"); + pw.println(sb.toString()); + + final long displayScreenOnTime = getDisplayScreenOnTime(display, rawRealtime); + sb.setLength(0); + sb.append(prefix); + sb.append(" Screen on: "); + formatTimeMs(sb, displayScreenOnTime / 1000); + sb.append("("); + sb.append(formatRatioLocked(displayScreenOnTime, whichBatteryRealtime)); + sb.append(") "); + pw.println(sb.toString()); + + sb.setLength(0); + sb.append(" Screen brightness levels:"); + didOne = false; + for (int bin = 0; bin < NUM_SCREEN_BRIGHTNESS_BINS; bin++) { + final long timeUs = getDisplayScreenBrightnessTime(display, bin, rawRealtime); + if (timeUs == 0) { + continue; + } + didOne = true; + sb.append("\n "); + sb.append(prefix); + sb.append(SCREEN_BRIGHTNESS_NAMES[bin]); + sb.append(" "); + formatTimeMs(sb, timeUs / 1000); + sb.append("("); + sb.append(formatRatioLocked(timeUs, displayScreenOnTime)); + sb.append(")"); + } + if (!didOne) sb.append(" (no activity)"); + pw.println(sb.toString()); + + final long displayScreenDozeTimeUs = getDisplayScreenDozeTime(display, rawRealtime); + sb.setLength(0); + sb.append(prefix); + sb.append(" Screen Doze: "); + formatTimeMs(sb, displayScreenDozeTimeUs / 1000); + sb.append("("); + sb.append(formatRatioLocked(displayScreenDozeTimeUs, whichBatteryRealtime)); + sb.append(") "); + pw.println(sb.toString()); + } + pw.print(prefix); + sb.setLength(0); + sb.append(prefix); + sb.append(" MULTI-DISPLAY POWER SUMMARY END"); + pw.println(sb.toString()); + } + pw.println(""); pw.print(prefix); sb.setLength(0); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7472cca7fbd6..1b6a03c7a736 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -16966,40 +16966,42 @@ public final class Settings { "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION"; /** - * Activity Action: For system or preinstalled apps to show their {@link Activity} in 2-pane - * mode in Settings app on large screen devices. + * Activity Action: For system or preinstalled apps to show their {@link Activity} embedded + * in Settings app on large screen devices. * <p> - * Input: {@link #EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI} must be included to - * specify the intent for the activity which will be displayed in 2-pane mode in Settings app. + * Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI} must be included to + * specify the intent for the activity which will be embedded in Settings app. * It's an intent URI string from {@code intent.toUri(Intent.URI_INTENT_SCHEME)}. * - * Input: {@link #EXTRA_SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY} must be included to + * Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY} must be included to * specify a key that indicates the menu item which will be highlighted on settings home menu. * <p> * Output: Nothing. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK = - "android.settings.SETTINGS_LARGE_SCREEN_DEEP_LINK"; + public static final String ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY = + "android.settings.SETTINGS_EMBED_DEEP_LINK_ACTIVITY"; /** - * Activity Extra: Specify the intent for the {@link Activity} which will be displayed in 2-pane - * mode in Settings app. It's an intent URI string from + * Activity Extra: Specify the intent for the {@link Activity} which will be embedded in + * Settings app. It's an intent URI string from * {@code intent.toUri(Intent.URI_INTENT_SCHEME)}. * <p> - * This must be passed as an extra field to {@link #ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK}. + * This must be passed as an extra field to + * {@link #ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY}. */ - public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI = - "android.provider.extra.SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI"; + public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI = + "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI"; /** * Activity Extra: Specify a key that indicates the menu item which should be highlighted on * settings home menu. * <p> - * This must be passed as an extra field to {@link #ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK}. + * This must be passed as an extra field to + * {@link #ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY}. */ - public static final String EXTRA_SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY = - "android.provider.extra.SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY"; + public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY = + "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY"; /** * Performs a strict and comprehensive check of whether a calling package is allowed to diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 71f90fd28e3f..c94595468aec 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -83,11 +83,11 @@ import java.util.Objects; * </intent-filter> * <meta-data * android:name="android.service.notification.default_filter_types" - * android:value="conversations,alerting"> + * android:value="conversations|alerting"> * </meta-data> * <meta-data * android:name="android.service.notification.disabled_filter_types" - * android:value="ongoing,silent"> + * android:value="ongoing|silent"> * </meta-data> * </service></pre> * @@ -112,8 +112,9 @@ public abstract class NotificationListenerService extends Service { private final String TAG = getClass().getSimpleName(); /** - * The name of the {@code meta-data} tag containing a comma separated list of default - * integer notification types that should be provided to this listener. See + * The name of the {@code meta-data} tag containing a pipe separated list of default + * integer notification types or "ongoing", "conversations", "alerting", or "silent" + * that should be provided to this listener. See * {@link #FLAG_FILTER_TYPE_ONGOING}, * {@link #FLAG_FILTER_TYPE_CONVERSATIONS}, {@link #FLAG_FILTER_TYPE_ALERTING), * and {@link #FLAG_FILTER_TYPE_SILENT}. @@ -1698,7 +1699,7 @@ public abstract class NotificationListenerService extends Service { private ArrayList<Notification.Action> mSmartActions; private ArrayList<CharSequence> mSmartReplies; private boolean mCanBubble; - private boolean mVisuallyInterruptive; + private boolean mIsTextChanged; private boolean mIsConversation; private ShortcutInfo mShortcutInfo; private @RankingAdjustment int mRankingAdjustment; @@ -1735,7 +1736,7 @@ public abstract class NotificationListenerService extends Service { out.writeTypedList(mSmartActions, flags); out.writeCharSequenceList(mSmartReplies); out.writeBoolean(mCanBubble); - out.writeBoolean(mVisuallyInterruptive); + out.writeBoolean(mIsTextChanged); out.writeBoolean(mIsConversation); out.writeParcelable(mShortcutInfo, flags); out.writeInt(mRankingAdjustment); @@ -1773,7 +1774,7 @@ public abstract class NotificationListenerService extends Service { mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR); mSmartReplies = in.readCharSequenceList(); mCanBubble = in.readBoolean(); - mVisuallyInterruptive = in.readBoolean(); + mIsTextChanged = in.readBoolean(); mIsConversation = in.readBoolean(); mShortcutInfo = in.readParcelable(cl); mRankingAdjustment = in.readInt(); @@ -1976,8 +1977,8 @@ public abstract class NotificationListenerService extends Service { } /** @hide */ - public boolean visuallyInterruptive() { - return mVisuallyInterruptive; + public boolean isTextChanged() { + return mIsTextChanged; } /** @hide */ @@ -2032,7 +2033,7 @@ public abstract class NotificationListenerService extends Service { int userSentiment, boolean hidden, long lastAudiblyAlertedMs, boolean noisy, ArrayList<Notification.Action> smartActions, ArrayList<CharSequence> smartReplies, boolean canBubble, - boolean visuallyInterruptive, boolean isConversation, ShortcutInfo shortcutInfo, + boolean isTextChanged, boolean isConversation, ShortcutInfo shortcutInfo, int rankingAdjustment, boolean isBubble) { mKey = key; mRank = rank; @@ -2054,7 +2055,7 @@ public abstract class NotificationListenerService extends Service { mSmartActions = smartActions; mSmartReplies = smartReplies; mCanBubble = canBubble; - mVisuallyInterruptive = visuallyInterruptive; + mIsTextChanged = isTextChanged; mIsConversation = isConversation; mShortcutInfo = shortcutInfo; mRankingAdjustment = rankingAdjustment; @@ -2095,7 +2096,7 @@ public abstract class NotificationListenerService extends Service { other.mSmartActions, other.mSmartReplies, other.mCanBubble, - other.mVisuallyInterruptive, + other.mIsTextChanged, other.mIsConversation, other.mShortcutInfo, other.mRankingAdjustment, @@ -2152,7 +2153,7 @@ public abstract class NotificationListenerService extends Service { == (other.mSmartActions == null ? 0 : other.mSmartActions.size())) && Objects.equals(mSmartReplies, other.mSmartReplies) && Objects.equals(mCanBubble, other.mCanBubble) - && Objects.equals(mVisuallyInterruptive, other.mVisuallyInterruptive) + && Objects.equals(mIsTextChanged, other.mIsTextChanged) && Objects.equals(mIsConversation, other.mIsConversation) // Shortcutinfo doesn't have equals either; use id && Objects.equals((mShortcutInfo == null ? 0 : mShortcutInfo.getId()), diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java index b2fc9a0c85fc..69af2a5ce7fb 100644 --- a/core/java/android/view/AttachedSurfaceControl.java +++ b/core/java/android/view/AttachedSurfaceControl.java @@ -18,6 +18,7 @@ package android.view; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiThread; +import android.hardware.HardwareBuffer; /** * Provides an interface to the root-Surface of a View Hierarchy or Window. This @@ -84,41 +85,43 @@ public interface AttachedSurfaceControl { * Note, when using ANativeWindow APIs in conjunction with a NativeActivity Surface or * SurfaceView Surface, the buffer producer will already have access to the transform hint and * no additional work is needed. + * + * @see HardwareBuffer */ - default @Surface.Rotation int getSurfaceTransformHint() { - return Surface.ROTATION_0; + default @SurfaceControl.BufferTransform int getBufferTransformHint() { + return SurfaceControl.BUFFER_TRANSFORM_IDENTITY; } /** - * Surface transform hint change listener. - * @see #getSurfaceTransformHint + * Buffer transform hint change listener. + * @see #getBufferTransformHint */ @UiThread - interface OnSurfaceTransformHintChangedListener { + interface OnBufferTransformHintChangedListener { /** * @param hint new surface transform hint - * @see #getSurfaceTransformHint + * @see #getBufferTransformHint */ - void onSurfaceTransformHintChanged(@Surface.Rotation int hint); + void onBufferTransformHintChanged(@SurfaceControl.BufferTransform int hint); } /** - * Registers a surface transform hint changed listener to receive notifications about when + * Registers a {@link OnBufferTransformHintChangedListener} to receive notifications about when * the transform hint changes. * - * @see #getSurfaceTransformHint - * @see #removeOnSurfaceTransformHintChangedListener + * @see #getBufferTransformHint + * @see #removeOnBufferTransformHintChangedListener */ - default void addOnSurfaceTransformHintChangedListener( - @NonNull OnSurfaceTransformHintChangedListener listener) { + default void addOnBufferTransformHintChangedListener( + @NonNull OnBufferTransformHintChangedListener listener) { } /** - * Unregisters a surface transform hint changed listener. + * Unregisters a {@link OnBufferTransformHintChangedListener}. * - * @see #addOnSurfaceTransformHintChangedListener + * @see #addOnBufferTransformHintChangedListener */ - default void removeOnSurfaceTransformHintChangedListener( - @NonNull OnSurfaceTransformHintChangedListener listener) { + default void removeOnBufferTransformHintChangedListener( + @NonNull OnBufferTransformHintChangedListener listener) { } } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 8e149a98af0e..1791d3a731f7 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -347,6 +347,14 @@ interface IWindowManager Bitmap screenshotWallpaper(); /** + * Mirrors the wallpaper for the given display. + * + * @param displayId ID of the display for the wallpaper. + * @return A SurfaceControl for the parent of the mirrored wallpaper. + */ + SurfaceControl mirrorWallpaperSurface(int displayId); + + /** * Registers a wallpaper visibility listener. * @return Current visibility. */ diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 5b8dc4055509..960d23d7afb0 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -23,6 +23,7 @@ import static android.graphics.Matrix.MSKEW_Y; import static android.graphics.Matrix.MTRANS_X; import static android.graphics.Matrix.MTRANS_Y; import static android.view.SurfaceControlProto.HASH_CODE; +import static android.view.SurfaceControlProto.LAYER_ID; import static android.view.SurfaceControlProto.NAME; import android.annotation.FloatRange; @@ -240,8 +241,80 @@ public final class SurfaceControl implements Parcelable { private static native void nativeRemoveJankDataListener(long nativeListener); private static native long nativeCreateJankDataListenerWrapper(OnJankDataListener listener); private static native int nativeGetGPUContextPriority(); - private static native void nativeSetTransformHint(long nativeObject, int transformHint); + private static native void nativeSetTransformHint(long nativeObject, + @SurfaceControl.BufferTransform int transformHint); private static native int nativeGetTransformHint(long nativeObject); + private static native int nativeGetLayerId(long nativeObject); + + /** + * Transforms that can be applied to buffers as they are displayed to a window. + * + * Supported transforms are any combination of horizontal mirror, vertical mirror, and + * clock-wise 90 degree rotation, in that order. Rotations of 180 and 270 degrees are made up + * of those basic transforms. + * Mirrors {@code ANativeWindowTransform} definitions. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"BUFFER_TRANSFORM_"}, + value = {BUFFER_TRANSFORM_IDENTITY, BUFFER_TRANSFORM_MIRROR_HORIZONTAL, + BUFFER_TRANSFORM_MIRROR_VERTICAL, BUFFER_TRANSFORM_ROTATE_90, + BUFFER_TRANSFORM_ROTATE_180, BUFFER_TRANSFORM_ROTATE_270, + BUFFER_TRANSFORM_MIRROR_HORIZONTAL | BUFFER_TRANSFORM_ROTATE_90, + BUFFER_TRANSFORM_MIRROR_VERTICAL | BUFFER_TRANSFORM_ROTATE_90}) + public @interface BufferTransform { + } + + /** + * Identity transform. + * + * These transforms that can be applied to buffers as they are displayed to a window. + * @see HardwareBuffer + * + * Supported transforms are any combination of horizontal mirror, vertical mirror, and + * clock-wise 90 degree rotation, in that order. Rotations of 180 and 270 degrees are + * made up of those basic transforms. + */ + public static final int BUFFER_TRANSFORM_IDENTITY = 0x00; + /** + * Mirror horizontally. Can be combined with {@link #BUFFER_TRANSFORM_MIRROR_VERTICAL} + * and {@link #BUFFER_TRANSFORM_ROTATE_90}. + */ + public static final int BUFFER_TRANSFORM_MIRROR_HORIZONTAL = 0x01; + /** + * Mirror vertically. Can be combined with {@link #BUFFER_TRANSFORM_MIRROR_HORIZONTAL} + * and {@link #BUFFER_TRANSFORM_ROTATE_90}. + */ + public static final int BUFFER_TRANSFORM_MIRROR_VERTICAL = 0x02; + /** + * Rotate 90 degrees clock-wise. Can be combined with {@link + * #BUFFER_TRANSFORM_MIRROR_HORIZONTAL} and {@link #BUFFER_TRANSFORM_MIRROR_VERTICAL}. + */ + public static final int BUFFER_TRANSFORM_ROTATE_90 = 0x04; + /** + * Rotate 180 degrees clock-wise. Cannot be combined with other transforms. + */ + public static final int BUFFER_TRANSFORM_ROTATE_180 = + BUFFER_TRANSFORM_MIRROR_HORIZONTAL | BUFFER_TRANSFORM_MIRROR_VERTICAL; + /** + * Rotate 270 degrees clock-wise. Cannot be combined with other transforms. + */ + public static final int BUFFER_TRANSFORM_ROTATE_270 = + BUFFER_TRANSFORM_ROTATE_180 | BUFFER_TRANSFORM_ROTATE_90; + + /** + * @hide + */ + public static @BufferTransform int rotationToBufferTransform(@Surface.Rotation int rotation) { + switch (rotation) { + case Surface.ROTATION_0: return BUFFER_TRANSFORM_IDENTITY; + case Surface.ROTATION_90: return BUFFER_TRANSFORM_ROTATE_90; + case Surface.ROTATION_180: return BUFFER_TRANSFORM_ROTATE_180; + case Surface.ROTATION_270: return BUFFER_TRANSFORM_ROTATE_270; + } + Log.e(TAG, "Trying to convert unknown rotation=" + rotation); + return BUFFER_TRANSFORM_IDENTITY; + } @Nullable @GuardedBy("mLock") @@ -357,8 +430,6 @@ public final class SurfaceControl implements Parcelable { @GuardedBy("mLock") private int mHeight; - private int mTransformHint; - private WeakReference<View> mLocalOwnerView; static GlobalTransactionWrapper sGlobalTransaction; @@ -1541,6 +1612,7 @@ public final class SurfaceControl implements Parcelable { final long token = proto.start(fieldId); proto.write(HASH_CODE, System.identityHashCode(this)); proto.write(NAME, mName); + proto.write(LAYER_ID, getLayerId()); proto.end(token); } @@ -3658,7 +3730,7 @@ public final class SurfaceControl implements Parcelable { /** * @hide */ - public int getTransformHint() { + public @SurfaceControl.BufferTransform int getTransformHint() { checkNotReleased(); return nativeGetTransformHint(mNativeObject); } @@ -3672,7 +3744,18 @@ public final class SurfaceControl implements Parcelable { * with the same size. * @hide */ - public void setTransformHint(@Surface.Rotation int transformHint) { + public void setTransformHint(@SurfaceControl.BufferTransform int transformHint) { nativeSetTransformHint(mNativeObject, transformHint); } + + /** + * @hide + */ + public int getLayerId() { + if (mNativeObject != 0) { + return nativeGetLayerId(mNativeObject); + } + + return -1; + } } diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index 11b161ad3cb2..a6c5042db275 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -292,11 +292,18 @@ public class SurfaceControlViewHost { */ @TestApi public void relayout(WindowManager.LayoutParams attrs) { + relayout(attrs, SurfaceControl.Transaction::apply); + } + + /** + * Forces relayout and draw and allows to set a custom callback when it is finished + * @hide + */ + public void relayout(WindowManager.LayoutParams attrs, + WindowlessWindowManager.ResizeCompleteCallback callback) { mViewRoot.setLayoutParams(attrs, false); mViewRoot.setReportNextDraw(); - mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), (SurfaceControl.Transaction t) -> { - t.apply(); - }); + mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), callback); } /** diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 60bb99d37c39..856dfe53dfac 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -214,7 +214,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) final Rect mSurfaceFrame = new Rect(); int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; - int mTransformHint = 0; + @SurfaceControl.BufferTransform int mTransformHint = 0; private boolean mGlobalListenersAdded; private boolean mAttachedToWindow; @@ -1104,7 +1104,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall || mWindowSpaceTop != mLocation[1]; final boolean layoutSizeChanged = getWidth() != mScreenRect.width() || getHeight() != mScreenRect.height(); - final boolean hintChanged = (viewRoot.getSurfaceTransformHint() != mTransformHint) + final boolean hintChanged = (viewRoot.getBufferTransformHint() != mTransformHint) && mRequestedVisible; if (creating || formatChanged || sizeChanged || visibleChanged || @@ -1130,7 +1130,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mSurfaceHeight = myHeight; mFormat = mRequestedFormat; mLastWindowVisibility = mWindowVisibility; - mTransformHint = viewRoot.getSurfaceTransformHint(); + mTransformHint = viewRoot.getBufferTransformHint(); mScreenRect.left = mWindowSpaceLeft; mScreenRect.top = mWindowSpaceTop; @@ -1362,7 +1362,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall if (mBlastBufferQueue != null) { mBlastBufferQueue.destroy(); } - mTransformHint = viewRoot.getSurfaceTransformHint(); + mTransformHint = viewRoot.getBufferTransformHint(); mBlastSurfaceControl.setTransformHint(mTransformHint); mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat); @@ -1889,18 +1889,45 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * @param p The SurfacePackage to embed. */ public void setChildSurfacePackage(@NonNull SurfaceControlViewHost.SurfacePackage p) { + setChildSurfacePackage(p, false /* applyTransactionOnDraw */); + } + + /** + * Similar to setChildSurfacePackage, but using the BLAST queue so the transaction can be + * synchronized with the ViewRootImpl frame. + * @hide + */ + public void setChildSurfacePackageOnDraw( + @NonNull SurfaceControlViewHost.SurfacePackage p) { + setChildSurfacePackage(p, true /* applyTransactionOnDraw */); + } + + /** + * @param applyTransactionOnDraw Whether to apply transaction at onDraw or immediately. + */ + private void setChildSurfacePackage( + @NonNull SurfaceControlViewHost.SurfacePackage p, boolean applyTransactionOnDraw) { final SurfaceControl lastSc = mSurfacePackage != null ? mSurfacePackage.getSurfaceControl() : null; if (mSurfaceControl != null && lastSc != null) { - mTmpTransaction.reparent(lastSc, null).apply(); + mTmpTransaction.reparent(lastSc, null); mSurfacePackage.release(); + applyTransaction(applyTransactionOnDraw); } else if (mSurfaceControl != null) { reparentSurfacePackage(mTmpTransaction, p); - mTmpTransaction.apply(); + applyTransaction(applyTransactionOnDraw); } mSurfacePackage = p; } + private void applyTransaction(boolean applyTransactionOnDraw) { + if (applyTransactionOnDraw) { + getViewRootImpl().applyTransactionOnDraw(mTmpTransaction); + } else { + mTmpTransaction.apply(); + } + } + private void reparentSurfacePackage(SurfaceControl.Transaction t, SurfaceControlViewHost.SurfacePackage p) { final SurfaceControl sc = p.getSurfaceControl(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index b729c9fb01b1..572a7cdabdc9 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -27060,7 +27060,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, switch (event.mAction) { case DragEvent.ACTION_DRAG_STARTED: { - if (result && li.mOnDragListener != null) { + if (result && li != null && li.mOnDragListener != null) { sendWindowContentChangedAccessibilityEvent( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @@ -27074,7 +27074,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, refreshDrawableState(); } break; case DragEvent.ACTION_DROP: { - if (result && (li.mOnDragListener != null | li.mOnReceiveContentListener != null)) { + if (result && li != null && (li.mOnDragListener != null + || li.mOnReceiveContentListener != null)) { sendWindowContentChangedAccessibilityEvent( AccessibilityEvent.CONTENT_CHANGE_TYPE_DRAG_DROPPED); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index c45b27a8a2c2..5a3a9d5bb571 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -312,9 +312,10 @@ public final class ViewRootImpl implements ViewParent, static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<>(); static boolean sFirstDrawComplete = false; - private ArrayList<OnSurfaceTransformHintChangedListener> mTransformHintListeners = + private ArrayList<OnBufferTransformHintChangedListener> mTransformHintListeners = new ArrayList<>(); - private @Surface.Rotation int mPreviousTransformHint = Surface.ROTATION_0; + private @SurfaceControl.BufferTransform + int mPreviousTransformHint = SurfaceControl.BUFFER_TRANSFORM_IDENTITY; /** * Callback for notifying about global configuration changes. */ @@ -10499,38 +10500,38 @@ public final class ViewRootImpl implements ViewParent, } @Override - public @Surface.Rotation int getSurfaceTransformHint() { + public @SurfaceControl.BufferTransform int getBufferTransformHint() { return mSurfaceControl.getTransformHint(); } @Override - public void addOnSurfaceTransformHintChangedListener( - OnSurfaceTransformHintChangedListener listener) { + public void addOnBufferTransformHintChangedListener( + OnBufferTransformHintChangedListener listener) { Objects.requireNonNull(listener); if (mTransformHintListeners.contains(listener)) { throw new IllegalArgumentException( - "attempt to call addOnSurfaceTransformHintChangedListener() " + "attempt to call addOnBufferTransformHintChangedListener() " + "with a previously registered listener"); } mTransformHintListeners.add(listener); } @Override - public void removeOnSurfaceTransformHintChangedListener( - OnSurfaceTransformHintChangedListener listener) { + public void removeOnBufferTransformHintChangedListener( + OnBufferTransformHintChangedListener listener) { Objects.requireNonNull(listener); mTransformHintListeners.remove(listener); } - private void dispatchTransformHintChanged(@Surface.Rotation int hint) { + private void dispatchTransformHintChanged(@SurfaceControl.BufferTransform int hint) { if (mTransformHintListeners.isEmpty()) { return; } - ArrayList<OnSurfaceTransformHintChangedListener> listeners = - (ArrayList<OnSurfaceTransformHintChangedListener>) mTransformHintListeners.clone(); + ArrayList<OnBufferTransformHintChangedListener> listeners = + (ArrayList<OnBufferTransformHintChangedListener>) mTransformHintListeners.clone(); for (int i = 0; i < listeners.size(); i++) { - OnSurfaceTransformHintChangedListener listener = listeners.get(i); - listener.onSurfaceTransformHintChanged(hint); + OnBufferTransformHintChangedListener listener = listeners.get(i); + listener.onBufferTransformHintChanged(hint); } } } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 18013e815d13..c92a3a086a8b 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -18,6 +18,7 @@ package android.view; import android.animation.ValueAnimator; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentCallbacks2; @@ -709,6 +710,16 @@ public final class WindowManagerGlobal { } } } + + /** @hide */ + @Nullable + public SurfaceControl mirrorWallpaperSurface(int displayId) { + try { + return getWindowManagerService().mirrorWallpaperSurface(displayId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } final class WindowLeaked extends AndroidRuntimeException { diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 7631269d9c1c..1c915cb016d4 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -352,25 +352,11 @@ public final class WindowManagerImpl implements WindowManager { throw e.rethrowFromSystemServer(); } - int size = possibleDisplayInfos.size(); - DisplayInfo currentDisplayInfo; - WindowInsets windowInsets = null; - if (size > 0) { - currentDisplayInfo = possibleDisplayInfos.get(0); - - final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); - final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0; - // TODO(181127261) not computing insets correctly - need to have underlying - // frame reflect the faked orientation. - windowInsets = getWindowInsetsFromServerForDisplay( - currentDisplayInfo.displayId, params, - new Rect(0, 0, currentDisplayInfo.getNaturalWidth(), - currentDisplayInfo.getNaturalHeight()), isScreenRound, - WINDOWING_MODE_FULLSCREEN); - } - Set<WindowMetrics> maxMetrics = new HashSet<>(); - for (int i = 0; i < size; i++) { + WindowInsets windowInsets; + DisplayInfo currentDisplayInfo; + final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + for (int i = 0; i < possibleDisplayInfos.size(); i++) { currentDisplayInfo = possibleDisplayInfos.get(i); // Calculate max bounds for this rotation and state. @@ -378,7 +364,18 @@ public final class WindowManagerImpl implements WindowManager { currentDisplayInfo.logicalHeight); // Calculate insets for the rotated max bounds. - // TODO(181127261) calculate insets for each display rotation and state. + final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0; + // Initialize insets based upon display rotation. Note any window-provided insets + // will not be set. + windowInsets = getWindowInsetsFromServerForDisplay( + currentDisplayInfo.displayId, params, + new Rect(0, 0, currentDisplayInfo.getNaturalWidth(), + currentDisplayInfo.getNaturalHeight()), isScreenRound, + WINDOWING_MODE_FULLSCREEN); + // Set the hardware-provided insets. + windowInsets = new WindowInsets.Builder(windowInsets).setRoundedCorners( + currentDisplayInfo.roundedCorners) + .setDisplayCutout(currentDisplayInfo.displayCutout).build(); maxMetrics.add(new WindowMetrics(maxBounds, windowInsets)); } diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 52d3612b6f77..3b65ffd35730 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -740,7 +740,10 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par CONTENT_CHANGE_TYPE_STATE_DESCRIPTION, CONTENT_CHANGE_TYPE_PANE_TITLE, CONTENT_CHANGE_TYPE_PANE_APPEARED, - CONTENT_CHANGE_TYPE_PANE_DISAPPEARED + CONTENT_CHANGE_TYPE_PANE_DISAPPEARED, + CONTENT_CHANGE_TYPE_DRAG_STARTED, + CONTENT_CHANGE_TYPE_DRAG_DROPPED, + CONTENT_CHANGE_TYPE_DRAG_CANCELLED }) public @interface ContentChangeTypes {} @@ -989,6 +992,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par case CONTENT_CHANGE_TYPE_PANE_APPEARED: return "CONTENT_CHANGE_TYPE_PANE_APPEARED"; case CONTENT_CHANGE_TYPE_PANE_DISAPPEARED: return "CONTENT_CHANGE_TYPE_PANE_DISAPPEARED"; + case CONTENT_CHANGE_TYPE_DRAG_STARTED: return "CONTENT_CHANGE_TYPE_DRAG_STARTED"; + case CONTENT_CHANGE_TYPE_DRAG_DROPPED: return "CONTENT_CHANGE_TYPE_DRAG_DROPPED"; + case CONTENT_CHANGE_TYPE_DRAG_CANCELLED: return "CONTENT_CHANGE_TYPE_DRAG_CANCELLED"; default: return Integer.toHexString(type); } } @@ -1047,6 +1053,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par /** * Sets the event type. * + * <b>Note: An event must represent a single event type.</b> * @param eventType The event type. * * @throws IllegalStateException If called from an AccessibilityService. diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index 60402eb30ba7..aa73ed7c8908 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -426,15 +426,19 @@ public class UiTranslationController { continue; } mActivity.runOnUiThread(() -> { + ViewTranslationCallback callback = view.getViewTranslationCallback(); if (view.getViewTranslationResponse() != null && view.getViewTranslationResponse().equals(response)) { - if (DEBUG) { - Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId - + ". Ignoring."); + if (callback instanceof TextViewTranslationCallback) { + if (((TextViewTranslationCallback) callback).isShowingTranslation()) { + if (DEBUG) { + Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId + + ". Ignoring."); + } + return; + } } - return; } - ViewTranslationCallback callback = view.getViewTranslationCallback(); if (callback == null) { if (view instanceof TextView) { // developer doesn't provide their override, we set the default TextView diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java index 152405bf4d37..4a78f3ee6fac 100644 --- a/core/java/android/widget/TextViewTranslationCallback.java +++ b/core/java/android/widget/TextViewTranslationCallback.java @@ -64,6 +64,12 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { */ @Override public boolean onShowTranslation(@NonNull View view) { + if (mIsShowingTranslation) { + if (DEBUG) { + Log.d(TAG, view + " is already showing translated text."); + } + return false; + } ViewTranslationResponse response = view.getViewTranslationResponse(); if (response == null) { Log.e(TAG, "onShowTranslation() shouldn't be called before " @@ -152,7 +158,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback { return true; } - boolean isShowingTranslation() { + public boolean isShowingTranslation() { return mIsShowingTranslation; } diff --git a/core/java/android/window/ITransitionMetricsReporter.aidl b/core/java/android/window/ITransitionMetricsReporter.aidl new file mode 100644 index 000000000000..00f71dc7bb90 --- /dev/null +++ b/core/java/android/window/ITransitionMetricsReporter.aidl @@ -0,0 +1,33 @@ +/* + * 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.window; + +import android.os.IBinder; + +/** + * Implemented by WM Core to know the metrics of transition that runs on a different process. + * @hide + */ +oneway interface ITransitionMetricsReporter { + + /** + * Called when the transition animation starts. + * + * @param startTime The time when the animation started. + */ + void reportAnimationStart(IBinder transitionToken, long startTime); +} diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl index e65fcdd7b13b..3c7cd0254e78 100644 --- a/core/java/android/window/IWindowOrganizerController.aidl +++ b/core/java/android/window/IWindowOrganizerController.aidl @@ -23,6 +23,7 @@ import android.view.RemoteAnimationAdapter; import android.window.IDisplayAreaOrganizerController; import android.window.ITaskFragmentOrganizerController; import android.window.ITaskOrganizerController; +import android.window.ITransitionMetricsReporter; import android.window.ITransitionPlayer; import android.window.IWindowContainerTransactionCallback; import android.window.WindowContainerToken; @@ -98,4 +99,7 @@ interface IWindowOrganizerController { * this will replace the existing one if set. */ void registerTransitionPlayer(in ITransitionPlayer player); + + /** @return An interface enabling the transition players to report its metrics. */ + ITransitionMetricsReporter getTransitionMetricsReporter(); } diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java index 3e0075857402..3354a6ca7738 100644 --- a/core/java/android/window/SplashScreen.java +++ b/core/java/android/window/SplashScreen.java @@ -241,7 +241,6 @@ public interface SplashScreen { public void handOverSplashScreenView(@NonNull IBinder token, @NonNull SplashScreenView splashScreenView) { - transferSurface(splashScreenView); dispatchOnExitAnimation(token, splashScreenView); } @@ -265,9 +264,5 @@ public interface SplashScreen { return impl != null && impl.mExitAnimationListener != null; } } - - private void transferSurface(@NonNull SplashScreenView splashScreenView) { - splashScreenView.transferSurface(); - } } } diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index f14294ee512e..f748d4bc121d 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -464,7 +464,10 @@ public final class SplashScreenView extends FrameLayout { } - void transferSurface() { + /** + * @hide + */ + public void syncTransferSurfaceOnDraw() { if (mSurfacePackage == null) { return; } @@ -474,8 +477,8 @@ public final class SplashScreenView extends FrameLayout { String.format("SurfacePackage'surface reparented to %s", parent))); Log.d(TAG, "Transferring surface " + mSurfaceView.toString()); } - mSurfaceView.setChildSurfacePackage(mSurfacePackage); + mSurfaceView.setChildSurfacePackageOnDraw(mSurfacePackage); } void initIconAnimation(Drawable iconDrawable, long duration) { @@ -533,10 +536,6 @@ public final class SplashScreenView extends FrameLayout { restoreSystemUIColors(); mWindow = null; } - if (mHostActivity != null) { - mHostActivity.setSplashScreenView(null); - mHostActivity = null; - } mHasRemoved = true; } @@ -582,7 +581,6 @@ public final class SplashScreenView extends FrameLayout { * @hide */ public void attachHostActivityAndSetSystemUIColors(Activity activity, Window window) { - activity.setSplashScreenView(this); mHostActivity = activity; mWindow = window; final WindowManager.LayoutParams attr = window.getAttributes(); diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index c2ffc03b6119..7208930c0b20 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -140,7 +140,7 @@ public final class TransitionInfo implements Parcelable { private TransitionInfo(Parcel in) { mType = in.readInt(); mFlags = in.readInt(); - in.readList(mChanges, null /* classLoader */); + in.readTypedList(mChanges, Change.CREATOR); mRootLeash = new SurfaceControl(); mRootLeash.readFromParcel(in); mRootOffset.readFromParcel(in); @@ -152,7 +152,7 @@ public final class TransitionInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mType); dest.writeInt(mFlags); - dest.writeList(mChanges); + dest.writeTypedList(mChanges); mRootLeash.writeToParcel(dest, flags); mRootOffset.writeToParcel(dest, flags); dest.writeTypedObject(mOptions, flags); diff --git a/core/java/android/window/TransitionMetrics.java b/core/java/android/window/TransitionMetrics.java new file mode 100644 index 000000000000..9a93c1a1ffd6 --- /dev/null +++ b/core/java/android/window/TransitionMetrics.java @@ -0,0 +1,57 @@ +/* + * 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.window; + +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Singleton; + +/** + * A helper class for who plays transition animation can report its metrics easily. + * @hide + */ +public class TransitionMetrics { + + private final ITransitionMetricsReporter mTransitionMetricsReporter; + + private TransitionMetrics(ITransitionMetricsReporter reporter) { + mTransitionMetricsReporter = reporter; + } + + /** Reports the current timestamp as when the transition animation starts. */ + public void reportAnimationStart(IBinder transitionToken) { + try { + mTransitionMetricsReporter.reportAnimationStart(transitionToken, + SystemClock.elapsedRealtime()); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** Gets the singleton instance of TransitionMetrics. */ + public static TransitionMetrics getInstance() { + return sTransitionMetrics.get(); + } + + private static final Singleton<TransitionMetrics> sTransitionMetrics = new Singleton<>() { + @Override + protected TransitionMetrics create() { + return new TransitionMetrics(WindowOrganizer.getTransitionMetricsReporter()); + } + }; +} diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 9d6488d7aa14..bbf813891387 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -295,7 +295,7 @@ public final class WindowContainerTransaction implements Parcelable { } /** - * Reparent's all children tasks of {@param currentParent} in the specified + * Reparent's all children tasks or the top task of {@param currentParent} in the specified * {@param windowingMode} and {@param activityType} to {@param newParent} in their current * z-order. * @@ -306,21 +306,46 @@ public final class WindowContainerTransaction implements Parcelable { * @param activityTypes of the tasks to reparent. * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to * the bottom. + * @param reparentTopOnly When {@code true}, only reparent the top task which fit windowingModes + * and activityTypes. + * @hide */ @NonNull public WindowContainerTransaction reparentTasks(@Nullable WindowContainerToken currentParent, @Nullable WindowContainerToken newParent, @Nullable int[] windowingModes, - @Nullable int[] activityTypes, boolean onTop) { + @Nullable int[] activityTypes, boolean onTop, boolean reparentTopOnly) { mHierarchyOps.add(HierarchyOp.createForChildrenTasksReparent( currentParent != null ? currentParent.asBinder() : null, newParent != null ? newParent.asBinder() : null, windowingModes, activityTypes, - onTop)); + onTop, + reparentTopOnly)); return this; } /** + * Reparent's all children tasks of {@param currentParent} in the specified + * {@param windowingMode} and {@param activityType} to {@param newParent} in their current + * z-order. + * + * @param currentParent of the tasks to perform the operation no. + * {@code null} will perform the operation on the display. + * @param newParent for the tasks. {@code null} will perform the operation on the display. + * @param windowingModes of the tasks to reparent. + * @param activityTypes of the tasks to reparent. + * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to + * the bottom. + */ + @NonNull + public WindowContainerTransaction reparentTasks(@Nullable WindowContainerToken currentParent, + @Nullable WindowContainerToken newParent, @Nullable int[] windowingModes, + @Nullable int[] activityTypes, boolean onTop) { + return reparentTasks(currentParent, newParent, windowingModes, activityTypes, onTop, + false /* reparentTopOnly */); + } + + /** * Sets whether a container should be the launch root for the specified windowing mode and * activity type. This currently only applies to Task containers created by organizer. */ @@ -948,6 +973,8 @@ public final class WindowContainerTransaction implements Parcelable { // Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom. private boolean mToTop; + private boolean mReparentTopOnly; + @Nullable private int[] mWindowingModes; @@ -985,13 +1012,15 @@ public final class WindowContainerTransaction implements Parcelable { } public static HierarchyOp createForChildrenTasksReparent(IBinder currentParent, - IBinder newParent, int[] windowingModes, int[] activityTypes, boolean onTop) { + IBinder newParent, int[] windowingModes, int[] activityTypes, boolean onTop, + boolean reparentTopOnly) { return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT) .setContainer(currentParent) .setReparentContainer(newParent) .setWindowingModes(windowingModes) .setActivityTypes(activityTypes) .setToTop(onTop) + .setReparentTopOnly(reparentTopOnly) .build(); } @@ -1040,6 +1069,7 @@ public final class WindowContainerTransaction implements Parcelable { mContainer = copy.mContainer; mReparent = copy.mReparent; mToTop = copy.mToTop; + mReparentTopOnly = copy.mReparentTopOnly; mWindowingModes = copy.mWindowingModes; mActivityTypes = copy.mActivityTypes; mLaunchOptions = copy.mLaunchOptions; @@ -1053,6 +1083,7 @@ public final class WindowContainerTransaction implements Parcelable { mContainer = in.readStrongBinder(); mReparent = in.readStrongBinder(); mToTop = in.readBoolean(); + mReparentTopOnly = in.readBoolean(); mWindowingModes = in.createIntArray(); mActivityTypes = in.createIntArray(); mLaunchOptions = in.readBundle(); @@ -1093,6 +1124,10 @@ public final class WindowContainerTransaction implements Parcelable { return mToTop; } + public boolean getReparentTopOnly() { + return mReparentTopOnly; + } + public int[] getWindowingModes() { return mWindowingModes; } @@ -1126,12 +1161,13 @@ public final class WindowContainerTransaction implements Parcelable { switch (mType) { case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: return "{ChildrenTasksReparent: from=" + mContainer + " to=" + mReparent - + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes - + " mActivityType=" + mActivityTypes + "}"; + + " mToTop=" + mToTop + " mReparentTopOnly=" + mReparentTopOnly + + " mWindowingMode=" + Arrays.toString(mWindowingModes) + + " mActivityType=" + Arrays.toString(mActivityTypes) + "}"; case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: return "{SetLaunchRoot: container=" + mContainer - + " mWindowingMode=" + mWindowingModes - + " mActivityType=" + mActivityTypes + "}"; + + " mWindowingMode=" + Arrays.toString(mWindowingModes) + + " mActivityType=" + Arrays.toString(mActivityTypes) + "}"; case HIERARCHY_OP_TYPE_REPARENT: return "{reparent: " + mContainer + " to " + (mToTop ? "top of " : "bottom of ") + mReparent + "}"; @@ -1163,8 +1199,9 @@ public final class WindowContainerTransaction implements Parcelable { + " adjacentContainer=" + mReparent + "}"; default: return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent - + " mToTop=" + mToTop + " mWindowingMode=" + mWindowingModes - + " mActivityType=" + mActivityTypes + "}"; + + " mToTop=" + mToTop + + " mWindowingMode=" + Arrays.toString(mWindowingModes) + + " mActivityType=" + Arrays.toString(mActivityTypes) + "}"; } } @@ -1174,6 +1211,7 @@ public final class WindowContainerTransaction implements Parcelable { dest.writeStrongBinder(mContainer); dest.writeStrongBinder(mReparent); dest.writeBoolean(mToTop); + dest.writeBoolean(mReparentTopOnly); dest.writeIntArray(mWindowingModes); dest.writeIntArray(mActivityTypes); dest.writeBundle(mLaunchOptions); @@ -1211,6 +1249,8 @@ public final class WindowContainerTransaction implements Parcelable { private boolean mToTop; + private boolean mReparentTopOnly; + @Nullable private int[] mWindowingModes; @@ -1248,6 +1288,11 @@ public final class WindowContainerTransaction implements Parcelable { return this; } + Builder setReparentTopOnly(boolean reparentTopOnly) { + mReparentTopOnly = reparentTopOnly; + return this; + } + Builder setWindowingModes(@Nullable int[] windowingModes) { mWindowingModes = windowingModes; return this; @@ -1290,6 +1335,7 @@ public final class WindowContainerTransaction implements Parcelable { ? Arrays.copyOf(mActivityTypes, mActivityTypes.length) : null; hierarchyOp.mToTop = mToTop; + hierarchyOp.mReparentTopOnly = mReparentTopOnly; hierarchyOp.mLaunchOptions = mLaunchOptions; hierarchyOp.mActivityIntent = mActivityIntent; hierarchyOp.mPendingIntent = mPendingIntent; diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java index e9b8174567a8..4ea5ea5694fa 100644 --- a/core/java/android/window/WindowOrganizer.java +++ b/core/java/android/window/WindowOrganizer.java @@ -159,7 +159,19 @@ public class WindowOrganizer { } } - IWindowOrganizerController getWindowOrganizerController() { + /** + * @see TransitionMetrics + * @hide + */ + public static ITransitionMetricsReporter getTransitionMetricsReporter() { + try { + return getWindowOrganizerController().getTransitionMetricsReporter(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + static IWindowOrganizerController getWindowOrganizerController() { return IWindowOrganizerControllerSingleton.get(); } diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java index 0307268a28b5..94430704468f 100644 --- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java +++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java @@ -16,6 +16,8 @@ package com.android.internal.os; +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT; + import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; @@ -29,11 +31,15 @@ import java.util.List; * Estimates power consumed by the ambient display */ public class AmbientDisplayPowerCalculator extends PowerCalculator { - private final UsageBasedPowerEstimator mPowerEstimator; + private final UsageBasedPowerEstimator[] mPowerEstimators; public AmbientDisplayPowerCalculator(PowerProfile powerProfile) { - mPowerEstimator = new UsageBasedPowerEstimator( - powerProfile.getAveragePower(PowerProfile.POWER_AMBIENT_DISPLAY)); + final int numDisplays = powerProfile.getNumDisplays(); + mPowerEstimators = new UsageBasedPowerEstimator[numDisplays]; + for (int display = 0; display < numDisplays; display++) { + mPowerEstimators[display] = new UsageBasedPowerEstimator( + powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, display)); + } } /** @@ -47,8 +53,8 @@ public class AmbientDisplayPowerCalculator extends PowerCalculator { final int powerModel = getPowerModel(measuredEnergyUC, query); final long durationMs = calculateDuration(batteryStats, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED); - final double powerMah = getMeasuredOrEstimatedPower(powerModel, - measuredEnergyUC, mPowerEstimator, durationMs); + final double powerMah = calculateTotalPower(powerModel, batteryStats, rawRealtimeUs, + measuredEnergyUC); builder.getAggregateBatteryConsumerBuilder( BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY, durationMs) @@ -68,9 +74,8 @@ public class AmbientDisplayPowerCalculator extends PowerCalculator { final long measuredEnergyUC = batteryStats.getScreenDozeMeasuredBatteryConsumptionUC(); final long durationMs = calculateDuration(batteryStats, rawRealtimeUs, statsType); final int powerModel = getPowerModel(measuredEnergyUC); - final double powerMah = getMeasuredOrEstimatedPower(powerModel, - batteryStats.getScreenDozeMeasuredBatteryConsumptionUC(), - mPowerEstimator, durationMs); + final double powerMah = calculateTotalPower(powerModel, batteryStats, rawRealtimeUs, + measuredEnergyUC); if (powerMah > 0) { BatterySipper bs = new BatterySipper(BatterySipper.DrainType.AMBIENT_DISPLAY, null, 0); bs.usagePowerMah = powerMah; @@ -83,4 +88,26 @@ public class AmbientDisplayPowerCalculator extends PowerCalculator { private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) { return batteryStats.getScreenDozeTime(rawRealtimeUs, statsType) / 1000; } + + private double calculateTotalPower(@BatteryConsumer.PowerModel int powerModel, + BatteryStats batteryStats, long rawRealtimeUs, long consumptionUC) { + switch (powerModel) { + case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY: + return uCtoMah(consumptionUC); + case BatteryConsumer.POWER_MODEL_POWER_PROFILE: + default: + return calculateEstimatedPower(batteryStats, rawRealtimeUs); + } + } + + private double calculateEstimatedPower(BatteryStats batteryStats, long rawRealtimeUs) { + final int numDisplays = mPowerEstimators.length; + double power = 0; + for (int display = 0; display < numDisplays; display++) { + final long dozeTime = batteryStats.getDisplayScreenDozeTime(display, rawRealtimeUs) + / 1000; + power += mPowerEstimators[display].calculatePower(dozeTime); + } + return power; + } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index a817119a735f..169eff009bff 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -690,7 +690,7 @@ public class BatteryStatsImpl extends BatteryStats { * Schedule a sync because of a screen state change. */ Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery, - boolean onBatteryScreenOff, int screenState); + boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates); Future<?> scheduleCpuSyncDueToWakelockChange(long delayMillis); void cancelCpuSyncDueToWakelockChange(); Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis); @@ -851,17 +851,91 @@ public class BatteryStatsImpl extends BatteryStats { public boolean mRecordAllHistory; boolean mNoAutoReset; + /** + * Overall screen state. For multidisplay devices, this represents the current highest screen + * state of the displays. + */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) protected int mScreenState = Display.STATE_UNKNOWN; + /** + * Overall screen on timer. For multidisplay devices, this represents the time spent with at + * least one display in the screen on state. + */ StopwatchTimer mScreenOnTimer; + /** + * Overall screen doze timer. For multidisplay devices, this represents the time spent with + * screen doze being the highest screen state. + */ StopwatchTimer mScreenDozeTimer; - + /** + * Overall screen brightness bin. For multidisplay devices, this represents the current + * brightest screen. + */ int mScreenBrightnessBin = -1; + /** + * Overall screen brightness timers. For multidisplay devices, the {@link mScreenBrightnessBin} + * timer will be active at any given time + */ final StopwatchTimer[] mScreenBrightnessTimer = new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS]; boolean mPretendScreenOff; + private static class DisplayBatteryStats { + /** + * Per display screen state. + */ + public int screenState = Display.STATE_UNKNOWN; + /** + * Per display screen on timers. + */ + public StopwatchTimer screenOnTimer; + /** + * Per display screen doze timers. + */ + public StopwatchTimer screenDozeTimer; + /** + * Per display screen brightness bins. + */ + public int screenBrightnessBin = -1; + /** + * Per display screen brightness timers. + */ + public StopwatchTimer[] screenBrightnessTimers = + new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS]; + /** + * Per display screen state the last time {@link #updateDisplayMeasuredEnergyStatsLocked} + * was called. + */ + public int screenStateAtLastEnergyMeasurement = Display.STATE_UNKNOWN; + + DisplayBatteryStats(Clocks clocks, TimeBase timeBase) { + screenOnTimer = new StopwatchTimer(clocks, null, -1, null, + timeBase); + screenDozeTimer = new StopwatchTimer(clocks, null, -1, null, + timeBase); + for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; i++) { + screenBrightnessTimers[i] = new StopwatchTimer(clocks, null, -100 - i, null, + timeBase); + } + } + + /** + * Reset display timers. + */ + public void reset(long elapsedRealtimeUs) { + screenOnTimer.reset(false, elapsedRealtimeUs); + screenDozeTimer.reset(false, elapsedRealtimeUs); + for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; i++) { + screenBrightnessTimers[i].reset(false, elapsedRealtimeUs); + } + } + } + + DisplayBatteryStats[] mPerDisplayBatteryStats; + + private int mDisplayMismatchWtfCount = 0; + boolean mInteractive; StopwatchTimer mInteractiveTimer; @@ -1006,8 +1080,6 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") @VisibleForTesting protected @Nullable MeasuredEnergyStats mGlobalMeasuredEnergyStats; - /** Last known screen state. Needed for apportioning display energy. */ - int mScreenStateAtLastEnergyMeasurement = Display.STATE_UNKNOWN; /** Bluetooth Power calculator for attributing measured bluetooth charge consumption to uids */ @Nullable BluetoothPowerCalculator mBluetoothPowerCalculator = null; /** Cpu Power calculator for attributing measured cpu charge consumption to uids */ @@ -4308,8 +4380,10 @@ public class BatteryStatsImpl extends BatteryStats { public void setPretendScreenOff(boolean pretendScreenOff) { if (mPretendScreenOff != pretendScreenOff) { mPretendScreenOff = pretendScreenOff; - noteScreenStateLocked(pretendScreenOff ? Display.STATE_OFF : Display.STATE_ON, - mClocks.elapsedRealtime(), mClocks.uptimeMillis(), mClocks.currentTimeMillis()); + final int primaryScreenState = mPerDisplayBatteryStats[0].screenState; + noteScreenStateLocked(0, primaryScreenState, + mClocks.elapsedRealtime(), mClocks.uptimeMillis(), + mClocks.currentTimeMillis()); } } @@ -4907,29 +4981,158 @@ public class BatteryStatsImpl extends BatteryStats { } @GuardedBy("this") - public void noteScreenStateLocked(int state) { - noteScreenStateLocked(state, mClocks.elapsedRealtime(), mClocks.uptimeMillis(), + public void noteScreenStateLocked(int display, int state) { + noteScreenStateLocked(display, state, mClocks.elapsedRealtime(), mClocks.uptimeMillis(), mClocks.currentTimeMillis()); } @GuardedBy("this") - public void noteScreenStateLocked(int state, + public void noteScreenStateLocked(int display, int displayState, long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) { - state = mPretendScreenOff ? Display.STATE_OFF : state; - // Battery stats relies on there being 4 states. To accommodate this, new states beyond the // original 4 are mapped to one of the originals. - if (state > MAX_TRACKED_SCREEN_STATE) { - switch (state) { - case Display.STATE_VR: - state = Display.STATE_ON; + if (displayState > MAX_TRACKED_SCREEN_STATE) { + if (Display.isOnState(displayState)) { + displayState = Display.STATE_ON; + } else if (Display.isDozeState(displayState)) { + if (Display.isSuspendedState(displayState)) { + displayState = Display.STATE_DOZE_SUSPEND; + } else { + displayState = Display.STATE_DOZE; + } + } else if (Display.isOffState(displayState)) { + displayState = Display.STATE_OFF; + } else { + Slog.wtf(TAG, "Unknown screen state (not mapped): " + displayState); + displayState = Display.STATE_UNKNOWN; + } + } + // As of this point, displayState should be mapped to one of: + // - Display.STATE_ON, + // - Display.STATE_DOZE + // - Display.STATE_DOZE_SUSPEND + // - Display.STATE_OFF + // - Display.STATE_UNKNOWN + + int state; + int overallBin = mScreenBrightnessBin; + int externalUpdateFlag = 0; + boolean shouldScheduleSync = false; + final int numDisplay = mPerDisplayBatteryStats.length; + if (display < 0 || display >= numDisplay) { + Slog.wtf(TAG, "Unexpected note screen state for display " + display + " (only " + + mPerDisplayBatteryStats.length + " displays exist...)"); + return; + } + final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display]; + final int oldDisplayState = displayStats.screenState; + + if (oldDisplayState == displayState) { + // Nothing changed + state = mScreenState; + } else { + displayStats.screenState = displayState; + + // Stop timer for previous display state. + switch (oldDisplayState) { + case Display.STATE_ON: + displayStats.screenOnTimer.stopRunningLocked(elapsedRealtimeMs); + final int bin = displayStats.screenBrightnessBin; + if (bin >= 0) { + displayStats.screenBrightnessTimers[bin].stopRunningLocked( + elapsedRealtimeMs); + } + overallBin = evaluateOverallScreenBrightnessBinLocked(); + shouldScheduleSync = true; + break; + case Display.STATE_DOZE: + // Transition from doze to doze suspend can be ignored. + if (displayState == Display.STATE_DOZE_SUSPEND) break; + displayStats.screenDozeTimer.stopRunningLocked(elapsedRealtimeMs); + shouldScheduleSync = true; + break; + case Display.STATE_DOZE_SUSPEND: + // Transition from doze suspend to doze can be ignored. + if (displayState == Display.STATE_DOZE) break; + displayStats.screenDozeTimer.stopRunningLocked(elapsedRealtimeMs); + shouldScheduleSync = true; + break; + case Display.STATE_OFF: // fallthrough + case Display.STATE_UNKNOWN: + // Not tracked by timers. break; default: - Slog.wtf(TAG, "Unknown screen state (not mapped): " + state); + Slog.wtf(TAG, + "Attempted to stop timer for unexpected display state " + display); + } + + // Start timer for new display state. + switch (displayState) { + case Display.STATE_ON: + displayStats.screenOnTimer.startRunningLocked(elapsedRealtimeMs); + final int bin = displayStats.screenBrightnessBin; + if (bin >= 0) { + displayStats.screenBrightnessTimers[bin].startRunningLocked( + elapsedRealtimeMs); + } + overallBin = evaluateOverallScreenBrightnessBinLocked(); + shouldScheduleSync = true; + break; + case Display.STATE_DOZE: + // Transition from doze suspend to doze can be ignored. + if (oldDisplayState == Display.STATE_DOZE_SUSPEND) break; + displayStats.screenDozeTimer.startRunningLocked(elapsedRealtimeMs); + shouldScheduleSync = true; break; + case Display.STATE_DOZE_SUSPEND: + // Transition from doze to doze suspend can be ignored. + if (oldDisplayState == Display.STATE_DOZE) break; + displayStats.screenDozeTimer.startRunningLocked(elapsedRealtimeMs); + shouldScheduleSync = true; + break; + case Display.STATE_OFF: // fallthrough + case Display.STATE_UNKNOWN: + // Not tracked by timers. + break; + default: + Slog.wtf(TAG, + "Attempted to start timer for unexpected display state " + displayState + + " for display " + display); + } + + if (shouldScheduleSync + && mGlobalMeasuredEnergyStats != null + && mGlobalMeasuredEnergyStats.isStandardBucketSupported( + MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON)) { + // Display measured energy stats is available. Prepare to schedule an + // external sync. + externalUpdateFlag |= ExternalStatsSync.UPDATE_DISPLAY; + } + + // Reevaluate most important display screen state. + state = Display.STATE_UNKNOWN; + for (int i = 0; i < numDisplay; i++) { + final int tempState = mPerDisplayBatteryStats[i].screenState; + if (tempState == Display.STATE_ON + || state == Display.STATE_ON) { + state = Display.STATE_ON; + } else if (tempState == Display.STATE_DOZE + || state == Display.STATE_DOZE) { + state = Display.STATE_DOZE; + } else if (tempState == Display.STATE_DOZE_SUSPEND + || state == Display.STATE_DOZE_SUSPEND) { + state = Display.STATE_DOZE_SUSPEND; + } else if (tempState == Display.STATE_OFF + || state == Display.STATE_OFF) { + state = Display.STATE_OFF; + } } } + final boolean batteryRunning = mOnBatteryTimeBase.isRunning(); + final boolean batteryScreenOffRunning = mOnBatteryScreenOffTimeBase.isRunning(); + + state = mPretendScreenOff ? Display.STATE_OFF : state; if (mScreenState != state) { recordDailyStatsIfNeededLocked(true, currentTimeMs); final int oldState = mScreenState; @@ -4983,11 +5186,11 @@ public class BatteryStatsImpl extends BatteryStats { + Display.stateToString(state)); addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); } - // TODO: (Probably overkill) Have mGlobalMeasuredEnergyStats store supported flags and - // only update DISPLAY if it is. Currently overkill since CPU is scheduled anyway. - final int updateFlag = ExternalStatsSync.UPDATE_CPU | ExternalStatsSync.UPDATE_DISPLAY; - mExternalSync.scheduleSyncDueToScreenStateChange(updateFlag, - mOnBatteryTimeBase.isRunning(), mOnBatteryScreenOffTimeBase.isRunning(), state); + + // Per screen state Cpu stats needed. Prepare to schedule an external sync. + externalUpdateFlag |= ExternalStatsSync.UPDATE_CPU; + shouldScheduleSync = true; + if (Display.isOnState(state)) { updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state, uptimeMs * 1000, elapsedRealtimeMs * 1000); @@ -5005,33 +5208,116 @@ public class BatteryStatsImpl extends BatteryStats { updateDischargeScreenLevelsLocked(oldState, state); } } + + // Changing display states might have changed the screen used to determine the overall + // brightness. + maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs); + + if (shouldScheduleSync) { + final int numDisplays = mPerDisplayBatteryStats.length; + final int[] displayStates = new int[numDisplays]; + for (int i = 0; i < numDisplays; i++) { + displayStates[i] = mPerDisplayBatteryStats[i].screenState; + } + mExternalSync.scheduleSyncDueToScreenStateChange(externalUpdateFlag, + batteryRunning, batteryScreenOffRunning, state, displayStates); + } } @UnsupportedAppUsage public void noteScreenBrightnessLocked(int brightness) { - noteScreenBrightnessLocked(brightness, mClocks.elapsedRealtime(), mClocks.uptimeMillis()); + noteScreenBrightnessLocked(0, brightness); + } + + /** + * Note screen brightness change for a display. + */ + public void noteScreenBrightnessLocked(int display, int brightness) { + noteScreenBrightnessLocked(display, brightness, mClocks.elapsedRealtime(), + mClocks.uptimeMillis()); } - public void noteScreenBrightnessLocked(int brightness, long elapsedRealtimeMs, long uptimeMs) { + + /** + * Note screen brightness change for a display. + */ + public void noteScreenBrightnessLocked(int display, int brightness, long elapsedRealtimeMs, + long uptimeMs) { // Bin the brightness. int bin = brightness / (256/NUM_SCREEN_BRIGHTNESS_BINS); if (bin < 0) bin = 0; else if (bin >= NUM_SCREEN_BRIGHTNESS_BINS) bin = NUM_SCREEN_BRIGHTNESS_BINS-1; - if (mScreenBrightnessBin != bin) { - mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_BRIGHTNESS_MASK) - | (bin << HistoryItem.STATE_BRIGHTNESS_SHIFT); - if (DEBUG_HISTORY) Slog.v(TAG, "Screen brightness " + bin + " to: " - + Integer.toHexString(mHistoryCur.states)); - addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); + + final int overallBin; + + final int numDisplays = mPerDisplayBatteryStats.length; + if (display < 0 || display >= numDisplays) { + Slog.wtf(TAG, "Unexpected note screen brightness for display " + display + " (only " + + mPerDisplayBatteryStats.length + " displays exist...)"); + return; + } + + final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display]; + final int oldBin = displayStats.screenBrightnessBin; + if (oldBin == bin) { + // Nothing changed + overallBin = mScreenBrightnessBin; + } else { + displayStats.screenBrightnessBin = bin; + if (displayStats.screenState == Display.STATE_ON) { + if (oldBin >= 0) { + displayStats.screenBrightnessTimers[oldBin].stopRunningLocked( + elapsedRealtimeMs); + } + displayStats.screenBrightnessTimers[bin].startRunningLocked( + elapsedRealtimeMs); + } + overallBin = evaluateOverallScreenBrightnessBinLocked(); + } + + maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs); + } + + private int evaluateOverallScreenBrightnessBinLocked() { + int overallBin = -1; + final int numDisplays = getDisplayCount(); + for (int display = 0; display < numDisplays; display++) { + final int displayBrightnessBin; + if (mPerDisplayBatteryStats[display].screenState == Display.STATE_ON) { + displayBrightnessBin = mPerDisplayBatteryStats[display].screenBrightnessBin; + } else { + displayBrightnessBin = -1; + } + if (displayBrightnessBin > overallBin) { + overallBin = displayBrightnessBin; + } + } + return overallBin; + } + + private void maybeUpdateOverallScreenBrightness(int overallBin, long elapsedRealtimeMs, + long uptimeMs) { + if (mScreenBrightnessBin != overallBin) { + if (overallBin >= 0) { + mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK) + | (overallBin << HistoryItem.STATE_BRIGHTNESS_SHIFT); + if (DEBUG_HISTORY) { + Slog.v(TAG, "Screen brightness " + overallBin + " to: " + + Integer.toHexString(mHistoryCur.states)); + } + addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); + } if (mScreenState == Display.STATE_ON) { if (mScreenBrightnessBin >= 0) { mScreenBrightnessTimer[mScreenBrightnessBin] .stopRunningLocked(elapsedRealtimeMs); } - mScreenBrightnessTimer[bin] - .startRunningLocked(elapsedRealtimeMs); + if (overallBin >= 0) { + mScreenBrightnessTimer[overallBin] + .startRunningLocked(elapsedRealtimeMs); + } } - mScreenBrightnessBin = bin; + mScreenBrightnessBin = overallBin; } } @@ -6693,6 +6979,31 @@ public class BatteryStatsImpl extends BatteryStats { return mScreenBrightnessTimer[brightnessBin]; } + @Override + public int getDisplayCount() { + return mPerDisplayBatteryStats.length; + } + + @Override + public long getDisplayScreenOnTime(int display, long elapsedRealtimeUs) { + return mPerDisplayBatteryStats[display].screenOnTimer.getTotalTimeLocked(elapsedRealtimeUs, + STATS_SINCE_CHARGED); + } + + @Override + public long getDisplayScreenDozeTime(int display, long elapsedRealtimeUs) { + return mPerDisplayBatteryStats[display].screenDozeTimer.getTotalTimeLocked( + elapsedRealtimeUs, STATS_SINCE_CHARGED); + } + + @Override + public long getDisplayScreenBrightnessTime(int display, int brightnessBin, + long elapsedRealtimeUs) { + final DisplayBatteryStats displayStats = mPerDisplayBatteryStats[display]; + return displayStats.screenBrightnessTimers[brightnessBin].getTotalTimeLocked( + elapsedRealtimeUs, STATS_SINCE_CHARGED); + } + @Override public long getInteractiveTime(long elapsedRealtimeUs, int which) { return mInteractiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which); } @@ -10694,6 +11005,10 @@ public class BatteryStatsImpl extends BatteryStats { mScreenBrightnessTimer[i] = new StopwatchTimer(mClocks, null, -100-i, null, mOnBatteryTimeBase); } + + mPerDisplayBatteryStats = new DisplayBatteryStats[1]; + mPerDisplayBatteryStats[0] = new DisplayBatteryStats(mClocks, mOnBatteryTimeBase); + mInteractiveTimer = new StopwatchTimer(mClocks, null, -10, null, mOnBatteryTimeBase); mPowerSaveModeEnabledTimer = new StopwatchTimer(mClocks, null, -2, null, mOnBatteryTimeBase); @@ -10806,6 +11121,8 @@ public class BatteryStatsImpl extends BatteryStats { // Initialize the estimated battery capacity to a known preset one. mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity(); } + + setDisplayCountLocked(mPowerProfile.getNumDisplays()); } PowerProfile getPowerProfile() { @@ -10838,6 +11155,16 @@ public class BatteryStatsImpl extends BatteryStats { mExternalSync = sync; } + /** + * Initialize and set multi display timers and states. + */ + public void setDisplayCountLocked(int numDisplays) { + mPerDisplayBatteryStats = new DisplayBatteryStats[numDisplays]; + for (int i = 0; i < numDisplays; i++) { + mPerDisplayBatteryStats[i] = new DisplayBatteryStats(mClocks, mOnBatteryTimeBase); + } + } + public void updateDailyDeadlineLocked() { // Get the current time. long currentTimeMs = mDailyStartTimeMs = mClocks.currentTimeMillis(); @@ -11314,6 +11641,11 @@ public class BatteryStatsImpl extends BatteryStats { mScreenBrightnessTimer[i].reset(false, elapsedRealtimeUs); } + final int numDisplays = mPerDisplayBatteryStats.length; + for (int i = 0; i < numDisplays; i++) { + mPerDisplayBatteryStats[i].reset(elapsedRealtimeUs); + } + if (mPowerProfile != null) { mEstimatedBatteryCapacityMah = (int) mPowerProfile.getBatteryCapacity(); } else { @@ -12597,22 +12929,43 @@ public class BatteryStatsImpl extends BatteryStats { * is always 0 when the screen is not "ON" and whenever the rail energy is 0 (if supported). * To the extent that those assumptions are violated, the algorithm will err. * - * @param chargeUC amount of charge (microcoulombs) used by Display since this was last called. - * @param screenState screen state at the time this data collection was scheduled + * @param chargesUC amount of charge (microcoulombs) used by each Display since this was last + * called. + * @param screenStates each screen state at the time this data collection was scheduled */ @GuardedBy("this") - public void updateDisplayMeasuredEnergyStatsLocked(long chargeUC, int screenState, + public void updateDisplayMeasuredEnergyStatsLocked(long[] chargesUC, int[] screenStates, long elapsedRealtimeMs) { - if (DEBUG_ENERGY) Slog.d(TAG, "Updating display stats: " + chargeUC); + if (DEBUG_ENERGY) Slog.d(TAG, "Updating display stats: " + Arrays.toString(chargesUC)); if (mGlobalMeasuredEnergyStats == null) { return; } - final @StandardPowerBucket int powerBucket = - MeasuredEnergyStats.getDisplayPowerBucket(mScreenStateAtLastEnergyMeasurement); - mScreenStateAtLastEnergyMeasurement = screenState; + final int numDisplays; + if (mPerDisplayBatteryStats.length == screenStates.length) { + numDisplays = screenStates.length; + } else { + // if this point is reached, it will be reached every display state change. + // Rate limit the wtf logging to once every 100 display updates. + if (mDisplayMismatchWtfCount++ % 100 == 0) { + Slog.wtf(TAG, "Mismatch between PowerProfile reported display count (" + + mPerDisplayBatteryStats.length + + ") and PowerStatsHal reported display count (" + screenStates.length + + ")"); + } + // Keep the show going, use the shorter of the two. + numDisplays = mPerDisplayBatteryStats.length < screenStates.length + ? mPerDisplayBatteryStats.length : screenStates.length; + } - if (!mOnBatteryInternal || chargeUC <= 0) { + final int[] oldScreenStates = new int[numDisplays]; + for (int i = 0; i < numDisplays; i++) { + final int screenState = screenStates[i]; + oldScreenStates[i] = mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement; + mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement = screenState; + } + + if (!mOnBatteryInternal) { // There's nothing further to update. return; } @@ -12627,17 +12980,31 @@ public class BatteryStatsImpl extends BatteryStats { return; } - mGlobalMeasuredEnergyStats.updateStandardBucket(powerBucket, chargeUC); + long totalScreenOnChargeUC = 0; + for (int i = 0; i < numDisplays; i++) { + final long chargeUC = chargesUC[i]; + if (chargeUC <= 0) { + // There's nothing further to update. + continue; + } + + final @StandardPowerBucket int powerBucket = + MeasuredEnergyStats.getDisplayPowerBucket(oldScreenStates[i]); + mGlobalMeasuredEnergyStats.updateStandardBucket(powerBucket, chargeUC); + if (powerBucket == MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON) { + totalScreenOnChargeUC += chargeUC; + } + } // Now we blame individual apps, but only if the display was ON. - if (powerBucket != MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON) { + if (totalScreenOnChargeUC <= 0) { return; } // TODO(b/175726779): Consider unifying the code with the non-rail display power blaming. // NOTE: fg time is NOT pooled. If two uids are both somehow in fg, then that time is // 'double counted' and will simply exceed the realtime that elapsed. - // If multidisplay becomes a reality, this is probably more reasonable than pooling. + // TODO(b/175726779): collect per display uid visibility for display power attribution. // Collect total time since mark so that we can normalize power. final SparseDoubleArray fgTimeUsArray = new SparseDoubleArray(); @@ -12650,7 +13017,8 @@ public class BatteryStatsImpl extends BatteryStats { if (fgTimeUs == 0) continue; fgTimeUsArray.put(uid.getUid(), (double) fgTimeUs); } - distributeEnergyToUidsLocked(powerBucket, chargeUC, fgTimeUsArray, 0); + distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON, + totalScreenOnChargeUC, fgTimeUsArray, 0); } /** @@ -14556,7 +14924,12 @@ public class BatteryStatsImpl extends BatteryStats { public void initMeasuredEnergyStatsLocked(@Nullable boolean[] supportedStandardBuckets, String[] customBucketNames) { boolean supportedBucketMismatch = false; - mScreenStateAtLastEnergyMeasurement = mScreenState; + + final int numDisplays = mPerDisplayBatteryStats.length; + for (int i = 0; i < numDisplays; i++) { + final int screenState = mPerDisplayBatteryStats[i].screenState; + mPerDisplayBatteryStats[i].screenStateAtLastEnergyMeasurement = screenState; + } if (supportedStandardBuckets == null) { if (mGlobalMeasuredEnergyStats != null) { diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java index 4979ecbae8cb..93d562c571f8 100644 --- a/core/java/com/android/internal/os/PowerCalculator.java +++ b/core/java/com/android/internal/os/PowerCalculator.java @@ -133,32 +133,6 @@ public abstract class PowerCalculator { } /** - * Returns either the measured energy converted to mAh or a usage-based estimate. - */ - protected static double getMeasuredOrEstimatedPower(@BatteryConsumer.PowerModel int powerModel, - long measuredEnergyUC, UsageBasedPowerEstimator powerEstimator, long durationMs) { - switch (powerModel) { - case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY: - return uCtoMah(measuredEnergyUC); - case BatteryConsumer.POWER_MODEL_POWER_PROFILE: - default: - return powerEstimator.calculatePower(durationMs); - } - } - - /** - * Returns either the measured energy converted to mAh or a usage-based estimate. - */ - protected static double getMeasuredOrEstimatedPower( - long measuredEnergyUC, UsageBasedPowerEstimator powerEstimator, long durationMs) { - if (measuredEnergyUC != BatteryStats.POWER_DATA_UNAVAILABLE) { - return uCtoMah(measuredEnergyUC); - } else { - return powerEstimator.calculatePower(durationMs); - } - } - - /** * Prints formatted amount of power in milli-amp-hours. */ public static void printPowerMah(PrintWriter pw, double powerMah) { diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index add2304afe9d..4d19b35b1e16 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -17,10 +17,12 @@ package com.android.internal.os; +import android.annotation.StringDef; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; @@ -30,6 +32,8 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; @@ -40,6 +44,8 @@ import java.util.HashMap; */ public class PowerProfile { + public static final String TAG = "PowerProfile"; + /* * POWER_CPU_SUSPEND: Power consumption when CPU is in power collapse mode. * POWER_CPU_IDLE: Power consumption when CPU is awake (when a wake lock is held). This should @@ -145,12 +151,18 @@ public class PowerProfile { /** * Power consumption when screen is in doze/ambient/always-on mode, including backlight power. + * + * @deprecated Use {@link #POWER_GROUP_DISPLAY_AMBIENT} instead. */ + @Deprecated public static final String POWER_AMBIENT_DISPLAY = "ambient.on"; /** * Power consumption when screen is on, not including the backlight power. + * + * @deprecated Use {@link #POWER_GROUP_DISPLAY_SCREEN_ON} instead. */ + @Deprecated @UnsupportedAppUsage public static final String POWER_SCREEN_ON = "screen.on"; @@ -175,7 +187,10 @@ public class PowerProfile { /** * Power consumption at full backlight brightness. If the backlight is at * 50% brightness, then this should be multiplied by 0.5 + * + * @deprecated Use {@link #POWER_GROUP_DISPLAY_SCREEN_FULL} instead. */ + @Deprecated @UnsupportedAppUsage public static final String POWER_SCREEN_FULL = "screen.full"; @@ -221,6 +236,29 @@ public class PowerProfile { public static final String POWER_BATTERY_CAPACITY = "battery.capacity"; /** + * Power consumption when a screen is in doze/ambient/always-on mode, including backlight power. + */ + public static final String POWER_GROUP_DISPLAY_AMBIENT = "ambient.on.display"; + + /** + * Power consumption when a screen is on, not including the backlight power. + */ + public static final String POWER_GROUP_DISPLAY_SCREEN_ON = "screen.on.display"; + + /** + * Power consumption of a screen at full backlight brightness. + */ + public static final String POWER_GROUP_DISPLAY_SCREEN_FULL = "screen.full.display"; + + @StringDef(prefix = { "POWER_GROUP_" }, value = { + POWER_GROUP_DISPLAY_AMBIENT, + POWER_GROUP_DISPLAY_SCREEN_ON, + POWER_GROUP_DISPLAY_SCREEN_FULL, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface PowerGroup {} + + /** * A map from Power Use Item to its power consumption. */ static final HashMap<String, Double> sPowerItemMap = new HashMap<>(); @@ -255,6 +293,7 @@ public class PowerProfile { readPowerValuesFromXml(context, forTest); } initCpuClusters(); + initDisplays(); } } @@ -424,6 +463,58 @@ public class PowerProfile { return 0; } + private int mNumDisplays; + + private void initDisplays() { + // Figure out how many displays are listed in the power profile. + mNumDisplays = 0; + while (!Double.isNaN( + getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, mNumDisplays, Double.NaN)) + || !Double.isNaN( + getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, mNumDisplays, Double.NaN)) + || !Double.isNaN( + getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, mNumDisplays, + Double.NaN))) { + mNumDisplays++; + } + + // Handle legacy display power constants. + final Double deprecatedAmbientDisplay = sPowerItemMap.get(POWER_AMBIENT_DISPLAY); + boolean legacy = false; + if (deprecatedAmbientDisplay != null && mNumDisplays == 0) { + final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_AMBIENT, 0); + Slog.w(TAG, POWER_AMBIENT_DISPLAY + " is deprecated! Use " + key + " instead."); + sPowerItemMap.put(key, deprecatedAmbientDisplay); + legacy = true; + } + + final Double deprecatedScreenOn = sPowerItemMap.get(POWER_SCREEN_ON); + if (deprecatedScreenOn != null && mNumDisplays == 0) { + final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_SCREEN_ON, 0); + Slog.w(TAG, POWER_SCREEN_ON + " is deprecated! Use " + key + " instead."); + sPowerItemMap.put(key, deprecatedScreenOn); + legacy = true; + } + + final Double deprecatedScreenFull = sPowerItemMap.get(POWER_SCREEN_FULL); + if (deprecatedScreenFull != null && mNumDisplays == 0) { + final String key = getOrdinalPowerType(POWER_GROUP_DISPLAY_SCREEN_FULL, 0); + Slog.w(TAG, POWER_SCREEN_FULL + " is deprecated! Use " + key + " instead."); + sPowerItemMap.put(key, deprecatedScreenFull); + legacy = true; + } + if (legacy) { + mNumDisplays = 1; + } + } + + /** + * Returns the number built in displays on the device as defined in the power_profile.xml. + */ + public int getNumDisplays() { + return mNumDisplays; + } + /** * Returns the number of memory bandwidth buckets defined in power_profile.xml, or a * default value if the subsystem has no recorded value. @@ -496,6 +587,32 @@ public class PowerProfile { } /** + * Returns the average current in mA consumed by an ordinaled subsystem, or the given + * default value if the subsystem has no recorded value. + * + * @param group the subsystem {@link PowerGroup}. + * @param ordinal which entity in the {@link PowerGroup}. + * @param defaultValue the value to return if the subsystem has no recorded value. + * @return the average current in milliAmps. + */ + public double getAveragePowerForOrdinal(@PowerGroup String group, int ordinal, + double defaultValue) { + final String type = getOrdinalPowerType(group, ordinal); + return getAveragePowerOrDefault(type, defaultValue); + } + + /** + * Returns the average current in mA consumed by an ordinaled subsystem. + * + * @param group the subsystem {@link PowerGroup}. + * @param ordinal which entity in the {@link PowerGroup}. + * @return the average current in milliAmps. + */ + public double getAveragePowerForOrdinal(@PowerGroup String group, int ordinal) { + return getAveragePowerForOrdinal(group, ordinal, 0); + } + + /** * Returns the battery capacity, if available, in milli Amp Hours. If not available, * it returns zero. * @@ -682,4 +799,9 @@ public class PowerProfile { } } } + + // Creates the key for an ordinaled power constant from the group and ordinal. + private static String getOrdinalPowerType(@PowerGroup String group, int ordinal) { + return group + ordinal; + } } diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java index 1b3bc234fc0f..2b634598bbbc 100644 --- a/core/java/com/android/internal/os/ScreenPowerCalculator.java +++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java @@ -16,6 +16,9 @@ package com.android.internal.os; +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL; +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON; + import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.BatteryUsageStats; @@ -41,8 +44,8 @@ public class ScreenPowerCalculator extends PowerCalculator { // Minimum amount of time the screen should be on to start smearing drain to apps public static final long MIN_ACTIVE_TIME_FOR_SMEARING = 10 * DateUtils.MINUTE_IN_MILLIS; - private final UsageBasedPowerEstimator mScreenOnPowerEstimator; - private final UsageBasedPowerEstimator mScreenFullPowerEstimator; + private final UsageBasedPowerEstimator[] mScreenOnPowerEstimators; + private final UsageBasedPowerEstimator[] mScreenFullPowerEstimators; private static class PowerAndDuration { public long durationMs; @@ -50,10 +53,16 @@ public class ScreenPowerCalculator extends PowerCalculator { } public ScreenPowerCalculator(PowerProfile powerProfile) { - mScreenOnPowerEstimator = new UsageBasedPowerEstimator( - powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_ON)); - mScreenFullPowerEstimator = new UsageBasedPowerEstimator( - powerProfile.getAveragePower(PowerProfile.POWER_SCREEN_FULL)); + final int numDisplays = powerProfile.getNumDisplays(); + mScreenOnPowerEstimators = new UsageBasedPowerEstimator[numDisplays]; + mScreenFullPowerEstimators = new UsageBasedPowerEstimator[numDisplays]; + for (int display = 0; display < numDisplays; display++) { + mScreenOnPowerEstimators[display] = new UsageBasedPowerEstimator( + powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, display)); + mScreenFullPowerEstimators[display] = new UsageBasedPowerEstimator( + powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, + display)); + } } @Override @@ -168,7 +177,7 @@ public class ScreenPowerCalculator extends PowerCalculator { case BatteryConsumer.POWER_MODEL_POWER_PROFILE: default: totalPowerAndDuration.powerMah = calculateTotalPowerFromBrightness(batteryStats, - rawRealtimeUs, statsType, totalPowerAndDuration.durationMs); + rawRealtimeUs); } } @@ -190,19 +199,25 @@ public class ScreenPowerCalculator extends PowerCalculator { return batteryStats.getScreenOnTime(rawRealtimeUs, statsType) / 1000; } - private double calculateTotalPowerFromBrightness(BatteryStats batteryStats, long rawRealtimeUs, - int statsType, long durationMs) { - double power = mScreenOnPowerEstimator.calculatePower(durationMs); - for (int i = 0; i < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; i++) { - final long brightnessTime = - batteryStats.getScreenBrightnessTime(i, rawRealtimeUs, statsType) / 1000; - final double binPowerMah = mScreenFullPowerEstimator.calculatePower(brightnessTime) - * (i + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; - if (DEBUG && binPowerMah != 0) { - Slog.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime - + " power=" + formatCharge(binPowerMah)); + private double calculateTotalPowerFromBrightness(BatteryStats batteryStats, + long rawRealtimeUs) { + final int numDisplays = mScreenOnPowerEstimators.length; + double power = 0; + for (int display = 0; display < numDisplays; display++) { + final long displayTime = batteryStats.getDisplayScreenOnTime(display, rawRealtimeUs) + / 1000; + power += mScreenOnPowerEstimators[display].calculatePower(displayTime); + for (int bin = 0; bin < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; bin++) { + final long brightnessTime = batteryStats.getDisplayScreenBrightnessTime(display, + bin, rawRealtimeUs) / 1000; + final double binPowerMah = mScreenFullPowerEstimators[display].calculatePower( + brightnessTime) * (bin + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; + if (DEBUG && binPowerMah != 0) { + Slog.d(TAG, "Screen bin #" + bin + ": time=" + brightnessTime + + " power=" + formatCharge(binPowerMah)); + } + power += binPowerMah; } - power += binPowerMah; } return power; } diff --git a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl index 8e454db4cb04..419b1f8feac7 100644 --- a/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl +++ b/core/java/com/android/internal/policy/IKeyguardStateCallback.aidl @@ -20,5 +20,4 @@ interface IKeyguardStateCallback { void onSimSecureStateChanged(boolean simSecure); void onInputRestrictedStateChanged(boolean inputRestricted); void onTrustedChanged(boolean trusted); - void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper); }
\ No newline at end of file diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index db019a67772f..954204fe781c 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -84,6 +84,7 @@ public enum ProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM), WM_DEBUG_LAYER_MIRRORING(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM), + WM_DEBUG_WALLPAPER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM), TEST_GROUP(true, true, false, "WindowManagerProtoLogTest"); private final boolean mEnabled; diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index 5144a91706a1..4c519f4c779f 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -117,6 +117,11 @@ public class LatencyTracker { */ public static final int ACTION_LOCKSCREEN_UNLOCK = 11; + /** + * Time it takes to switch users. + */ + public static final int ACTION_USER_SWITCH = 12; + private static final int[] ACTIONS_ALL = { ACTION_EXPAND_PANEL, ACTION_TOGGLE_RECENTS, @@ -129,7 +134,8 @@ public class LatencyTracker { ACTION_START_RECENTS_ANIMATION, ACTION_ROTATE_SCREEN_SENSOR, ACTION_ROTATE_SCREEN_CAMERA_CHECK, - ACTION_LOCKSCREEN_UNLOCK + ACTION_LOCKSCREEN_UNLOCK, + ACTION_USER_SWITCH }; /** @hide */ @@ -145,7 +151,8 @@ public class LatencyTracker { ACTION_START_RECENTS_ANIMATION, ACTION_ROTATE_SCREEN_SENSOR, ACTION_ROTATE_SCREEN_CAMERA_CHECK, - ACTION_LOCKSCREEN_UNLOCK + ACTION_LOCKSCREEN_UNLOCK, + ACTION_USER_SWITCH }) @Retention(RetentionPolicy.SOURCE) public @interface Action { @@ -163,7 +170,8 @@ public class LatencyTracker { FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION, FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR, FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK, - FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK + FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK, + FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH }; private static LatencyTracker sLatencyTracker; @@ -247,6 +255,8 @@ public class LatencyTracker { return "ACTION_ROTATE_SCREEN_SENSOR"; case 12: return "ACTION_LOCKSCREEN_UNLOCK"; + case 13: + return "ACTION_USER_SWITCH"; default: throw new IllegalArgumentException("Invalid action"); } @@ -424,7 +434,7 @@ public class LatencyTracker { // start counting timeout. mTimeoutRunnable = timeoutAction; BackgroundThread.getHandler() - .postDelayed(mTimeoutRunnable, TimeUnit.SECONDS.toMillis(2)); + .postDelayed(mTimeoutRunnable, TimeUnit.SECONDS.toMillis(15)); } void end() { diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 5ce43df53819..1452c67ae3c6 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -1815,16 +1815,18 @@ static void nativeSetTransformHint(JNIEnv* env, jclass clazz, jlong nativeSurfac if (surface == nullptr) { return; } - surface->setTransformHint( - ui::Transform::toRotationFlags(static_cast<ui::Rotation>(transformHint))); + surface->setTransformHint(transformHint); } static jint nativeGetTransformHint(JNIEnv* env, jclass clazz, jlong nativeSurfaceControl) { sp<SurfaceControl> surface(reinterpret_cast<SurfaceControl*>(nativeSurfaceControl)); - ui::Transform::RotationFlags transformHintRotationFlags = - static_cast<ui::Transform::RotationFlags>(surface->getTransformHint()); + return surface->getTransformHint(); +} + +static jint nativeGetLayerId(JNIEnv* env, jclass clazz, jlong nativeSurfaceControl) { + sp<SurfaceControl> surface(reinterpret_cast<SurfaceControl*>(nativeSurfaceControl)); - return toRotationInt(ui::Transform::toRotation((transformHintRotationFlags))); + return surface->getLayerId(); } // ---------------------------------------------------------------------------- @@ -2026,6 +2028,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetTrustedOverlay }, {"nativeSetDropInputMode", "(JJI)V", (void*)nativeSetDropInputMode }, + {"nativeGetLayerId", "(J)I", + (void*)nativeGetLayerId }, // clang-format on }; diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 0121bff3e7ef..6f81b822554a 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -69,6 +69,7 @@ message RootWindowContainerProto { // know what activity types to check for when invoking splitscreen multi-window. optional bool is_home_recents_component = 6; repeated IdentifierProto pending_activities = 7 [deprecated=true]; + optional int32 default_min_size_resizable_task = 8; } message BarControllerProto { @@ -480,6 +481,7 @@ message WindowContainerProto { optional SurfaceAnimatorProto surface_animator = 4; repeated WindowContainerChildProto children = 5; optional IdentifierProto identifier = 6; + optional .android.view.SurfaceControlProto surface_control = 7; } /* represents a generic child of a WindowContainer */ diff --git a/core/proto/android/view/surfacecontrol.proto b/core/proto/android/view/surfacecontrol.proto index cbb243ba7872..5a5f035412b2 100644 --- a/core/proto/android/view/surfacecontrol.proto +++ b/core/proto/android/view/surfacecontrol.proto @@ -29,4 +29,5 @@ message SurfaceControlProto { optional int32 hash_code = 1; optional string name = 2 [ (android.privacy).dest = DEST_EXPLICIT ]; + optional int32 layerId = 3; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 1255dad27eed..3968193e044f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4866,15 +4866,15 @@ android:protectionLevel="signature|privileged" /> <!-- An application needs this permission for - {@link android.provider.Settings#ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK} to show its - {@link android.app.Activity} in 2-pane of Settings app. --> - <permission android:name="android.permission.LAUNCH_TWO_PANE_SETTINGS_DEEP_LINK" + {@link android.provider.Settings#ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY} to show its + {@link android.app.Activity} embedded in Settings app. --> + <permission android:name="android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK" android:protectionLevel="signature|preinstalled" /> <!-- @SystemApi {@link android.app.Activity} should require this permission to ensure that only - the settings app can embed it in a 2-pane window. + the settings app can embed it in a multi pane window. @hide --> - <permission android:name="android.permission.ALLOW_PLACE_IN_TWO_PANE_SETTINGS" + <permission android:name="android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS" android:protectionLevel="signature" /> <!-- @SystemApi Allows applications to set a live wallpaper. diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index c33257df730e..c27ff94bfc94 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -1452,7 +1452,7 @@ <string name="usb_power_notification_message" msgid="7284765627437897702">"جارٍ شحن الجهاز المتصل. انقر لعرض خيارات أكثر."</string> <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"تم اكتشاف ملحق صوتي تناظري"</string> <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"الجهاز الذي تم توصيله بالهاتف غير متوافق معه. انقر للحصول على المزيد من المعلومات."</string> - <string name="adb_active_notification_title" msgid="408390247354560331">"تم توصيل أداة تصحيح أخطاء الجهاز عبر USB"</string> + <string name="adb_active_notification_title" msgid="408390247354560331">"تم توصيل USB لتصحيح أخطاء الجهاز"</string> <string name="adb_active_notification_message" msgid="5617264033476778211">"انقر لإيقاف تصحيح أخطاء الجهاز عبر USB."</string> <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"اختيار إيقاف تصحيح أخطاء USB."</string> <string name="adbwifi_active_notification_title" msgid="6147343659168302473">"تم تفعيل ميزة \"تصحيح الأخطاء اللاسلكي\"."</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 67b2ee45d8c0..a974e90736dd 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -336,7 +336,7 @@ <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Legt die Zoom-Stufe und -Position auf dem Display fest."</string> <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Touch-Gesten möglich"</string> <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Tippen, Wischen, Zusammenziehen und andere Touch-Gesten möglich."</string> - <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gesten auf dem Fingerabdrucksensor"</string> + <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gesten auf dem Fingerabdrucksensor"</string> <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Erfasst Touch-Gesten auf dem Fingerabdrucksensor des Geräts."</string> <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Screenshot erstellen"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Es kann ein Screenshot des Displays erstellt werden."</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 4bb06bb0145d..8d4956d0cad0 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -100,7 +100,7 @@ <string name="peerTtyModeHco" msgid="5626377160840915617">"समवयस्क व्यक्तीने TTY मोड HCO ची विनंती केली"</string> <string name="peerTtyModeVco" msgid="572208600818270944">"समवयस्क व्यक्तीने TTY मोड VCO ची विनंती केली"</string> <string name="peerTtyModeOff" msgid="2420380956369226583">"समवयस्क व्यक्तीने TTY मोड बंद ची विनंती केली"</string> - <string name="serviceClassVoice" msgid="2065556932043454987">"Voice"</string> + <string name="serviceClassVoice" msgid="2065556932043454987">"व्हॉइस"</string> <string name="serviceClassData" msgid="4148080018967300248">"डेटा"</string> <string name="serviceClassFAX" msgid="2561653371698904118">"फॅक्स"</string> <string name="serviceClassSMS" msgid="1547664561704509004">"SMS"</string> @@ -299,14 +299,14 @@ <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"बॅटरी आणि डेटा वापराच्या तपशीलांसाठी टॅप करा"</string> <string name="foreground_service_multiple_separator" msgid="5002287361849863168">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string> <string name="safeMode" msgid="8974401416068943888">"सुरक्षित मोड"</string> - <string name="android_system_label" msgid="5974767339591067210">"Android सिस्टम"</string> + <string name="android_system_label" msgid="5974767339591067210">"Android सिस्टीम"</string> <string name="user_owner_label" msgid="8628726904184471211">"वैयक्तिक प्रोफाइलवर स्विच करा"</string> <string name="managed_profile_label" msgid="7316778766973512382">"कार्य प्रोफाइलवर स्विच करा"</string> <string name="permgrouplab_contacts" msgid="4254143639307316920">"संपर्क"</string> <string name="permgroupdesc_contacts" msgid="9163927941244182567">"आपल्या संपर्कांवर प्रवेश"</string> <string name="permgrouplab_location" msgid="1858277002233964394">"स्थान"</string> <string name="permgroupdesc_location" msgid="1995955142118450685">"या डिव्हाइसच्या स्थानावर प्रवेश"</string> - <string name="permgrouplab_calendar" msgid="6426860926123033230">"Calendar"</string> + <string name="permgrouplab_calendar" msgid="6426860926123033230">"कॅलेंडर"</string> <string name="permgroupdesc_calendar" msgid="6762751063361489379">"आपल्या कॅलेंडरवर प्रवेश"</string> <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string> <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS मेसेज पाठवणे आणि पाहणे हे"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 7b80f606c99e..70e0d1f37e63 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -358,7 +358,7 @@ <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"ਐਪ ਨੂੰ ਆਉਣ ਵਾਲੀ ਫ਼ੋਨ ਕਾਲ ਦਾ ਜਵਾਬ ਦੇਣ ਦੀ ਇਜਾਜ਼ਤ ਦਿੰਦੀ ਹੈ।"</string> <string name="permlab_receiveSms" msgid="505961632050451881">"ਲਿਖਤ ਸੁਨੇਹੇ (SMS) ਪ੍ਰਾਪਤ ਕਰੋ"</string> <string name="permdesc_receiveSms" msgid="1797345626687832285">"ਐਪ ਨੂੰ SMS ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰਨ ਅਤੇ ਉਹਨਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਸਦਾ ਮਤਲਬ ਹੈ ਕਿ ਐਪ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਤੇ ਭੇਜੇ ਗਏ ਸੁਨੇਹਿਆਂ ਨੂੰ ਤੁਹਾਨੂੰ ਦਿਖਾਏ ਬਿਨਾਂ ਨਿਰੀਖਣ ਕਰ ਸਕਦੀ ਹੈ ਜਾਂ ਮਿਟਾ ਸਕਦੀ ਹੈ।"</string> - <string name="permlab_receiveMms" msgid="4000650116674380275">"ਟੈਕਸਟ ਸੁਨੇਹੇ (MMS) ਪੜ੍ਹੋ"</string> + <string name="permlab_receiveMms" msgid="4000650116674380275">"ਲਿਖਤ ਸੁਨੇਹੇ (MMS) ਪ੍ਰਾਪਤ ਕਰੋ"</string> <string name="permdesc_receiveMms" msgid="958102423732219710">"ਐਪ ਨੂੰ MMS ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰਨ ਅਤੇ ਉਹਨਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਸਦਾ ਮਤਲਬ ਹੈ ਕਿ ਐਪ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਤੇ ਭੇਜੇ ਗਏ ਸੁਨੇਹਿਆਂ ਨੂੰ ਤੁਹਾਨੂੰ ਦਿਖਾਏ ਬਿਨਾਂ ਨਿਰੀਖਣ ਕਰ ਸਕਦੀ ਹੈ ਜਾਂ ਮਿਟਾ ਸਕਦੀ ਹੈ।"</string> <string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਨੇਹਿਆਂ ਨੂੰ ਅੱਗੇ ਭੇਜੋ"</string> <string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"ਐਪ ਨੂੰ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਨੇਹਿਆਂ ਦੇ ਪ੍ਰਾਪਤ ਹੁੰਦੇ ਹੀ ਉਹਨਾਂ ਨੂੰ ਅੱਗੇ ਭੇਜਣ ਲਈ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਮਾਡਿਊਲ ਨਾਲ ਜੋੜਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿੱਤੀ ਜਾਂਦੀ ਹੈ। ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਸੁਚੇਤਨਾਵਾਂ ਤੁਹਾਨੂੰ ਸੰਕਟਕਾਲੀ ਸਥਿਤੀਆਂ ਦੀ ਚਿਤਾਵਨੀ ਦੇਣ ਲਈ ਕੁਝ ਟਿਕਾਣਿਆਂ \'ਤੇ ਪ੍ਰਦਾਨ ਕੀਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ। ਭੈੜੀਆਂ ਐਪਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀ ਕਾਰਗੁਜ਼ਾਰੀ ਜਾਂ ਓਪਰੇਸ਼ਨ ਵਿੱਚ ਵਿਘਨ ਪਾ ਸਕਦੀਆਂ ਹਨ ਜਦੋਂ ਇੱਕ ਸੰਕਟਕਾਲੀ ਸੈੱਲ ਪ੍ਰਸਾਰਨ ਪ੍ਰਾਪਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ।"</string> @@ -374,7 +374,7 @@ <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"ਇਹ ਐਪ ਤੁਹਾਡੇ ਟੈਬਲੈੱਟ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਸਾਰੇ SMS (ਲਿਖਤ) ਸੁਨੇਹਿਆਂ ਨੂੰ ਪੜ੍ਹ ਸਕਦੀ ਹੈ।"</string> <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"ਇਹ ਐਪ ਤੁਹਾਡੇ Android TV ਡੀਵਾਈਸ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਸਾਰੇ SMS (ਲਿਖਤ) ਸੁਨੇਹਿਆਂ ਨੂੰ ਪੜ੍ਹ ਸਕਦੀ ਹੈ।"</string> <string name="permdesc_readSms" product="default" msgid="774753371111699782">"ਇਹ ਐਪ ਤੁਹਾਡੇ ਫ਼ੋਨ \'ਤੇ ਸਟੋਰ ਕੀਤੇ ਸਾਰੇ SMS (ਲਿਖਤ) ਸੁਨੇਹਿਆਂ ਨੂੰ ਪੜ੍ਹ ਸਕਦੀ ਹੈ।"</string> - <string name="permlab_receiveWapPush" msgid="4223747702856929056">"ਟੈਕਸਟ ਸੁਨੇਹੇ (WAP) ਪ੍ਰਾਪਤ ਕਰੋ"</string> + <string name="permlab_receiveWapPush" msgid="4223747702856929056">"ਲਿਖਤ ਸੁਨੇਹੇ (WAP) ਪ੍ਰਾਪਤ ਕਰੋ"</string> <string name="permdesc_receiveWapPush" msgid="1638677888301778457">"ਐਪ ਨੂੰ WAP ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਕਰਨ ਅਤੇ ਉਹਨਾਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਸ ਇਜਾਜ਼ਤ ਵਿੱਚ ਸ਼ਾਮਲ ਹੈ ਐਪ ਦੀ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਤੇ ਭੇਜੇ ਗਏ ਸੁਨੇਹਿਆਂ ਨੂੰ ਤੁਹਾਨੂੰ ਦਿਖਾਏ ਬਿਨਾਂ ਨਿਰੀਖਣ ਕਰਨ ਅਤੇ ਮਿਟਾਉਣ ਦੀ ਸਮਰੱਥਾ।"</string> <string name="permlab_getTasks" msgid="7460048811831750262">"ਚੱਲ ਰਹੇ ਐਪਸ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰੋ"</string> <string name="permdesc_getTasks" msgid="7388138607018233726">"ਐਪ ਨੂੰ ਵਰਤਮਾਨ ਵਿੱਚ ਅਤੇ ਹੁਣੇ ਜਿਹੇ ਚੱਲ ਰਹੇ ਕੰਮਾਂ ਬਾਰੇ ਵਿਸਤ੍ਰਿਤ ਜਾਣਕਾਰੀ ਮੁੜ ਪ੍ਰਾਪਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਐਪ ਨੂੰ ਇਸ ਬਾਰੇ ਜਾਣਕਾਰੀ ਖੋਜਣ ਦੀ ਆਗਿਆ ਦੇ ਸਕਦਾ ਹੈ ਕਿ ਡੀਵਾਈਸ ਤੇ ਕਿਹੜੀਆਂ ਐਪਲੀਕੇਸ਼ਨਾਂ ਵਰਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ।"</string> @@ -1046,7 +1046,7 @@ <string name="searchview_description_query" msgid="7430242366971716338">"ਖੋਜ ਪੁੱਛਗਿੱਛ"</string> <string name="searchview_description_clear" msgid="1989371719192982900">"ਸਵਾਲ ਹਟਾਓ"</string> <string name="searchview_description_submit" msgid="6771060386117334686">"ਸਵਾਲ ਪ੍ਰਸਤੁਤ ਕਰੋ"</string> - <string name="searchview_description_voice" msgid="42360159504884679">"ਵੌਇਸ ਖੋਜ"</string> + <string name="searchview_description_voice" msgid="42360159504884679">"ਅਵਾਜ਼ੀ ਖੋਜ"</string> <string name="enable_explore_by_touch_warning_title" msgid="5095399706284943314">"ਕੀ ਸਪੱਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ ਕਰੋ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਹੈ?"</string> <string name="enable_explore_by_touch_warning_message" product="tablet" msgid="1037295476738940824">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> \'ਸਪੱਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ\' ਨੂੰ ਸਮਰੱਥ ਬਣਾਉਣਾ ਚਾਹੁੰਦੀ ਹੈ। ਜਦੋਂ \'ਸਪੱਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ\' ਨੂੰ ਚਾਲੂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਤੁਸੀਂ ਇਸ ਬਾਰੇ ਵੇਰਵੇ ਸੁਣ ਜਾਂ ਦੇਖ ਸਕਦੇ ਹੋ ਕਿ ਤੁਹਾਡੀ ਉਂਗਲੀ ਦੇ ਹੇਠਾਂ ਕੀ ਹੈ ਜਾਂ ਟੈਬਲੈੱਟ ਨਾਲ ਇੰਟਰੈਕਟ ਕਰਨ ਲਈ ਸੰਕੇਤਾਂ ਦੀ ਪਾਲਣਾ ਕਰ ਸਕਦੇ ਹੋ।"</string> <string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> ਸਪਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ ਕਰੋ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਚਾਹੁੰਦਾ ਹੈ। ਜਦੋਂ ਸਪਰਸ਼ ਰਾਹੀਂ ਪੜਚੋਲ ਕਰੋ ਨੂੰ ਚਾਲੂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਤਾਂ ਤੁਸੀਂ ਇਸ ਬਾਰੇ ਵੇਰਵੇ ਸੁਣ ਜਾਂ ਦੇਖ ਸਕਦੇ ਹੋ ਤਿ ਤੁਹਾਡੀ ਉਂਗਲੀ ਦੇ ਹੇਠਾਂ ਕੀ ਹੈ ਜਾਂ ਫ਼ੋਨ ਨਾਲ ਇੰਟਰੈਕਟ ਕਰਨ ਲਈ ਸੰਕੇਤ ਪਰਫੌਰਮ ਕਰ ਸਕਦੇ ਹੋ।"</string> @@ -2010,7 +2010,7 @@ <string name="app_category_image" msgid="7307840291864213007">"ਫ਼ੋਟੋਆਂ ਅਤੇ ਚਿੱਤਰ"</string> <string name="app_category_social" msgid="2278269325488344054">"ਸਮਾਜਕ ਅਤੇ ਸੰਚਾਰ"</string> <string name="app_category_news" msgid="1172762719574964544">"ਖਬਰਾਂ ਅਤੇ ਰਸਾਲੇ"</string> - <string name="app_category_maps" msgid="6395725487922533156">"Maps ਅਤੇ ਨੈਵੀਗੇਸ਼ਨ"</string> + <string name="app_category_maps" msgid="6395725487922533156">"ਨਕਸ਼ੇ ਅਤੇ ਨੈਵੀਗੇਸ਼ਨ"</string> <string name="app_category_productivity" msgid="1844422703029557883">"ਉਤਪਾਦਕਤਾ"</string> <string name="app_category_accessibility" msgid="6643521607848547683">"ਪਹੁੰਚਯੋਗਤਾ"</string> <string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"ਡੀਵਾਈਸ ਸਟੋਰੇਜ"</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 1baaa1f3607d..b27af3447e66 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -176,10 +176,10 @@ <string name="contentServiceSync" msgid="2341041749565687871">"ஒத்திசை"</string> <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"ஒத்திசைக்க முடியவில்லை"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"அதிகளவிலான <xliff:g id="CONTENT_TYPE">%s</xliff:g> உள்ளடக்க வகைகளை நீக்க முயன்றுள்ளீர்கள்."</string> - <string name="low_memory" product="tablet" msgid="5557552311566179924">"டேப்லெட் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில கோப்புகளை அழிக்கவும்."</string> - <string name="low_memory" product="watch" msgid="3479447988234030194">"வாட்ச் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில கோப்புகளை நீக்கவும்."</string> + <string name="low_memory" product="tablet" msgid="5557552311566179924">"டேப்லெட் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில ஃபைல்களை அழிக்கவும்."</string> + <string name="low_memory" product="watch" msgid="3479447988234030194">"வாட்ச் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில ஃபைல்களை நீக்கவும்."</string> <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TVயின் சேமிப்பிடம் நிரம்பிவிட்டது. இடத்தைக் காலியாக்க சில ஃபைல்களை நீக்கவும்."</string> - <string name="low_memory" product="default" msgid="2539532364144025569">"மொபைல் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில கோப்புகளை அழிக்கவும்."</string> + <string name="low_memory" product="default" msgid="2539532364144025569">"மொபைல் சேமிப்பிடம் நிரம்பியது. இடத்தைக் காலியாக்க சில ஃபைல்களை அழிக்கவும்."</string> <plurals name="ssl_ca_cert_warning" formatted="false" msgid="2288194355006173029"> <item quantity="other">சான்றிதழ் அங்கீகாரங்கள் நிறுவப்பட்டன</item> <item quantity="one">சான்றிதழ் அங்கீகாரம் நிறுவப்பட்டது</item> @@ -311,7 +311,7 @@ <string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string> <string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS அனுப்பலாம், வந்த SMSகளைப் பார்க்கலாம்"</string> <string name="permgrouplab_storage" msgid="1938416135375282333">"ஃபைல்களும் மீடியாவும்"</string> - <string name="permgroupdesc_storage" msgid="6351503740613026600">"உங்கள் சாதனத்தில் உள்ள படங்கள், மீடியா மற்றும் கோப்புகளை அணுக வேண்டும்"</string> + <string name="permgroupdesc_storage" msgid="6351503740613026600">"உங்கள் சாதனத்தில் உள்ள படங்கள், மீடியா மற்றும் ஃபைல்களை அணுக வேண்டும்"</string> <string name="permgrouplab_microphone" msgid="2480597427667420076">"மைக்ரோஃபோன்"</string> <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ஒலிப் பதிவு செய்யலாம்"</string> <string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"உடல் செயல்பாடுகள்"</string> @@ -1413,7 +1413,7 @@ <string name="ext_media_new_notification_message" product="tv" msgid="216863352100263668">"அமைக்கத் தேர்ந்தெடுங்கள்"</string> <string name="ext_media_new_notification_message" product="automotive" msgid="5140127881613227162">"சாதனத்தை ரீஃபார்மேட் செய்ய வேண்டியிருக்கும். வெளியேற்ற தட்டவும்."</string> <string name="ext_media_ready_notification_message" msgid="777258143284919261">"படங்களையும் மீடியாவையும் மாற்றலாம்"</string> - <string name="ext_media_ready_notification_message" product="tv" msgid="8847134811163165935">"மீடியா கோப்புகளை உலாவுக"</string> + <string name="ext_media_ready_notification_message" product="tv" msgid="8847134811163165935">"மீடியா ஃபைல்களை உலாவுக"</string> <string name="ext_media_unmountable_notification_title" msgid="4895444667278979910">"<xliff:g id="NAME">%s</xliff:g> இல் சிக்கல்"</string> <string name="ext_media_unmountable_notification_title" product="automotive" msgid="3142723758949023280">"<xliff:g id="NAME">%s</xliff:g> வேலை செய்யவில்லை"</string> <string name="ext_media_unmountable_notification_message" msgid="3256290114063126205">"சரிசெய்ய, தட்டவும்"</string> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 77820d1dfa38..ab39152fc10f 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8372,7 +8372,7 @@ <attr name="supportsAmbientMode" format="boolean" /> <!-- Indicates that this wallpaper service should receive zoom transition updates when - changing the display state of the device (e.g. when folding or unfolding + changing the structural state of the device (e.g. when folding or unfolding a foldable device). When this value is set to true {@link android.service.wallpaper.WallpaperService.Engine} could receive zoom updates before or after changing the device state. Wallpapers receive zoom updates using @@ -8381,8 +8381,8 @@ {@link android.service.wallpaper.WallpaperService.Engine} is created and not destroyed. Default value is true. Corresponds to - {@link android.app.WallpaperInfo#shouldUseDefaultDisplayStateChangeTransition()} --> - <attr name="shouldUseDefaultDisplayStateChangeTransition" format="boolean" /> + {@link android.app.WallpaperInfo#shouldUseDefaultUnfoldTransition()} --> + <attr name="shouldUseDefaultUnfoldTransition" format="boolean" /> <!-- Uri that specifies a settings Slice for this wallpaper. --> <attr name="settingsSliceUri" format="string"/> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ddc9f0225025..3718d2832f51 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4068,7 +4068,7 @@ <string translatable="false" name="config_inCallNotificationSound">/product/media/audio/ui/InCallNotification.ogg</string> <!-- URI for default ringtone sound file to be used for silent ringer vibration --> - <string translatable="false" name="config_defaultRingtoneVibrationSound">/product/media/audio/ui/AttentionalHaptics.ogg</string> + <string translatable="false" name="config_defaultRingtoneVibrationSound"></string> <!-- Default number of notifications from the same app before they are automatically grouped by the OS --> <integer translatable="false" name="config_autoGroupAtCount">4</integer> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 462b1883e29d..7d489049d112 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3221,7 +3221,7 @@ <eat-comment /> <staging-public-group type="attr" first-id="0x01ff0000"> - <public name="shouldUseDefaultDisplayStateChangeTransition" /> + <public name="shouldUseDefaultUnfoldTransition" /> </staging-public-group> <staging-public-group type="id" first-id="0x01fe0000"> diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml index 166edca3d046..d310736ae121 100644 --- a/core/res/res/xml/power_profile.xml +++ b/core/res/res/xml/power_profile.xml @@ -27,9 +27,33 @@ are totally dependent on the platform and can vary significantly, so should be measured on the shipping platform with a power meter. --> - <item name="ambient.on">0.1</item> <!-- ~100mA --> - <item name="screen.on">0.1</item> <!-- ~100mA --> - <item name="screen.full">0.1</item> <!-- ~100mA --> + + <!-- Display related values. --> + <!-- Average battery current draw of display0 while in ambient mode, including backlight. + There must be one of these for each display, labeled: + ambient.on.display0, ambient.on.display1, etc... + + Each display suffix number should match it's ordinal in its display device config. + --> + <item name="ambient.on.display0">0.1</item> <!-- ~100mA --> + <!-- Average battery current draw of display0 while on without backlight. + There must be one of these for each display, labeled: + screen.on.display0, screen.on.display1, etc... + + Each display suffix number should match it's ordinal in its display device config. + --> + <item name="screen.on.display0">0.1</item> <!-- ~100mA --> + <!-- Average battery current draw of the backlight at full brightness. + The full current draw of display N at full brightness should be the sum of screen.on.displayN + and screen.full.displayN + + There must be one of these for each display, labeled: + screen.full.display0, screen.full.display1, etc... + + Each display suffix number should match it's ordinal in its display device config. + --> + <item name="screen.full.display0">0.1</item> <!-- ~100mA --> + <item name="bluetooth.active">0.1</item> <!-- Bluetooth data transfer, ~10mA --> <item name="bluetooth.on">0.1</item> <!-- Bluetooth on & connectable, but not connected, ~0.1mA --> <item name="wifi.on">0.1</item> <!-- ~3mA --> diff --git a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java index 79f7a5c9df18..130f552f6e3a 100644 --- a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java @@ -16,6 +16,8 @@ package com.android.internal.os; +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT; + import static com.google.common.truth.Truth.assertThat; import android.os.BatteryConsumer; @@ -36,26 +38,28 @@ public class AmbientDisplayPowerCalculatorTest { @Rule public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() - .setAveragePower(PowerProfile.POWER_AMBIENT_DISPLAY, 10.0); + .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0, 10.0) + .setNumDisplays(1); @Test public void testMeasuredEnergyBasedModel() { mStatsRule.initMeasuredEnergyStatsLocked(); BatteryStatsImpl stats = mStatsRule.getBatteryStats(); - stats.updateDisplayMeasuredEnergyStatsLocked(300_000_000, Display.STATE_ON, 0); + stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300_000_000}, + new int[]{Display.STATE_ON}, 0); - stats.noteScreenStateLocked(Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, + stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); - stats.updateDisplayMeasuredEnergyStatsLocked(200_000_000, Display.STATE_DOZE, - 30 * MINUTE_IN_MS); + stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{200_000_000}, + new int[]{Display.STATE_DOZE}, 30 * MINUTE_IN_MS); - stats.noteScreenStateLocked(Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, + stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS); - stats.updateDisplayMeasuredEnergyStatsLocked(100_000_000, Display.STATE_OFF, - 120 * MINUTE_IN_MS); + stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100_000_000}, + new int[]{Display.STATE_OFF}, 120 * MINUTE_IN_MS); AmbientDisplayPowerCalculator calculator = new AmbientDisplayPowerCalculator(mStatsRule.getPowerProfile()); @@ -73,12 +77,73 @@ public class AmbientDisplayPowerCalculatorTest { } @Test + public void testMeasuredEnergyBasedModel_multiDisplay() { + mStatsRule.initMeasuredEnergyStatsLocked() + .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 1, 20.0) + .setNumDisplays(2); + BatteryStatsImpl stats = mStatsRule.getBatteryStats(); + + + final int[] screenStates = new int[] {Display.STATE_OFF, Display.STATE_OFF}; + + stats.noteScreenStateLocked(0, screenStates[0], 0, 0, 0); + stats.noteScreenStateLocked(1, screenStates[1], 0, 0, 0); + stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300, 400}, screenStates, 0); + + // Switch display0 to doze + screenStates[0] = Display.STATE_DOZE; + stats.noteScreenStateLocked(0, screenStates[0], 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, + 30 * MINUTE_IN_MS); + stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{200, 300}, + screenStates, 30 * MINUTE_IN_MS); + + // Switch display1 to doze + screenStates[1] = Display.STATE_DOZE; + stats.noteScreenStateLocked(1, Display.STATE_DOZE, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS, + 90 * MINUTE_IN_MS); + // 100,000,000 uC should be attributed to display 0 doze here. + stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100_000_000, 700_000_000}, + screenStates, 90 * MINUTE_IN_MS); + + // Switch display0 to off + screenStates[0] = Display.STATE_OFF; + stats.noteScreenStateLocked(0, screenStates[0], 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, + 120 * MINUTE_IN_MS); + // 40,000,000 and 70,000,000 uC should be attributed to display 0 and 1 doze here. + stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{40_000_000, 70_000_000}, + screenStates, 120 * MINUTE_IN_MS); + + // Switch display1 to off + screenStates[1] = Display.STATE_OFF; + stats.noteScreenStateLocked(1, screenStates[1], 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS, + 150 * MINUTE_IN_MS); + stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100, 90_000_000}, screenStates, + 150 * MINUTE_IN_MS); + // 90,000,000 uC should be attributed to display 1 doze here. + + AmbientDisplayPowerCalculator calculator = + new AmbientDisplayPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(calculator); + + BatteryConsumer consumer = mStatsRule.getDeviceBatteryConsumer(); + assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY)) + .isEqualTo(120 * MINUTE_IN_MS); + // 100,000,000 + 40,000,000 + 70,000,000 + 90,000,000 uC / 1000 (micro-/milli-) / 3600 + // (seconds/hour) = 27.777778 mAh + assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY)) + .isWithin(PRECISION).of(83.33333); + assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY)) + .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + } + + @Test public void testPowerProfileBasedModel() { BatteryStatsImpl stats = mStatsRule.getBatteryStats(); - stats.noteScreenStateLocked(Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, + stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); - stats.noteScreenStateLocked(Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, + stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS); AmbientDisplayPowerCalculator calculator = @@ -94,4 +159,36 @@ public class AmbientDisplayPowerCalculatorTest { assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY)) .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE); } + + @Test + public void testPowerProfileBasedModel_multiDisplay() { + mStatsRule.setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 1, 20.0) + .setNumDisplays(2); + + BatteryStatsImpl stats = mStatsRule.getBatteryStats(); + + stats.noteScreenStateLocked(1, Display.STATE_OFF, 0, 0, 0); + stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS, + 30 * MINUTE_IN_MS); + stats.noteScreenStateLocked(1, Display.STATE_DOZE, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS, + 90 * MINUTE_IN_MS); + stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS, + 120 * MINUTE_IN_MS); + stats.noteScreenStateLocked(1, Display.STATE_OFF, 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS, + 150 * MINUTE_IN_MS); + + AmbientDisplayPowerCalculator calculator = + new AmbientDisplayPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator); + + BatteryConsumer consumer = mStatsRule.getDeviceBatteryConsumer(); + // Duration should only be the union of + assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY)) + .isEqualTo(120 * MINUTE_IN_MS); + assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY)) + .isWithin(PRECISION).of(35.0); + assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY)) + .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE); + } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java index d4799a8f5fd3..3e2885a74287 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java @@ -16,9 +16,13 @@ package com.android.internal.os; +import static android.os.BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; import static android.os.BatteryStats.STATS_SINCE_CHARGED; import static android.os.BatteryStats.WAKE_TYPE_PARTIAL; +import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_CPU; +import static com.android.internal.os.BatteryStatsImpl.ExternalStatsSync.UPDATE_DISPLAY; + import android.app.ActivityManager; import android.os.BatteryStats; import android.os.BatteryStats.HistoryItem; @@ -37,8 +41,10 @@ import com.android.internal.power.MeasuredEnergyStats; import junit.framework.TestCase; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.function.IntConsumer; /** * Test various BatteryStatsImpl noteStart methods. @@ -317,18 +323,130 @@ public class BatteryStatsNoteTest extends TestCase { public void testNoteScreenStateLocked() throws Exception { final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"}); bi.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0); - bi.noteScreenStateLocked(Display.STATE_ON); - bi.noteScreenStateLocked(Display.STATE_DOZE); + bi.noteScreenStateLocked(0, Display.STATE_ON); + + bi.noteScreenStateLocked(0, Display.STATE_DOZE); + assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(Display.STATE_DOZE, bi.getScreenState()); + assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + bi.noteScreenStateLocked(0, Display.STATE_ON); + assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(Display.STATE_ON, bi.getScreenState()); + assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + bi.noteScreenStateLocked(0, Display.STATE_OFF); + assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(Display.STATE_OFF, bi.getScreenState()); + assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + bi.noteScreenStateLocked(0, Display.STATE_DOZE_SUSPEND); + assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(Display.STATE_DOZE_SUSPEND, bi.getScreenState()); + assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + // STATE_VR note should map to STATE_ON. + bi.noteScreenStateLocked(0, Display.STATE_VR); + assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(Display.STATE_ON, bi.getScreenState()); + assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + // STATE_ON_SUSPEND note should map to STATE_ON. + bi.noteScreenStateLocked(0, Display.STATE_ON_SUSPEND); + assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(Display.STATE_ON, bi.getScreenState()); + // Transition from ON to ON state should not cause an External Sync + assertEquals(0, bi.getAndClearExternalStatsSyncFlags()); + } + + /** + * Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly for + * multi display devices + */ + @SmallTest + public void testNoteScreenStateLocked_multiDisplay() throws Exception { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setDisplayCountLocked(2); + bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"}); + + bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + bi.noteScreenStateLocked(0, Display.STATE_OFF); + bi.noteScreenStateLocked(1, Display.STATE_OFF); + + bi.noteScreenStateLocked(0, Display.STATE_DOZE); + assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(Display.STATE_DOZE, bi.getScreenState()); + assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + bi.noteScreenStateLocked(0, Display.STATE_ON); + assertEquals(Display.STATE_ON, bi.getScreenState()); + assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + bi.noteScreenStateLocked(0, Display.STATE_OFF); + assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(Display.STATE_OFF, bi.getScreenState()); + assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + bi.noteScreenStateLocked(0, Display.STATE_DOZE_SUSPEND); + assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(Display.STATE_DOZE_SUSPEND, bi.getScreenState()); + assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + // STATE_VR note should map to STATE_ON. + bi.noteScreenStateLocked(0, Display.STATE_VR); + assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(Display.STATE_ON, bi.getScreenState()); + assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + // STATE_ON_SUSPEND note should map to STATE_ON. + bi.noteScreenStateLocked(0, Display.STATE_ON_SUSPEND); + assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(Display.STATE_ON, bi.getScreenState()); + // Transition from ON to ON state should not cause an External Sync + assertEquals(0, bi.getAndClearExternalStatsSyncFlags()); + + bi.noteScreenStateLocked(1, Display.STATE_DOZE); + assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning()); + // Should remain STATE_ON since display0 is still on. + assertEquals(Display.STATE_ON, bi.getScreenState()); + // Overall screen state did not change, so no need to sync CPU stats. + assertEquals(UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + bi.noteScreenStateLocked(0, Display.STATE_DOZE); assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning()); - assertEquals(bi.getScreenState(), Display.STATE_DOZE); - bi.noteScreenStateLocked(Display.STATE_ON); + assertEquals(Display.STATE_DOZE, bi.getScreenState()); + assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + bi.noteScreenStateLocked(0, Display.STATE_ON); assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning()); - assertEquals(bi.getScreenState(), Display.STATE_ON); - bi.noteScreenStateLocked(Display.STATE_OFF); + assertEquals(Display.STATE_ON, bi.getScreenState()); + assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + bi.noteScreenStateLocked(0, Display.STATE_OFF); + assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(Display.STATE_DOZE, bi.getScreenState()); + assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + bi.noteScreenStateLocked(0, Display.STATE_DOZE_SUSPEND); assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning()); - assertEquals(bi.getScreenState(), Display.STATE_OFF); + assertEquals(Display.STATE_DOZE, bi.getScreenState()); + // Overall screen state did not change, so no need to sync CPU stats. + assertEquals(UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + bi.noteScreenStateLocked(0, Display.STATE_VR); + assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(Display.STATE_ON, bi.getScreenState()); + assertEquals(UPDATE_CPU | UPDATE_DISPLAY, bi.getAndClearExternalStatsSyncFlags()); + + bi.noteScreenStateLocked(0, Display.STATE_ON_SUSPEND); + assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning()); + assertEquals(Display.STATE_ON, bi.getScreenState()); + assertEquals(0, bi.getAndClearExternalStatsSyncFlags()); } /* @@ -352,32 +470,317 @@ public class BatteryStatsNoteTest extends TestCase { bi.updateTimeBasesLocked(true, Display.STATE_UNKNOWN, 100_000, 100_000); // Turn on display at 200us clocks.realtime = clocks.uptime = 200; - bi.noteScreenStateLocked(Display.STATE_ON); + bi.noteScreenStateLocked(0, Display.STATE_ON); assertEquals(150_000, bi.computeBatteryRealtime(250_000, STATS_SINCE_CHARGED)); assertEquals(100_000, bi.computeBatteryScreenOffRealtime(250_000, STATS_SINCE_CHARGED)); assertEquals(50_000, bi.getScreenOnTime(250_000, STATS_SINCE_CHARGED)); assertEquals(0, bi.getScreenDozeTime(250_000, STATS_SINCE_CHARGED)); + assertEquals(50_000, bi.getDisplayScreenOnTime(0, 250_000)); + assertEquals(0, bi.getDisplayScreenDozeTime(0, 250_000)); clocks.realtime = clocks.uptime = 310; - bi.noteScreenStateLocked(Display.STATE_OFF); + bi.noteScreenStateLocked(0, Display.STATE_OFF); assertEquals(250_000, bi.computeBatteryRealtime(350_000, STATS_SINCE_CHARGED)); assertEquals(140_000, bi.computeBatteryScreenOffRealtime(350_000, STATS_SINCE_CHARGED)); assertEquals(110_000, bi.getScreenOnTime(350_000, STATS_SINCE_CHARGED)); assertEquals(0, bi.getScreenDozeTime(350_000, STATS_SINCE_CHARGED)); + assertEquals(110_000, bi.getDisplayScreenOnTime(0, 350_000)); + assertEquals(0, bi.getDisplayScreenDozeTime(0, 350_000)); clocks.realtime = clocks.uptime = 400; - bi.noteScreenStateLocked(Display.STATE_DOZE); + bi.noteScreenStateLocked(0, Display.STATE_DOZE); assertEquals(400_000, bi.computeBatteryRealtime(500_000, STATS_SINCE_CHARGED)); assertEquals(290_000, bi.computeBatteryScreenOffRealtime(500_000, STATS_SINCE_CHARGED)); assertEquals(110_000, bi.getScreenOnTime(500_000, STATS_SINCE_CHARGED)); assertEquals(100_000, bi.getScreenDozeTime(500_000, STATS_SINCE_CHARGED)); + assertEquals(110_000, bi.getDisplayScreenOnTime(0, 500_000)); + assertEquals(100_000, bi.getDisplayScreenDozeTime(0, 500_000)); clocks.realtime = clocks.uptime = 1000; - bi.noteScreenStateLocked(Display.STATE_OFF); + bi.noteScreenStateLocked(0, Display.STATE_OFF); assertEquals(1400_000, bi.computeBatteryRealtime(1500_000, STATS_SINCE_CHARGED)); assertEquals(1290_000, bi.computeBatteryScreenOffRealtime(1500_000, STATS_SINCE_CHARGED)); assertEquals(110_000, bi.getScreenOnTime(1500_000, STATS_SINCE_CHARGED)); assertEquals(600_000, bi.getScreenDozeTime(1500_000, STATS_SINCE_CHARGED)); + assertEquals(110_000, bi.getDisplayScreenOnTime(0, 1500_000)); + assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1500_000)); + } + + /* + * Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly for multi display + * devices. + */ + @SmallTest + public void testNoteScreenStateTimersLocked_multiDisplay() throws Exception { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.setDisplayCountLocked(2); + + clocks.realtime = clocks.uptime = 100; + // Device startup, setOnBatteryLocked calls updateTimebases + bi.updateTimeBasesLocked(true, Display.STATE_UNKNOWN, 100_000, 100_000); + // Turn on display at 200us + clocks.realtime = clocks.uptime = 200; + bi.noteScreenStateLocked(0, Display.STATE_ON); + bi.noteScreenStateLocked(1, Display.STATE_OFF); + assertEquals(150_000, bi.computeBatteryRealtime(250_000, STATS_SINCE_CHARGED)); + assertEquals(100_000, bi.computeBatteryScreenOffRealtime(250_000, STATS_SINCE_CHARGED)); + assertEquals(50_000, bi.getScreenOnTime(250_000, STATS_SINCE_CHARGED)); + assertEquals(0, bi.getScreenDozeTime(250_000, STATS_SINCE_CHARGED)); + assertEquals(50_000, bi.getDisplayScreenOnTime(0, 250_000)); + assertEquals(0, bi.getDisplayScreenDozeTime(0, 250_000)); + assertEquals(0, bi.getDisplayScreenOnTime(1, 250_000)); + assertEquals(0, bi.getDisplayScreenDozeTime(1, 250_000)); + + clocks.realtime = clocks.uptime = 310; + bi.noteScreenStateLocked(0, Display.STATE_OFF); + assertEquals(250_000, bi.computeBatteryRealtime(350_000, STATS_SINCE_CHARGED)); + assertEquals(140_000, bi.computeBatteryScreenOffRealtime(350_000, STATS_SINCE_CHARGED)); + assertEquals(110_000, bi.getScreenOnTime(350_000, STATS_SINCE_CHARGED)); + assertEquals(0, bi.getScreenDozeTime(350_000, STATS_SINCE_CHARGED)); + assertEquals(110_000, bi.getDisplayScreenOnTime(0, 350_000)); + assertEquals(0, bi.getDisplayScreenDozeTime(0, 350_000)); + assertEquals(0, bi.getDisplayScreenOnTime(1, 350_000)); + assertEquals(0, bi.getDisplayScreenDozeTime(1, 350_000)); + + clocks.realtime = clocks.uptime = 400; + bi.noteScreenStateLocked(0, Display.STATE_DOZE); + assertEquals(400_000, bi.computeBatteryRealtime(500_000, STATS_SINCE_CHARGED)); + assertEquals(290_000, bi.computeBatteryScreenOffRealtime(500_000, STATS_SINCE_CHARGED)); + assertEquals(110_000, bi.getScreenOnTime(500_000, STATS_SINCE_CHARGED)); + assertEquals(100_000, bi.getScreenDozeTime(500_000, STATS_SINCE_CHARGED)); + assertEquals(110_000, bi.getDisplayScreenOnTime(0, 500_000)); + assertEquals(100_000, bi.getDisplayScreenDozeTime(0, 500_000)); + assertEquals(0, bi.getDisplayScreenOnTime(1, 500_000)); + assertEquals(0, bi.getDisplayScreenDozeTime(1, 500_000)); + + clocks.realtime = clocks.uptime = 1000; + bi.noteScreenStateLocked(0, Display.STATE_OFF); + assertEquals(1000_000, bi.computeBatteryRealtime(1100_000, STATS_SINCE_CHARGED)); + assertEquals(890_000, bi.computeBatteryScreenOffRealtime(1100_000, STATS_SINCE_CHARGED)); + assertEquals(110_000, bi.getScreenOnTime(1100_000, STATS_SINCE_CHARGED)); + assertEquals(600_000, bi.getScreenDozeTime(1100_000, STATS_SINCE_CHARGED)); + assertEquals(110_000, bi.getDisplayScreenOnTime(0, 1100_000)); + assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1100_000)); + assertEquals(0, bi.getDisplayScreenOnTime(1, 1100_000)); + assertEquals(0, bi.getDisplayScreenDozeTime(1, 1100_000)); + + clocks.realtime = clocks.uptime = 1200; + // Change state of second display to doze + bi.noteScreenStateLocked(1, Display.STATE_DOZE); + assertEquals(1150_000, bi.computeBatteryRealtime(1250_000, STATS_SINCE_CHARGED)); + assertEquals(1040_000, bi.computeBatteryScreenOffRealtime(1250_000, STATS_SINCE_CHARGED)); + assertEquals(110_000, bi.getScreenOnTime(1250_000, STATS_SINCE_CHARGED)); + assertEquals(650_000, bi.getScreenDozeTime(1250_000, STATS_SINCE_CHARGED)); + assertEquals(110_000, bi.getDisplayScreenOnTime(0, 1250_000)); + assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1250_000)); + assertEquals(0, bi.getDisplayScreenOnTime(1, 1250_000)); + assertEquals(50_000, bi.getDisplayScreenDozeTime(1, 1250_000)); + + clocks.realtime = clocks.uptime = 1310; + bi.noteScreenStateLocked(0, Display.STATE_ON); + assertEquals(1250_000, bi.computeBatteryRealtime(1350_000, STATS_SINCE_CHARGED)); + assertEquals(1100_000, bi.computeBatteryScreenOffRealtime(1350_000, STATS_SINCE_CHARGED)); + assertEquals(150_000, bi.getScreenOnTime(1350_000, STATS_SINCE_CHARGED)); + assertEquals(710_000, bi.getScreenDozeTime(1350_000, STATS_SINCE_CHARGED)); + assertEquals(150_000, bi.getDisplayScreenOnTime(0, 1350_000)); + assertEquals(600_000, bi.getDisplayScreenDozeTime(0, 1350_000)); + assertEquals(0, bi.getDisplayScreenOnTime(1, 1350_000)); + assertEquals(150_000, bi.getDisplayScreenDozeTime(1, 1350_000)); + + clocks.realtime = clocks.uptime = 1400; + bi.noteScreenStateLocked(0, Display.STATE_DOZE); + assertEquals(1400_000, bi.computeBatteryRealtime(1500_000, STATS_SINCE_CHARGED)); + assertEquals(1200_000, bi.computeBatteryScreenOffRealtime(1500_000, STATS_SINCE_CHARGED)); + assertEquals(200_000, bi.getScreenOnTime(1500_000, STATS_SINCE_CHARGED)); + assertEquals(810_000, bi.getScreenDozeTime(1500_000, STATS_SINCE_CHARGED)); + assertEquals(200_000, bi.getDisplayScreenOnTime(0, 1500_000)); + assertEquals(700_000, bi.getDisplayScreenDozeTime(0, 1500_000)); + assertEquals(0, bi.getDisplayScreenOnTime(1, 1500_000)); + assertEquals(300_000, bi.getDisplayScreenDozeTime(1, 1500_000)); + + clocks.realtime = clocks.uptime = 2000; + bi.noteScreenStateLocked(0, Display.STATE_OFF); + assertEquals(2000_000, bi.computeBatteryRealtime(2100_000, STATS_SINCE_CHARGED)); + assertEquals(1800_000, bi.computeBatteryScreenOffRealtime(2100_000, STATS_SINCE_CHARGED)); + assertEquals(200_000, bi.getScreenOnTime(2100_000, STATS_SINCE_CHARGED)); + assertEquals(1410_000, bi.getScreenDozeTime(2100_000, STATS_SINCE_CHARGED)); + assertEquals(200_000, bi.getDisplayScreenOnTime(0, 2100_000)); + assertEquals(1200_000, bi.getDisplayScreenDozeTime(0, 2100_000)); + assertEquals(0, bi.getDisplayScreenOnTime(1, 2100_000)); + assertEquals(900_000, bi.getDisplayScreenDozeTime(1, 2100_000)); + + + clocks.realtime = clocks.uptime = 2200; + // Change state of second display to on + bi.noteScreenStateLocked(1, Display.STATE_ON); + assertEquals(2150_000, bi.computeBatteryRealtime(2250_000, STATS_SINCE_CHARGED)); + assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(2250_000, STATS_SINCE_CHARGED)); + assertEquals(250_000, bi.getScreenOnTime(2250_000, STATS_SINCE_CHARGED)); + assertEquals(1510_000, bi.getScreenDozeTime(2250_000, STATS_SINCE_CHARGED)); + assertEquals(200_000, bi.getDisplayScreenOnTime(0, 2250_000)); + assertEquals(1200_000, bi.getDisplayScreenDozeTime(0, 2250_000)); + assertEquals(50_000, bi.getDisplayScreenOnTime(1, 2250_000)); + assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 2250_000)); + + clocks.realtime = clocks.uptime = 2310; + bi.noteScreenStateLocked(0, Display.STATE_ON); + assertEquals(2250_000, bi.computeBatteryRealtime(2350_000, STATS_SINCE_CHARGED)); + assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(2350_000, STATS_SINCE_CHARGED)); + assertEquals(350_000, bi.getScreenOnTime(2350_000, STATS_SINCE_CHARGED)); + assertEquals(1510_000, bi.getScreenDozeTime(2350_000, STATS_SINCE_CHARGED)); + assertEquals(240_000, bi.getDisplayScreenOnTime(0, 2350_000)); + assertEquals(1200_000, bi.getDisplayScreenDozeTime(0, 2350_000)); + assertEquals(150_000, bi.getDisplayScreenOnTime(1, 2350_000)); + assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 2350_000)); + + clocks.realtime = clocks.uptime = 2400; + bi.noteScreenStateLocked(0, Display.STATE_DOZE); + assertEquals(2400_000, bi.computeBatteryRealtime(2500_000, STATS_SINCE_CHARGED)); + assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(2500_000, STATS_SINCE_CHARGED)); + assertEquals(500_000, bi.getScreenOnTime(2500_000, STATS_SINCE_CHARGED)); + assertEquals(1510_000, bi.getScreenDozeTime(2500_000, STATS_SINCE_CHARGED)); + assertEquals(290_000, bi.getDisplayScreenOnTime(0, 2500_000)); + assertEquals(1300_000, bi.getDisplayScreenDozeTime(0, 2500_000)); + assertEquals(300_000, bi.getDisplayScreenOnTime(1, 2500_000)); + assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 2500_000)); + + clocks.realtime = clocks.uptime = 3000; + bi.noteScreenStateLocked(0, Display.STATE_OFF); + assertEquals(3000_000, bi.computeBatteryRealtime(3100_000, STATS_SINCE_CHARGED)); + assertEquals(1900_000, bi.computeBatteryScreenOffRealtime(3100_000, STATS_SINCE_CHARGED)); + assertEquals(1100_000, bi.getScreenOnTime(3100_000, STATS_SINCE_CHARGED)); + assertEquals(1510_000, bi.getScreenDozeTime(3100_000, STATS_SINCE_CHARGED)); + assertEquals(290_000, bi.getDisplayScreenOnTime(0, 3100_000)); + assertEquals(1800_000, bi.getDisplayScreenDozeTime(0, 3100_000)); + assertEquals(900_000, bi.getDisplayScreenOnTime(1, 3100_000)); + assertEquals(1000_000, bi.getDisplayScreenDozeTime(1, 3100_000)); + } + + + /** + * Test BatteryStatsImpl.noteScreenBrightnessLocked updates timers correctly. + */ + @SmallTest + public void testScreenBrightnessLocked_multiDisplay() throws Exception { + final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + + final int numDisplay = 2; + bi.setDisplayCountLocked(numDisplay); + + + final long[] overallExpected = new long[NUM_SCREEN_BRIGHTNESS_BINS]; + final long[][] perDisplayExpected = new long[numDisplay][NUM_SCREEN_BRIGHTNESS_BINS]; + class Bookkeeper { + public long currentTimeMs = 100; + public int overallActiveBin = -1; + public int[] perDisplayActiveBin = new int[numDisplay]; + } + final Bookkeeper bk = new Bookkeeper(); + Arrays.fill(bk.perDisplayActiveBin, -1); + + IntConsumer incrementTime = inc -> { + bk.currentTimeMs += inc; + if (bk.overallActiveBin >= 0) { + overallExpected[bk.overallActiveBin] += inc; + } + for (int i = 0; i < numDisplay; i++) { + final int bin = bk.perDisplayActiveBin[i]; + if (bin >= 0) { + perDisplayExpected[i][bin] += inc; + } + } + clocks.realtime = clocks.uptime = bk.currentTimeMs; + }; + + bi.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0); + bi.noteScreenStateLocked(0, Display.STATE_ON); + bi.noteScreenStateLocked(1, Display.STATE_ON); + + incrementTime.accept(100); + bi.noteScreenBrightnessLocked(0, 25); + bi.noteScreenBrightnessLocked(1, 25); + // floor(25/256*5) = bin 0 + bk.overallActiveBin = 0; + bk.perDisplayActiveBin[0] = 0; + bk.perDisplayActiveBin[1] = 0; + + incrementTime.accept(50); + checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs); + + incrementTime.accept(13); + bi.noteScreenBrightnessLocked(0, 100); + // floor(25/256*5) = bin 1 + bk.overallActiveBin = 1; + bk.perDisplayActiveBin[0] = 1; + + incrementTime.accept(44); + checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs); + + incrementTime.accept(22); + bi.noteScreenBrightnessLocked(1, 200); + // floor(200/256*5) = bin 3 + bk.overallActiveBin = 3; + bk.perDisplayActiveBin[1] = 3; + + incrementTime.accept(33); + checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs); + + incrementTime.accept(77); + bi.noteScreenBrightnessLocked(0, 150); + // floor(150/256*5) = bin 2 + // Overall active bin should not change + bk.perDisplayActiveBin[0] = 2; + + incrementTime.accept(88); + checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs); + + incrementTime.accept(11); + bi.noteScreenStateLocked(1, Display.STATE_OFF); + // Display 1 should timers should stop incrementing + // Overall active bin should fallback to display 0's bin + bk.overallActiveBin = 2; + bk.perDisplayActiveBin[1] = -1; + + incrementTime.accept(99); + checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs); + + incrementTime.accept(200); + bi.noteScreenBrightnessLocked(0, 255); + // floor(150/256*5) = bin 4 + bk.overallActiveBin = 4; + bk.perDisplayActiveBin[0] = 4; + + incrementTime.accept(300); + checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs); + + incrementTime.accept(200); + bi.noteScreenStateLocked(0, Display.STATE_DOZE); + // No displays are on. No brightness timers should be active. + bk.overallActiveBin = -1; + bk.perDisplayActiveBin[0] = -1; + + incrementTime.accept(300); + checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs); + + incrementTime.accept(400); + bi.noteScreenStateLocked(1, Display.STATE_ON); + // Display 1 turned back on. + bk.overallActiveBin = 3; + bk.perDisplayActiveBin[1] = 3; + + incrementTime.accept(500); + checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs); + + incrementTime.accept(600); + bi.noteScreenStateLocked(0, Display.STATE_ON); + // Display 0 turned back on. + bk.overallActiveBin = 4; + bk.perDisplayActiveBin[0] = 4; + + incrementTime.accept(700); + checkScreenBrightnesses(overallExpected, perDisplayExpected, bi, bk.currentTimeMs); } @SmallTest @@ -595,7 +998,7 @@ public class BatteryStatsNoteTest extends TestCase { bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"}); clocks.realtime = 0; - int screen = Display.STATE_OFF; + int[] screen = new int[]{Display.STATE_OFF}; boolean battery = false; final int uid1 = 10500; @@ -605,35 +1008,35 @@ public class BatteryStatsNoteTest extends TestCase { long globalDoze = 0; // Case A: uid1 off, uid2 off, battery off, screen off - bi.updateTimeBasesLocked(battery, screen, clocks.realtime*1000, 0); + bi.updateTimeBasesLocked(battery, screen[0], clocks.realtime * 1000, 0); bi.setOnBatteryInternal(battery); - bi.updateDisplayMeasuredEnergyStatsLocked(500_000, screen, clocks.realtime); + bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{500_000}, screen, clocks.realtime); checkMeasuredCharge("A", uid1, blame1, uid2, blame2, globalDoze, bi); // Case B: uid1 off, uid2 off, battery ON, screen off clocks.realtime += 17; battery = true; - bi.updateTimeBasesLocked(battery, screen, clocks.realtime*1000, 0); + bi.updateTimeBasesLocked(battery, screen[0], clocks.realtime * 1000, 0); bi.setOnBatteryInternal(battery); clocks.realtime += 19; - bi.updateDisplayMeasuredEnergyStatsLocked(510_000, screen, clocks.realtime); + bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{510_000}, screen, clocks.realtime); checkMeasuredCharge("B", uid1, blame1, uid2, blame2, globalDoze, bi); // Case C: uid1 ON, uid2 off, battery on, screen off clocks.realtime += 18; setFgState(uid1, true, bi); clocks.realtime += 18; - bi.updateDisplayMeasuredEnergyStatsLocked(520_000, screen, clocks.realtime); + bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{520_000}, screen, clocks.realtime); checkMeasuredCharge("C", uid1, blame1, uid2, blame2, globalDoze, bi); // Case D: uid1 on, uid2 off, battery on, screen ON clocks.realtime += 17; - screen = Display.STATE_ON; - bi.updateDisplayMeasuredEnergyStatsLocked(521_000, screen, clocks.realtime); + screen[0] = Display.STATE_ON; + bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{521_000}, screen, clocks.realtime); blame1 += 0; // Screen had been off during the measurement period checkMeasuredCharge("D.1", uid1, blame1, uid2, blame2, globalDoze, bi); clocks.realtime += 101; - bi.updateDisplayMeasuredEnergyStatsLocked(530_000, screen, clocks.realtime); + bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{530_000}, screen, clocks.realtime); blame1 += 530_000; checkMeasuredCharge("D.2", uid1, blame1, uid2, blame2, globalDoze, bi); @@ -641,33 +1044,33 @@ public class BatteryStatsNoteTest extends TestCase { clocks.realtime += 20; setFgState(uid2, true, bi); clocks.realtime += 40; - bi.updateDisplayMeasuredEnergyStatsLocked(540_000, screen, clocks.realtime); + bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{540_000}, screen, clocks.realtime); // In the past 60ms, sum of fg is 20+40+40=100ms. uid1 is blamed for 60/100; uid2 for 40/100 blame1 += 540_000 * (20 + 40) / (20 + 40 + 40); - blame2 += 540_000 * ( 0 + 40) / (20 + 40 + 40); + blame2 += 540_000 * (0 + 40) / (20 + 40 + 40); checkMeasuredCharge("E", uid1, blame1, uid2, blame2, globalDoze, bi); // Case F: uid1 on, uid2 OFF, battery on, screen on clocks.realtime += 40; setFgState(uid2, false, bi); clocks.realtime += 120; - bi.updateDisplayMeasuredEnergyStatsLocked(550_000, screen, clocks.realtime); + bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{550_000}, screen, clocks.realtime); // In the past 160ms, sum f fg is 200ms. uid1 is blamed for 40+120 of it; uid2 for 40 of it. blame1 += 550_000 * (40 + 120) / (40 + 40 + 120); - blame2 += 550_000 * (40 + 0 ) / (40 + 40 + 120); + blame2 += 550_000 * (40 + 0) / (40 + 40 + 120); checkMeasuredCharge("F", uid1, blame1, uid2, blame2, globalDoze, bi); // Case G: uid1 on, uid2 off, battery on, screen DOZE clocks.realtime += 5; - screen = Display.STATE_DOZE; - bi.updateDisplayMeasuredEnergyStatsLocked(570_000, screen, clocks.realtime); + screen[0] = Display.STATE_DOZE; + bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{570_000}, screen, clocks.realtime); blame1 += 570_000; // All of this pre-doze time is blamed on uid1. checkMeasuredCharge("G", uid1, blame1, uid2, blame2, globalDoze, bi); // Case H: uid1 on, uid2 off, battery on, screen ON clocks.realtime += 6; - screen = Display.STATE_ON; - bi.updateDisplayMeasuredEnergyStatsLocked(580_000, screen, clocks.realtime); + screen[0] = Display.STATE_ON; + bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{580_000}, screen, clocks.realtime); blame1 += 0; // The screen had been doze during the energy period globalDoze += 580_000; checkMeasuredCharge("H", uid1, blame1, uid2, blame2, globalDoze, bi); @@ -822,4 +1225,19 @@ public class BatteryStatsNoteTest extends TestCase { assertEquals("Wrong uid2 blame in bucket 1 for Case " + caseName, blame2B, actualUid2[1]); } + + private void checkScreenBrightnesses(long[] overallExpected, long[][] perDisplayExpected, + BatteryStatsImpl bi, long currentTimeMs) { + final int numDisplay = bi.getDisplayCount(); + for (int bin = 0; bin < NUM_SCREEN_BRIGHTNESS_BINS; bin++) { + for (int display = 0; display < numDisplay; display++) { + assertEquals("Failure for display " + display + " screen brightness bin " + bin, + perDisplayExpected[display][bin] * 1000, + bi.getDisplayScreenBrightnessTime(display, bin, currentTimeMs * 1000)); + } + assertEquals("Failure for overall screen brightness bin " + bin, + overallExpected[bin] * 1000, + bi.getScreenBrightnessTime(bin, currentTimeMs * 1000, STATS_SINCE_CHARGED)); + } + } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java index 083090c54619..ac87806b1639 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java @@ -110,6 +110,20 @@ public class BatteryUsageStatsRule implements TestRule { return this; } + public BatteryUsageStatsRule setAveragePowerForOrdinal(String group, int ordinal, + double value) { + when(mPowerProfile.getAveragePowerForOrdinal(group, ordinal)).thenReturn(value); + when(mPowerProfile.getAveragePowerForOrdinal(eq(group), eq(ordinal), + anyDouble())).thenReturn(value); + return this; + } + + public BatteryUsageStatsRule setNumDisplays(int value) { + when(mPowerProfile.getNumDisplays()).thenReturn(value); + mBatteryStats.setDisplayCountLocked(value); + return this; + } + /** Call only after setting the power profile information. */ public BatteryUsageStatsRule initMeasuredEnergyStatsLocked() { return initMeasuredEnergyStatsLocked(new String[0]); diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java index cee1a0352a7e..cfecf15b55ef 100644 --- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java +++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java @@ -39,13 +39,14 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { public BatteryStatsImpl.Clocks clocks; public boolean mForceOnBattery; private NetworkStats mNetworkStats; + private DummyExternalStatsSync mExternalStatsSync = new DummyExternalStatsSync(); MockBatteryStatsImpl(Clocks clocks) { super(clocks); this.clocks = mClocks; initTimersAndCounters(); - setExternalStatsSyncLocked(new DummyExternalStatsSync()); + setExternalStatsSyncLocked(mExternalStatsSync); informThatAllExternalStatsAreFlushed(); // A no-op handler. @@ -182,7 +183,15 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { return mPendingUids; } + public int getAndClearExternalStatsSyncFlags() { + final int flags = mExternalStatsSync.flags; + mExternalStatsSync.flags = 0; + return flags; + } + private class DummyExternalStatsSync implements ExternalStatsSync { + public int flags = 0; + @Override public Future<?> scheduleSync(String reason, int flags) { return null; @@ -211,8 +220,9 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { } @Override - public Future<?> scheduleSyncDueToScreenStateChange( - int flag, boolean onBattery, boolean onBatteryScreenOff, int screenState) { + public Future<?> scheduleSyncDueToScreenStateChange(int flag, boolean onBattery, + boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) { + flags |= flag; return null; } diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java index 5862368f44d2..88ee405483db 100644 --- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java +++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java @@ -17,6 +17,10 @@ package com.android.internal.os; +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT; +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL; +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON; + import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -53,7 +57,12 @@ public class PowerProfileTest extends TestCase { assertEquals(4, mProfile.getNumSpeedStepsInCpuCluster(1)); assertEquals(60.0, mProfile.getAveragePowerForCpuCore(1, 3)); assertEquals(3000.0, mProfile.getBatteryCapacity()); - assertEquals(0.5, mProfile.getAveragePower(PowerProfile.POWER_AMBIENT_DISPLAY)); + assertEquals(0.5, + mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0)); + assertEquals(100.0, + mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 0)); + assertEquals(800.0, + mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0)); assertEquals(100.0, mProfile.getAveragePower(PowerProfile.POWER_AUDIO)); assertEquals(150.0, mProfile.getAveragePower(PowerProfile.POWER_VIDEO)); } diff --git a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java index c695fc9eb87d..eee5d57c7bc6 100644 --- a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java @@ -16,6 +16,9 @@ package com.android.internal.os; +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL; +import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON; + import static com.google.common.truth.Truth.assertThat; import android.app.ActivityManager; @@ -39,24 +42,27 @@ public class ScreenPowerCalculatorTest { private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 43; private static final long MINUTE_IN_MS = 60 * 1000; private static final long MINUTE_IN_US = 60 * 1000 * 1000; + private static final long HOUR_IN_MS = 60 * MINUTE_IN_MS; @Rule public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() - .setAveragePower(PowerProfile.POWER_SCREEN_ON, 36.0) - .setAveragePower(PowerProfile.POWER_SCREEN_FULL, 48.0); + .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 0, 36.0) + .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0, 48.0) + .setNumDisplays(1); @Test public void testMeasuredEnergyBasedModel() { mStatsRule.initMeasuredEnergyStatsLocked(); BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); - batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0); - batteryStats.updateDisplayMeasuredEnergyStatsLocked(0, Display.STATE_ON, 0); + batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0); + batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{0}, + new int[]{Display.STATE_ON}, 0); setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true, 0, 0); - batteryStats.updateDisplayMeasuredEnergyStatsLocked(200_000_000, Display.STATE_ON, - 15 * MINUTE_IN_MS); + batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{200_000_000}, + new int[]{Display.STATE_ON}, 15 * MINUTE_IN_MS); setProcState(APP_UID1, ActivityManager.PROCESS_STATE_CACHED_EMPTY, false, 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); @@ -64,16 +70,16 @@ public class ScreenPowerCalculatorTest { setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true, 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); - batteryStats.updateDisplayMeasuredEnergyStatsLocked(300_000_000, Display.STATE_ON, - 60 * MINUTE_IN_MS); + batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300_000_000}, + new int[]{Display.STATE_ON}, 60 * MINUTE_IN_MS); - batteryStats.noteScreenStateLocked(Display.STATE_OFF, + batteryStats.noteScreenStateLocked(0, Display.STATE_OFF, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); - batteryStats.updateDisplayMeasuredEnergyStatsLocked(100_000_000, Display.STATE_DOZE, - 120 * MINUTE_IN_MS); + batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100_000_000}, + new int[]{Display.STATE_DOZE}, 120 * MINUTE_IN_MS); mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US); @@ -126,24 +132,122 @@ public class ScreenPowerCalculatorTest { .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); } + + @Test + public void testMeasuredEnergyBasedModel_multiDisplay() { + mStatsRule.initMeasuredEnergyStatsLocked() + .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 1, 60.0) + .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 1, 100.0) + .setNumDisplays(2); + + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + final int[] screenStates = new int[]{Display.STATE_ON, Display.STATE_OFF}; + + batteryStats.noteScreenStateLocked(0, screenStates[0], 0, 0, 0); + batteryStats.noteScreenStateLocked(1, screenStates[1], 0, 0, 0); + batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0); + setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true, 0, 0); + batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300, 400}, screenStates, 0); + + batteryStats.noteScreenBrightnessLocked(0, 100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS); + batteryStats.noteScreenBrightnessLocked(0, 200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS); + + setProcState(APP_UID1, ActivityManager.PROCESS_STATE_CACHED_EMPTY, false, + 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); + setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true, + 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); + + screenStates[0] = Display.STATE_OFF; + screenStates[1] = Display.STATE_ON; + batteryStats.noteScreenStateLocked(0, screenStates[0], + 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); + batteryStats.noteScreenStateLocked(1, screenStates[1], 80 * MINUTE_IN_MS, + 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); + batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{600_000_000, 500}, + screenStates, 80 * MINUTE_IN_MS); + + batteryStats.noteScreenBrightnessLocked(1, 25, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); + batteryStats.noteScreenBrightnessLocked(1, 250, 86 * MINUTE_IN_MS, 86 * MINUTE_IN_MS); + batteryStats.noteScreenBrightnessLocked(1, 75, 98 * MINUTE_IN_MS, 98 * MINUTE_IN_MS); + + screenStates[1] = Display.STATE_OFF; + batteryStats.noteScreenStateLocked(1, screenStates[1], 110 * MINUTE_IN_MS, + 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS); + batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{700, 800_000_000}, + screenStates, 110 * MINUTE_IN_MS); + + setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false, + 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS); + + mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US); + + ScreenPowerCalculator calculator = + new ScreenPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(calculator); + + BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer(); + assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(110 * MINUTE_IN_MS); + // (600000000 + 800000000) uAs * (1 mA / 1000 uA) * (1 h / 3600 s) = 166.66666 mAh + assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isWithin(PRECISION).of(388.88888); + assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + + UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1); + assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(20 * MINUTE_IN_MS); + + // Uid1 ran for 20 out of 80 min during the first Display update. + // It also ran for 5 out of 45 min during the second Display update: + // Uid1 charge = 20 / 80 * 600000000 mAs = 41.66666 mAh + assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isWithin(PRECISION).of(41.66666); + assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + + UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2); + assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(90 * MINUTE_IN_MS); + + // Uid2 ran for 60 out of 80 min during the first Display update. + // It also ran for all of the second Display update: + // Uid1 charge = 60 / 80 * 600000000 + 800000000 mAs = 347.22222 mAh + assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isWithin(PRECISION).of(347.22222); + assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + + BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer(); + assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(110 * MINUTE_IN_MS); + assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isWithin(PRECISION).of(388.88888); + assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + + } + @Test public void testPowerProfileBasedModel() { BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); - batteryStats.noteScreenStateLocked(Display.STATE_ON, 0, 0, 0); - batteryStats.noteScreenBrightnessLocked(255, 0, 0); + batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0); + batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0); setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true, 0, 0); - batteryStats.noteScreenBrightnessLocked(100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS); - batteryStats.noteScreenBrightnessLocked(200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS); + batteryStats.noteScreenBrightnessLocked(0, 100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS); + batteryStats.noteScreenBrightnessLocked(0, 200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS); setProcState(APP_UID1, ActivityManager.PROCESS_STATE_CACHED_EMPTY, false, 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true, 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); - batteryStats.noteScreenStateLocked(Display.STATE_OFF, + batteryStats.noteScreenStateLocked(0, Display.STATE_OFF, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); @@ -194,6 +298,95 @@ public class ScreenPowerCalculatorTest { .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE); } + + @Test + public void testPowerProfileBasedModel_multiDisplay() { + mStatsRule.setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 1, 60.0) + .setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 1, 100.0) + .setNumDisplays(2); + + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0); + batteryStats.noteScreenStateLocked(1, Display.STATE_OFF, 0, 0, 0); + batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0); + setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true, + 0, 0); + + batteryStats.noteScreenBrightnessLocked(0, 100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS); + batteryStats.noteScreenBrightnessLocked(0, 200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS); + + setProcState(APP_UID1, ActivityManager.PROCESS_STATE_CACHED_EMPTY, false, + 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); + setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true, + 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); + + batteryStats.noteScreenStateLocked(0, Display.STATE_OFF, + 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); + batteryStats.noteScreenStateLocked(1, Display.STATE_ON, 80 * MINUTE_IN_MS, + 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); + batteryStats.noteScreenBrightnessLocked(1, 20, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); + + batteryStats.noteScreenBrightnessLocked(1, 250, 86 * MINUTE_IN_MS, 86 * MINUTE_IN_MS); + batteryStats.noteScreenBrightnessLocked(1, 75, 98 * MINUTE_IN_MS, 98 * MINUTE_IN_MS); + batteryStats.noteScreenStateLocked(1, Display.STATE_OFF, 110 * MINUTE_IN_MS, + 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS); + + setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false, + 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS); + + mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US); + ScreenPowerCalculator calculator = + new ScreenPowerCalculator(mStatsRule.getPowerProfile()); + + mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator); + + BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer(); + assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(110 * MINUTE_IN_MS); + // First display consumed 92 mAh. + // Second display ran for 0.5 hours at a base drain rate of 60 mA. + // 6 minutes (0.1 hours) spent in the first brightness level which drains an extra 10 mA. + // 12 minutes (0.2 hours) spent in the fifth brightness level which drains an extra 90 mA. + // 12 minutes (0.2 hours) spent in the second brightness level which drains an extra 30 mA. + // 92 + 60 * 0.5 + 10 * 0.1 + 90 * 0.2 + 30 * 0.2 = 147 + assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isWithin(PRECISION).of(147); + assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE); + + UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1); + assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(20 * MINUTE_IN_MS); + + // Uid1 took 20 out of the total of 110 min of foreground activity + // Uid1 charge = 20 / 110 * 147.0 = 23.0 mAh + assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isWithin(PRECISION).of(26.72727); + assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE); + + UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2); + assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(90 * MINUTE_IN_MS); + + // Uid2 took 90 out of the total of 110 min of foreground activity + // Uid2 charge = 90 / 110 * 92.0 = 69.0 mAh + assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isWithin(PRECISION).of(120.272727); + assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE); + + BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer(); + assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(110 * MINUTE_IN_MS); + assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isWithin(PRECISION).of(147); + assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN)) + .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE); + + } + private void setProcState(int uid, int procState, boolean resumed, long realtimeMs, long uptimeMs) { BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 909ca3988c5d..6e92755be98e 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -595,6 +595,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DisplayContent.java" }, + "-1478175541": { + "message": "No longer animating wallpaper targets!", + "level": "VERBOSE", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, "-1474602871": { "message": "Launch on display check: disallow launch on virtual display for not-embedded activity.", "level": "DEBUG", @@ -1135,6 +1141,12 @@ "group": "WM_DEBUG_BOOT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-863438038": { + "message": "Aborting Transition: %d", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/Transition.java" + }, "-861859917": { "message": "Attempted to add window to a display that does not exist: %d. Aborting.", "level": "WARN", @@ -1621,6 +1633,12 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, + "-360208282": { + "message": "Animating wallpapers: old: %s hidden=%b new: %s hidden=%b", + "level": "VERBOSE", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, "-354571697": { "message": "Existence Changed in transition %d: %s", "level": "VERBOSE", @@ -1675,6 +1693,12 @@ "group": "WM_DEBUG_LAYER_MIRRORING", "at": "com\/android\/server\/wm\/DisplayContent.java" }, + "-304728471": { + "message": "New wallpaper: target=%s prev=%s", + "level": "DEBUG", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, "-302468788": { "message": "Expected target rootTask=%s to be top most but found rootTask=%s", "level": "WARN", @@ -1693,6 +1717,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-275077723": { + "message": "New animation: %s old animation: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, "-262984451": { "message": "Relaunch failed %s", "level": "INFO", @@ -1747,6 +1777,12 @@ "group": "WM_DEBUG_LAYER_MIRRORING", "at": "com\/android\/server\/wm\/DisplayContent.java" }, + "-182877285": { + "message": "Wallpaper layer changed: assigning layers + relayout", + "level": "VERBOSE", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "-177040661": { "message": "Start rotation animation. customAnim=%s, mCurRotation=%s, mOriginalRotation=%s", "level": "DEBUG", @@ -2005,6 +2041,12 @@ "group": "WM_DEBUG_STARTING_WINDOW", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "114070759": { + "message": "New wallpaper target: %s prevTarget: %s caller=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, "115358443": { "message": "Focus changing: %s -> %s", "level": "INFO", @@ -2347,6 +2389,12 @@ "group": "WM_DEBUG_RESIZE", "at": "com\/android\/server\/wm\/WindowState.java" }, + "422634333": { + "message": "First draw done in potential wallpaper target %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "424524729": { "message": "Attempted to add wallpaper window with unknown token %s. Aborting.", "level": "WARN", @@ -2371,12 +2419,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "457951957": { - "message": "\tNot visible=%s", - "level": "DEBUG", - "group": "WM_DEBUG_REMOTE_ANIMATIONS", - "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java" - }, "463993897": { "message": "Aborted waiting for drawn: %s", "level": "WARN", @@ -2425,6 +2467,12 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/WindowContainerThumbnail.java" }, + "535103992": { + "message": "Wallpaper may change! Adjusting", + "level": "VERBOSE", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/RootWindowContainer.java" + }, "539077569": { "message": "Clear freezing of %s force=%b", "level": "VERBOSE", @@ -2653,6 +2701,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "733466617": { + "message": "Wallpaper token %s visible=%b", + "level": "DEBUG", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperWindowToken.java" + }, "736692676": { "message": "Config is relaunching %s", "level": "VERBOSE", @@ -2989,6 +3043,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/DisplayContent.java" }, + "1178653181": { + "message": "Old wallpaper still the target.", + "level": "VERBOSE", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, "1186730970": { "message": " no common mode yet, so set it", "level": "VERBOSE", @@ -3667,6 +3727,12 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/WindowAnimator.java" }, + "1984843251": { + "message": "Hiding wallpaper %s from %s target=%s prev=%s callers=%s", + "level": "DEBUG", + "group": "WM_DEBUG_WALLPAPER", + "at": "com\/android\/server\/wm\/WallpaperController.java" + }, "1995093920": { "message": "Checking to restart %s: changed=0x%s, handles=0x%s, mLastReportedConfiguration=%s", "level": "VERBOSE", @@ -3697,6 +3763,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "2024493888": { + "message": "\tWallpaper of display=%s is not visible", + "level": "DEBUG", + "group": "WM_DEBUG_REMOTE_ANIMATIONS", + "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java" + }, "2028163120": { "message": "applyAnimation: anim=%s nextAppTransition=ANIM_SCALE_UP transit=%s isEntrance=%s Callers=%s", "level": "VERBOSE", @@ -3721,12 +3793,6 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, - "2057434754": { - "message": "\tvisible=%s", - "level": "DEBUG", - "group": "WM_DEBUG_REMOTE_ANIMATIONS", - "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java" - }, "2060978050": { "message": "moveWindowTokenToDisplay: Attempted to move token: %s to non-exiting displayId=%d", "level": "WARN", @@ -3867,6 +3933,9 @@ "WM_DEBUG_TASKS": { "tag": "WindowManager" }, + "WM_DEBUG_WALLPAPER": { + "tag": "WindowManager" + }, "WM_DEBUG_WINDOW_INSETS": { "tag": "WindowManager" }, diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java index 06f6228623d8..194b6330d92c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java @@ -16,7 +16,8 @@ package androidx.window.extensions.embedding; -import android.graphics.Point; +import static android.graphics.Matrix.MSCALE_X; + import android.graphics.Rect; import android.view.Choreographer; import android.view.RemoteAnimationTarget; @@ -25,58 +26,151 @@ import android.view.animation.Animation; import android.view.animation.Transformation; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; /** * Wrapper to handle the TaskFragment animation update in one {@link SurfaceControl.Transaction}. + * + * The base adapter can be used for {@link RemoteAnimationTarget} that is simple open/close. */ class TaskFragmentAnimationAdapter { - private final Animation mAnimation; - private final RemoteAnimationTarget mTarget; - private final SurfaceControl mLeash; - private final boolean mSizeChanged; - private final Point mPosition; - private final Transformation mTransformation = new Transformation(); - private final float[] mMatrix = new float[9]; - private final float[] mVecs = new float[4]; - private final Rect mRect = new Rect(); + final Animation mAnimation; + final RemoteAnimationTarget mTarget; + final SurfaceControl mLeash; + + final Transformation mTransformation = new Transformation(); + final float[] mMatrix = new float[9]; private boolean mIsFirstFrame = true; TaskFragmentAnimationAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) { - this(animation, target, target.leash, false /* sizeChanged */, null /* position */); + this(animation, target, target.leash); } /** - * @param sizeChanged whether the surface size needs to be changed. + * @param leash the surface to animate. */ TaskFragmentAnimationAdapter(@NonNull Animation animation, - @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash, - boolean sizeChanged, @Nullable Point position) { + @NonNull RemoteAnimationTarget target, @NonNull SurfaceControl leash) { mAnimation = animation; mTarget = target; mLeash = leash; - mSizeChanged = sizeChanged; - mPosition = position != null - ? position - : new Point(target.localBounds.left, target.localBounds.top); } /** Called on frame update. */ - void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) { + final void onAnimationUpdate(@NonNull SurfaceControl.Transaction t, long currentPlayTime) { if (mIsFirstFrame) { t.show(mLeash); mIsFirstFrame = false; } - currentPlayTime = Math.min(currentPlayTime, mAnimation.getDuration()); - mAnimation.getTransformation(currentPlayTime, mTransformation); - mTransformation.getMatrix().postTranslate(mPosition.x, mPosition.y); + // Extract the transformation to the current time. + mAnimation.getTransformation(Math.min(currentPlayTime, mAnimation.getDuration()), + mTransformation); + t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); + onAnimationUpdateInner(t); + } + + /** To be overridden by subclasses to adjust the animation surface change. */ + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + mTransformation.getMatrix().postTranslate( + mTarget.localBounds.left, mTarget.localBounds.top); t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); t.setAlpha(mLeash, mTransformation.getAlpha()); - t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); + } + + /** Called after animation finished. */ + final void onAnimationEnd(@NonNull SurfaceControl.Transaction t) { + onAnimationUpdate(t, mAnimation.getDuration()); + } + + final long getDurationHint() { + return mAnimation.computeDurationHint(); + } + + /** + * Should be used when the {@link RemoteAnimationTarget} is in split with others, and want to + * animate together as one. This adapter will offset the animation leash to make the animate of + * two windows look like a single window. + */ + static class SplitAdapter extends TaskFragmentAnimationAdapter { + private final boolean mIsLeftHalf; + private final int mWholeAnimationWidth; + + /** + * @param isLeftHalf whether this is the left half of the animation. + * @param wholeAnimationWidth the whole animation windows width. + */ + SplitAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target, + boolean isLeftHalf, int wholeAnimationWidth) { + super(animation, target); + mIsLeftHalf = isLeftHalf; + mWholeAnimationWidth = wholeAnimationWidth; + if (wholeAnimationWidth == 0) { + throw new IllegalArgumentException("SplitAdapter must provide wholeAnimationWidth"); + } + } + + @Override + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + float posX = mTarget.localBounds.left; + final float posY = mTarget.localBounds.top; + // This window is half of the whole animation window. Offset left/right to make it + // look as one with the other half. + mTransformation.getMatrix().getValues(mMatrix); + final int targetWidth = mTarget.localBounds.width(); + final float scaleX = mMatrix[MSCALE_X]; + final float totalOffset = mWholeAnimationWidth * (1 - scaleX) / 2; + final float curOffset = targetWidth * (1 - scaleX) / 2; + final float offsetDiff = totalOffset - curOffset; + if (mIsLeftHalf) { + posX += offsetDiff; + } else { + posX -= offsetDiff; + } + mTransformation.getMatrix().postTranslate(posX, posY); + t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); + t.setAlpha(mLeash, mTransformation.getAlpha()); + } + } + + /** + * Should be used for the animation of the snapshot of a {@link RemoteAnimationTarget} that has + * size change. + */ + static class SnapshotAdapter extends TaskFragmentAnimationAdapter { + + SnapshotAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) { + // Start leash is the snapshot of the starting surface. + super(animation, target, target.startLeash); + } + + @Override + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + // Snapshot should always be placed at the top left of the animation leash. + mTransformation.getMatrix().postTranslate(0, 0); + t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); + t.setAlpha(mLeash, mTransformation.getAlpha()); + } + } + + /** + * Should be used for the animation of the {@link RemoteAnimationTarget} that has size change. + */ + static class BoundsChangeAdapter extends TaskFragmentAnimationAdapter { + private final float[] mVecs = new float[4]; + private final Rect mRect = new Rect(); + + BoundsChangeAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) { + super(animation, target); + } + + @Override + void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) { + mTransformation.getMatrix().postTranslate( + mTarget.localBounds.left, mTarget.localBounds.top); + t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix); + t.setAlpha(mLeash, mTransformation.getAlpha()); - if (mSizeChanged) { // The following applies an inverse scale to the clip-rect so that it crops "after" the // scale instead of before. mVecs[1] = mVecs[2] = 0; @@ -92,13 +186,4 @@ class TaskFragmentAnimationAdapter { t.setWindowCrop(mLeash, mRect); } } - - /** Called after animation finished. */ - void onAnimationEnd(@NonNull SurfaceControl.Transaction t) { - onAnimationUpdate(t, mAnimation.getDuration()); - } - - long getDurationHint() { - return mAnimation.computeDurationHint(); - } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java index 65797667687e..535dac1a5101 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java @@ -29,7 +29,6 @@ import android.window.TaskFragmentOrganizer; class TaskFragmentAnimationController { private static final String TAG = "TaskFragAnimationCtrl"; - // TODO(b/196173550) turn off when finalize static final boolean DEBUG = false; private final TaskFragmentOrganizer mOrganizer; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java index 3980d077a7dd..412559e34070 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java @@ -23,7 +23,7 @@ import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN; import android.animation.Animator; import android.animation.ValueAnimator; -import android.graphics.Point; +import android.graphics.Rect; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; @@ -40,6 +40,7 @@ import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.List; +import java.util.function.BiFunction; /** To run the TaskFragment animations. */ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { @@ -167,42 +168,86 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters( @NonNull RemoteAnimationTarget[] targets) { - final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>(); - for (RemoteAnimationTarget target : targets) { - final Animation animation = - mAnimationSpec.loadOpenAnimation(target.mode != MODE_CLOSING /* isEnter */); - adapters.add(new TaskFragmentAnimationAdapter(animation, target)); - } - return adapters; + return createOpenCloseAnimationAdapters(targets, + mAnimationSpec::loadOpenAnimation); } private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters( @NonNull RemoteAnimationTarget[] targets) { - final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>(); + return createOpenCloseAnimationAdapters(targets, + mAnimationSpec::loadCloseAnimation); + } + + private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters( + @NonNull RemoteAnimationTarget[] targets, + @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) { + // We need to know if the target window is only a partial of the whole animation screen. + // If so, we will need to adjust it to make the whole animation screen looks like one. + final List<RemoteAnimationTarget> openingTargets = new ArrayList<>(); + final List<RemoteAnimationTarget> closingTargets = new ArrayList<>(); + final Rect openingWholeScreenBounds = new Rect(); + final Rect closingWholeScreenBounds = new Rect(); for (RemoteAnimationTarget target : targets) { - final Animation animation = - mAnimationSpec.loadCloseAnimation(target.mode != MODE_CLOSING /* isEnter */); - adapters.add(new TaskFragmentAnimationAdapter(animation, target)); + if (target.mode != MODE_CLOSING) { + openingTargets.add(target); + openingWholeScreenBounds.union(target.localBounds); + } else { + closingTargets.add(target); + closingWholeScreenBounds.union(target.localBounds); + } + } + + final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>(); + for (RemoteAnimationTarget target : openingTargets) { + adapters.add(createOpenCloseAnimationAdapter(target, animationProvider, + openingWholeScreenBounds)); + } + for (RemoteAnimationTarget target : closingTargets) { + adapters.add(createOpenCloseAnimationAdapter(target, animationProvider, + closingWholeScreenBounds)); } return adapters; } + private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter( + @NonNull RemoteAnimationTarget target, + @NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider, + @NonNull Rect wholeAnimationBounds) { + final Animation animation = animationProvider.apply(target, wholeAnimationBounds); + final Rect targetBounds = target.localBounds; + if (targetBounds.left == wholeAnimationBounds.left + && targetBounds.right != wholeAnimationBounds.right) { + // This is the left split of the whole animation window. + return new TaskFragmentAnimationAdapter.SplitAdapter(animation, target, + true /* isLeftHalf */, wholeAnimationBounds.width()); + } else if (targetBounds.left != wholeAnimationBounds.left + && targetBounds.right == wholeAnimationBounds.right) { + // This is the right split of the whole animation window. + return new TaskFragmentAnimationAdapter.SplitAdapter(animation, target, + false /* isLeftHalf */, wholeAnimationBounds.width()); + } + // Open/close window that fills the whole animation. + return new TaskFragmentAnimationAdapter(animation, target); + } + private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters( @NonNull RemoteAnimationTarget[] targets) { final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>(); for (RemoteAnimationTarget target : targets) { if (target.startBounds != null) { + // This is the target with bounds change. final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(target); - // The snapshot surface will always be at (0, 0) of its parent. - adapters.add(new TaskFragmentAnimationAdapter(animations[0], target, - target.startLeash, false /* sizeChanged */, new Point(0, 0))); - // The end surface will have size change for scaling. - adapters.add(new TaskFragmentAnimationAdapter(animations[1], target, - target.leash, true /* sizeChanged */, null /* position */)); + // Adapter for the starting snapshot leash. + adapters.add(new TaskFragmentAnimationAdapter.SnapshotAdapter( + animations[0], target)); + // Adapter for the ending bounds changed leash. + adapters.add(new TaskFragmentAnimationAdapter.BoundsChangeAdapter( + animations[1], target)); continue; } + // These are the other targets that don't have bounds change in the same transition. final Animation animation; if (target.hasAnimatingParent) { // No-op if it will be covered by the changing parent window. diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java index 11a79b2f4a27..c0908a548501 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java @@ -176,18 +176,28 @@ class TaskFragmentAnimationSpec { return new Animation[]{startSet, endSet}; } - Animation loadOpenAnimation(boolean isEnter) { - // TODO(b/196173550) We need to customize the animation to handle two open window as one. - return mTransitionAnimation.loadDefaultAnimationAttr(isEnter + Animation loadOpenAnimation(@NonNull RemoteAnimationTarget target, + @NonNull Rect wholeAnimationBounds) { + final boolean isEnter = target.mode != MODE_CLOSING; + final Animation animation = mTransitionAnimation.loadDefaultAnimationAttr(isEnter ? R.styleable.WindowAnimation_activityOpenEnterAnimation : R.styleable.WindowAnimation_activityOpenExitAnimation); + animation.initialize(target.localBounds.width(), target.localBounds.height(), + wholeAnimationBounds.width(), wholeAnimationBounds.height()); + animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); + return animation; } - Animation loadCloseAnimation(boolean isEnter) { - // TODO(b/196173550) We need to customize the animation to handle two open window as one. - return mTransitionAnimation.loadDefaultAnimationAttr(isEnter + Animation loadCloseAnimation(@NonNull RemoteAnimationTarget target, + @NonNull Rect wholeAnimationBounds) { + final boolean isEnter = target.mode != MODE_CLOSING; + final Animation animation = mTransitionAnimation.loadDefaultAnimationAttr(isEnter ? R.styleable.WindowAnimation_activityCloseEnterAnimation : R.styleable.WindowAnimation_activityCloseExitAnimation); + animation.initialize(target.localBounds.width(), target.localBounds.height(), + wholeAnimationBounds.width(), wholeAnimationBounds.height()); + animation.scaleCurrentDuration(mTransitionAnimationScaleSetting); + return animation; } private class SettingsObserver extends ContentObserver { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java index e6f8388b031f..62959b7b95e9 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java @@ -28,7 +28,7 @@ public class SidecarProvider { * an OEM by overriding this method. */ public static SidecarInterface getSidecarImpl(Context context) { - return new SampleSidecarImpl(context); + return new SampleSidecarImpl(context.getApplicationContext()); } /** @@ -36,6 +36,6 @@ public class SidecarProvider { * @return API version string in MAJOR.MINOR.PATCH-description format. */ public static String getApiVersion() { - return "0.1.0-settings_sample"; + return "1.0.0-reference"; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java new file mode 100644 index 000000000000..14ba9df93f24 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java @@ -0,0 +1,114 @@ +/* + * 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.wm.shell; + +import android.util.SparseArray; +import android.view.SurfaceControl; +import android.window.DisplayAreaAppearedInfo; +import android.window.DisplayAreaInfo; +import android.window.DisplayAreaOrganizer; + +import androidx.annotation.NonNull; + +import java.io.PrintWriter; +import java.util.List; +import java.util.concurrent.Executor; + +/** Display area organizer for the root display areas */ +public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer { + + private static final String TAG = RootDisplayAreaOrganizer.class.getSimpleName(); + + /** {@link DisplayAreaInfo} list, which is mapped by display IDs. */ + private final SparseArray<DisplayAreaInfo> mDisplayAreasInfo = new SparseArray<>(); + /** Display area leashes, which is mapped by display IDs. */ + private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>(); + + public RootDisplayAreaOrganizer(Executor executor) { + super(executor); + List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT); + for (int i = infos.size() - 1; i >= 0; --i) { + onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash()); + } + } + + public void attachToDisplayArea(int displayId, SurfaceControl.Builder b) { + final SurfaceControl sc = mLeashes.get(displayId); + if (sc != null) { + b.setParent(sc); + } + } + + @Override + public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo, + @NonNull SurfaceControl leash) { + if (displayAreaInfo.featureId != FEATURE_ROOT) { + throw new IllegalArgumentException( + "Unknown feature: " + displayAreaInfo.featureId + + "displayAreaInfo:" + displayAreaInfo); + } + + final int displayId = displayAreaInfo.displayId; + if (mDisplayAreasInfo.get(displayId) != null) { + throw new IllegalArgumentException( + "Duplicate DA for displayId: " + displayId + + " displayAreaInfo:" + displayAreaInfo + + " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId)); + } + + mDisplayAreasInfo.put(displayId, displayAreaInfo); + mLeashes.put(displayId, leash); + } + + @Override + public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) { + final int displayId = displayAreaInfo.displayId; + if (mDisplayAreasInfo.get(displayId) == null) { + throw new IllegalArgumentException( + "onDisplayAreaVanished() Unknown DA displayId: " + displayId + + " displayAreaInfo:" + displayAreaInfo + + " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId)); + } + + mDisplayAreasInfo.remove(displayId); + } + + @Override + public void onDisplayAreaInfoChanged(@NonNull DisplayAreaInfo displayAreaInfo) { + final int displayId = displayAreaInfo.displayId; + if (mDisplayAreasInfo.get(displayId) == null) { + throw new IllegalArgumentException( + "onDisplayAreaInfoChanged() Unknown DA displayId: " + displayId + + " displayAreaInfo:" + displayAreaInfo + + " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId)); + } + + mDisplayAreasInfo.put(displayId, displayAreaInfo); + } + + public void dump(@NonNull PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + final String childPrefix = innerPrefix + " "; + pw.println(prefix + this); + } + + @Override + public String toString() { + return TAG + "#" + mDisplayAreasInfo.size(); + } + +} 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 10d7725b6184..6a252e0d7dcb 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 @@ -232,7 +232,7 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou if (mSplitLayout != null && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) { - onLayoutChanged(mSplitLayout); + onLayoutSizeChanged(mSplitLayout); } } else if (taskInfo.taskId == getTaskId1()) { mTaskInfo1 = taskInfo; @@ -313,13 +313,19 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou } @Override - public void onLayoutChanging(SplitLayout layout) { + public void onLayoutPositionChanging(SplitLayout layout) { mSyncQueue.runInSync(t -> layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2)); } @Override - public void onLayoutChanged(SplitLayout layout) { + public void onLayoutSizeChanging(SplitLayout layout) { + mSyncQueue.runInSync(t -> + layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2)); + } + + @Override + public void onLayoutSizeChanged(SplitLayout layout) { final WindowContainerTransaction wct = new WindowContainerTransaction(); layout.applyTaskChanges(wct, mTaskInfo1, mTaskInfo2); mSyncQueue.queue(wct); @@ -328,9 +334,9 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou } @Override - public void onLayoutShifted(int offsetX, int offsetY, SplitLayout layout) { + public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) { final WindowContainerTransaction wct = new WindowContainerTransaction(); - layout.applyLayoutShifted(wct, offsetX, offsetY, mTaskInfo1, mTaskInfo2); + layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, mTaskInfo1, mTaskInfo2); mController.getTaskOrganizer().applyTransaction(wct); } } 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 05ebbba4e955..8d43f1375a8c 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 @@ -121,7 +121,7 @@ public class Bubble implements BubbleViewProvider { @Nullable private Icon mIcon; private boolean mIsBubble; - private boolean mIsVisuallyInterruptive; + private boolean mIsTextChanged; private boolean mIsClearable; private boolean mShouldSuppressNotificationDot; private boolean mShouldSuppressNotificationList; @@ -342,12 +342,12 @@ public class Bubble implements BubbleViewProvider { } /** - * Sets whether this bubble is considered visually interruptive. This method is purely for + * Sets whether this bubble is considered text changed. This method is purely for * testing. */ @VisibleForTesting - void setVisuallyInterruptiveForTest(boolean visuallyInterruptive) { - mIsVisuallyInterruptive = visuallyInterruptive; + void setTextChangedForTest(boolean textChanged) { + mIsTextChanged = textChanged; } /** @@ -454,7 +454,7 @@ public class Bubble implements BubbleViewProvider { mFlyoutMessage = extractFlyoutMessage(entry); if (entry.getRanking() != null) { mShortcutInfo = entry.getRanking().getConversationShortcutInfo(); - mIsVisuallyInterruptive = entry.getRanking().visuallyInterruptive(); + mIsTextChanged = entry.getRanking().isTextChanged(); if (entry.getRanking().getChannel() != null) { mIsImportantConversation = entry.getRanking().getChannel().isImportantConversation(); @@ -495,8 +495,8 @@ public class Bubble implements BubbleViewProvider { return mIcon; } - boolean isVisuallyInterruptive() { - return mIsVisuallyInterruptive; + boolean isTextChanged() { + return mIsTextChanged; } /** 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 c126f32387f0..b6d65bebff28 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 @@ -939,7 +939,7 @@ public class BubbleController { public void updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade) { // If this is an interruptive notif, mark that it's interrupted mSysuiProxy.setNotificationInterruption(notif.getKey()); - if (!notif.getRanking().visuallyInterruptive() + if (!notif.getRanking().isTextChanged() && (notif.getBubbleMetadata() != null && !notif.getBubbleMetadata().getAutoExpandBubble()) && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index b48bda3a6e48..519a856538c7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -323,7 +323,7 @@ public class BubbleData { } mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey()); - suppressFlyout |= !bubble.isVisuallyInterruptive(); + suppressFlyout |= !bubble.isTextChanged(); if (prevBubble == null) { // Create a new bubble @@ -558,6 +558,8 @@ public class BubbleData { } Bubble bubbleToRemove = mBubbles.get(indexToRemove); bubbleToRemove.stopInflation(); + overflowBubble(reason, bubbleToRemove); + if (mBubbles.size() == 1) { if (hasOverflowBubbles() && (mPositioner.showingInTaskbar() || isExpanded())) { // No more active bubbles but we have stuff in the overflow -- select that view @@ -581,8 +583,6 @@ public class BubbleData { mStateChange.orderChanged |= repackAll(); } - overflowBubble(reason, bubbleToRemove); - // Note: If mBubbles.isEmpty(), then mSelectedBubble is now null. if (Objects.equals(mSelectedBubble, bubbleToRemove)) { // Move selection to the new bubble at the same position. 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 d590ab112aae..300319a2f78f 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 @@ -120,8 +120,6 @@ public class BubbleStackView extends FrameLayout private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150; - private static final int MANAGE_MENU_SCRIM_ANIM_DURATION = 150; - private static final float SCRIM_ALPHA = 0.6f; /** @@ -894,6 +892,7 @@ public class BubbleStackView extends FrameLayout updatePointerPosition(false /* forIme */); mExpandedAnimationController.expandFromStack(() -> { afterExpandedViewAnimation(); + showManageMenu(mShowingManage); } /* after */); final float translationY = mPositioner.getExpandedViewY(mExpandedBubble, getBubbleIndex(mExpandedBubble)); @@ -1253,9 +1252,6 @@ public class BubbleStackView extends FrameLayout mRelativeStackPositionBeforeRotation = new RelativeStackPosition( mPositioner.getRestingPosition(), mStackAnimationController.getAllowableStackPositionRegion()); - mManageMenu.setVisibility(View.INVISIBLE); - mShowingManage = false; - addOnLayoutChangeListener(mOrientationChangedListener); hideFlyoutImmediate(); } @@ -2555,16 +2551,19 @@ public class BubbleStackView extends FrameLayout invalidate(); } - private void showManageMenu(boolean show) { + /** Hide or show the manage menu for the currently expanded bubble. */ + @VisibleForTesting + public void showManageMenu(boolean show) { mShowingManage = show; // This should not happen, since the manage menu is only visible when there's an expanded // bubble. If we end up in this state, just hide the menu immediately. if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { mManageMenu.setVisibility(View.INVISIBLE); + mManageMenuScrim.setVisibility(INVISIBLE); + mBubbleController.getSysuiProxy().onManageMenuExpandChanged(false /* show */); return; } - if (show) { mManageMenuScrim.setVisibility(VISIBLE); mManageMenuScrim.setTranslationZ(mManageMenu.getElevation() - 1f); @@ -2576,8 +2575,8 @@ public class BubbleStackView extends FrameLayout } }; + mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show); mManageMenuScrim.animate() - .setDuration(MANAGE_MENU_SCRIM_ANIM_DURATION) .setInterpolator(show ? ALPHA_IN : ALPHA_OUT) .alpha(show ? SCRIM_ALPHA : 0f) .withEndAction(endAction) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 9b7eb2f1cfb3..c82249b8a369 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -284,6 +284,8 @@ public interface Bubbles { void onStackExpandChanged(boolean shouldExpand); + void onManageMenuExpandChanged(boolean menuExpanded); + void onUnbubbleConversation(String key); } } 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 596a2f4467c3..5b3ce2dbaeb9 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 @@ -291,13 +291,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange void updateDivideBounds(int position) { updateBounds(position); mSplitWindowManager.setResizingSplits(true); - mSplitLayoutHandler.onLayoutChanging(this); + mSplitLayoutHandler.onLayoutSizeChanging(this); } void setDividePosition(int position) { mDividePosition = position; updateBounds(mDividePosition); - mSplitLayoutHandler.onLayoutChanged(this); + mSplitLayoutHandler.onLayoutSizeChanged(this); mSplitWindowManager.setResizingSplits(false); } @@ -451,7 +451,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange * Shift configuration bounds to prevent client apps get configuration changed or relaunch. And * restore shifted configuration bounds if it's no longer shifted. */ - public void applyLayoutShifted(WindowContainerTransaction wct, int offsetX, int offsetY, + public void applyLayoutOffsetTarget(WindowContainerTransaction wct, int offsetX, int offsetY, ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2) { if (offsetX == 0 && offsetY == 0) { wct.setBounds(taskInfo1.token, mBounds1); @@ -492,19 +492,43 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange /** Calls when dismissing split. */ void onSnappedToDismiss(boolean snappedToEnd); - /** Calls when the bounds is changing due to animation or dragging divider bar. */ - void onLayoutChanging(SplitLayout layout); + /** + * Calls when resizing the split bounds. + * + * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl, + * SurfaceControl, SurfaceControl) + */ + void onLayoutSizeChanging(SplitLayout layout); - /** Calls when the target bounds changed. */ - void onLayoutChanged(SplitLayout layout); + /** + * Calls when finish resizing the split bounds. + * + * @see #applyTaskChanges(WindowContainerTransaction, ActivityManager.RunningTaskInfo, + * ActivityManager.RunningTaskInfo) + * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl, + * SurfaceControl, SurfaceControl) + */ + void onLayoutSizeChanged(SplitLayout layout); /** - * Notifies when the layout shifted. So the layout handler can shift configuration + * Calls when re-positioning the split bounds. Like moving split bounds while showing IME + * panel. + * + * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl, + * SurfaceControl, SurfaceControl) + */ + void onLayoutPositionChanging(SplitLayout layout); + + /** + * Notifies the target offset for shifting layout. So layout handler can shift configuration * bounds correspondingly to make sure client apps won't get configuration changed or - * relaunch. If the layout is no longer shifted, layout handler should restore shifted + * relaunched. If the layout is no longer shifted, layout handler should restore shifted * configuration bounds. + * + * @see #applyLayoutOffsetTarget(WindowContainerTransaction, int, int, + * ActivityManager.RunningTaskInfo, ActivityManager.RunningTaskInfo) */ - void onLayoutShifted(int offsetX, int offsetY, SplitLayout layout); + void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout); /** Calls when user double tapped on the divider bar. */ default void onDoubleTappedDivider() { @@ -674,9 +698,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange // changed or relaunch. This is required to make sure client apps will calculate // insets properly after layout shifted. if (mTargetYOffset == 0) { - mSplitLayoutHandler.onLayoutShifted(0, 0, SplitLayout.this); + mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this); } else { - mSplitLayoutHandler.onLayoutShifted(0, mTargetYOffset - mLastYOffset, + mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset - mLastYOffset, SplitLayout.this); } } @@ -695,7 +719,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) { if (displayId != mDisplayId) return; onProgress(getProgress(imeTop)); - mSplitLayoutHandler.onLayoutChanging(SplitLayout.this); + mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this); } @Override @@ -703,7 +727,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange SurfaceControl.Transaction t) { if (displayId != mDisplayId || cancel) return; onProgress(1.0f); - mSplitLayoutHandler.onLayoutChanging(SplitLayout.this); + mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this); } @Override @@ -713,7 +737,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange if (!controlling && mImeShown) { reset(); mSplitWindowManager.setInteractive(true); - mSplitLayoutHandler.onLayoutChanging(SplitLayout.this); + mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelper.java new file mode 100644 index 000000000000..defbd5af01d9 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelper.java @@ -0,0 +1,39 @@ +/* + * 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.displayareahelper; + +import android.view.SurfaceControl; + +import java.util.function.Consumer; + +/** + * Interface that allows to perform various display area related actions + */ +public interface DisplayAreaHelper { + + /** + * Updates SurfaceControl builder to reparent it to the root display area + * @param displayId id of the display to which root display area it should be reparented to + * @param builder surface control builder that should be updated + * @param onUpdated callback that is invoked after updating the builder, called on + * the shell main thread + */ + default void attachToRootDisplayArea(int displayId, SurfaceControl.Builder builder, + Consumer<SurfaceControl.Builder> onUpdated) { + } + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelperController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelperController.java new file mode 100644 index 000000000000..ef9ad6d10e6b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/displayareahelper/DisplayAreaHelperController.java @@ -0,0 +1,45 @@ +/* + * 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.displayareahelper; + +import android.view.SurfaceControl; + +import com.android.wm.shell.RootDisplayAreaOrganizer; + +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +public class DisplayAreaHelperController implements DisplayAreaHelper { + + private final Executor mExecutor; + private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer; + + public DisplayAreaHelperController(Executor executor, + RootDisplayAreaOrganizer rootDisplayAreaOrganizer) { + mExecutor = executor; + mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer; + } + + @Override + public void attachToRootDisplayArea(int displayId, SurfaceControl.Builder builder, + Consumer<SurfaceControl.Builder> onUpdated) { + mExecutor.execute(() -> { + mRootDisplayAreaOrganizer.attachToDisplayArea(displayId, builder); + onUpdated.accept(builder); + }); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java index 08ab85cab97b..fc1b704e95ad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java @@ -16,9 +16,6 @@ package com.android.wm.shell.fullscreen; -import static android.graphics.Color.blue; -import static android.graphics.Color.green; -import static android.graphics.Color.red; import static android.util.MathUtils.lerp; import static android.view.Display.DEFAULT_DISPLAY; @@ -36,12 +33,11 @@ import android.view.InsetsState; import android.view.SurfaceControl; import com.android.internal.policy.ScreenDecorationsUtils; -import com.android.wm.shell.R; -import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; +import com.android.wm.shell.unfold.UnfoldBackgroundController; import java.util.concurrent.Executor; @@ -59,21 +55,17 @@ public final class FullscreenUnfoldController implements UnfoldListener, private static final float VERTICAL_START_MARGIN = 0.03f; private static final float END_SCALE = 1f; private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2; - private static final int BACKGROUND_LAYER_Z_INDEX = -1; - private final Context mContext; private final Executor mExecutor; private final ShellUnfoldProgressProvider mProgressProvider; - private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private final DisplayInsetsController mDisplayInsetsController; private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>(); + private final UnfoldBackgroundController mBackgroundController; - private SurfaceControl mBackgroundLayer; private InsetsSource mTaskbarInsetsSource; private final float mWindowCornerRadiusPx; - private final float[] mBackgroundColor; private final float mExpandedTaskBarHeight; private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); @@ -81,19 +73,17 @@ public final class FullscreenUnfoldController implements UnfoldListener, public FullscreenUnfoldController( @NonNull Context context, @NonNull Executor executor, + @NonNull UnfoldBackgroundController backgroundController, @NonNull ShellUnfoldProgressProvider progressProvider, - @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, @NonNull DisplayInsetsController displayInsetsController ) { - mContext = context; mExecutor = executor; - mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mProgressProvider = progressProvider; mDisplayInsetsController = displayInsetsController; mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.taskbar_frame_height); - mBackgroundColor = getBackgroundColor(); + mBackgroundController = backgroundController; } /** @@ -108,7 +98,7 @@ public final class FullscreenUnfoldController implements UnfoldListener, public void onStateChangeProgress(float progress) { if (mAnimationContextByTaskId.size() == 0) return; - ensureBackground(); + mBackgroundController.ensureBackground(mTransaction); for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { final AnimationContext context = mAnimationContextByTaskId.valueAt(i); @@ -135,7 +125,7 @@ public final class FullscreenUnfoldController implements UnfoldListener, resetSurface(context); } - removeBackground(); + mBackgroundController.removeBackground(mTransaction); mTransaction.apply(); } @@ -178,7 +168,7 @@ public final class FullscreenUnfoldController implements UnfoldListener, } if (mAnimationContextByTaskId.size() == 0) { - removeBackground(); + mBackgroundController.removeBackground(mTransaction); } mTransaction.apply(); @@ -194,39 +184,6 @@ public final class FullscreenUnfoldController implements UnfoldListener, (float) context.mTaskInfo.positionInParent.y); } - private void ensureBackground() { - if (mBackgroundLayer != null) return; - - SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder() - .setName("app-unfold-background") - .setCallsite("AppUnfoldTransitionController") - .setColorLayer(); - mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder); - mBackgroundLayer = colorLayerBuilder.build(); - - mTransaction - .setColor(mBackgroundLayer, mBackgroundColor) - .show(mBackgroundLayer) - .setLayer(mBackgroundLayer, BACKGROUND_LAYER_Z_INDEX); - } - - private void removeBackground() { - if (mBackgroundLayer == null) return; - if (mBackgroundLayer.isValid()) { - mTransaction.remove(mBackgroundLayer); - } - mBackgroundLayer = null; - } - - private float[] getBackgroundColor() { - int colorInt = mContext.getResources().getColor(R.color.unfold_transition_background); - return new float[]{ - (float) red(colorInt) / 255.0F, - (float) green(colorInt) / 255.0F, - (float) blue(colorInt) / 255.0F - }; - } - private class AnimationContext { final SurfaceControl mLeash; final Rect mStartCropRect = new Rect(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 96867761cc7e..291cbb3676dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -282,6 +282,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mMainExecutor.execute(() -> { mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP); }); + mPipTransitionController.setPipOrganizer(this); displayController.addDisplayWindowListener(this); } @@ -349,6 +350,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } } + public ActivityManager.RunningTaskInfo getTaskInfo() { + return mTaskInfo; + } + public SurfaceControl getSurfaceControl() { return mLeash; } @@ -716,6 +721,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY); } + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mPipTransitionController.forceFinishTransition(); + } final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController.getCurrentAnimator(); if (animator != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 6fec1fbda7b0..328f3ed73f2e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -32,17 +32,18 @@ import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection; import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; +import android.app.ActivityManager; import android.app.TaskInfo; import android.content.Context; import android.graphics.Matrix; import android.graphics.Rect; import android.os.IBinder; +import android.util.Log; import android.view.Surface; import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; -import android.window.WindowContainerTransactionCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -57,11 +58,14 @@ import com.android.wm.shell.transition.Transitions; */ public class PipTransition extends PipTransitionController { + private static final String TAG = PipTransition.class.getSimpleName(); + private final PipTransitionState mPipTransitionState; private final int mEnterExitAnimationDuration; private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS; private Transitions.TransitionFinishCallback mFinishCallback; private Rect mExitDestinationBounds = new Rect(); + private IBinder mExitTransition = null; public PipTransition(Context context, PipBoundsState pipBoundsState, @@ -96,7 +100,7 @@ public class PipTransition extends PipTransitionController { public void startTransition(Rect destinationBounds, WindowContainerTransaction out) { if (destinationBounds != null) { mExitDestinationBounds.set(destinationBounds); - mTransitions.startTransition(TRANSIT_EXIT_PIP, out, this); + mExitTransition = mTransitions.startTransition(TRANSIT_EXIT_PIP, out, this); } else { mTransitions.startTransition(TRANSIT_REMOVE_PIP, out, this); } @@ -109,14 +113,19 @@ public class PipTransition extends PipTransitionController { @android.annotation.NonNull SurfaceControl.Transaction finishTransaction, @android.annotation.NonNull Transitions.TransitionFinishCallback finishCallback) { - if (info.getType() == TRANSIT_EXIT_PIP && info.getChanges().size() == 1) { - final TransitionInfo.Change change = info.getChanges().get(0); - mFinishCallback = finishCallback; - startTransaction.apply(); - boolean success = startExpandAnimation(change.getTaskInfo(), change.getLeash(), - new Rect(mExitDestinationBounds)); - mExitDestinationBounds.setEmpty(); - return success; + if (mExitTransition == transition || info.getType() == TRANSIT_EXIT_PIP) { + mExitTransition = null; + if (info.getChanges().size() == 1) { + final TransitionInfo.Change change = info.getChanges().get(0); + mFinishCallback = finishCallback; + startTransaction.apply(); + boolean success = startExpandAnimation(change.getTaskInfo(), change.getLeash(), + new Rect(mExitDestinationBounds)); + mExitDestinationBounds.setEmpty(); + return success; + } else { + Log.e(TAG, "Got an exit-pip transition with unexpected change-list"); + } } if (info.getType() == TRANSIT_REMOVE_PIP) { @@ -183,26 +192,58 @@ public class PipTransition extends PipTransitionController { } @Override + public void onTransitionMerged(@NonNull IBinder transition) { + if (transition != mExitTransition) { + return; + } + // This means an expand happened before enter-pip finished and we are now "merging" a + // no-op transition that happens to match our exit-pip. + boolean cancelled = false; + if (mPipAnimationController.getCurrentAnimator() != null) { + mPipAnimationController.getCurrentAnimator().cancel(); + cancelled = true; + } + // Unset exitTransition AFTER cancel so that finishResize knows we are merging. + mExitTransition = null; + if (!cancelled) return; + final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo(); + if (taskInfo != null) { + startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(), + new Rect(mExitDestinationBounds)); + } + mExitDestinationBounds.setEmpty(); + } + + @Override public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, - SurfaceControl.Transaction tx) { + @Nullable SurfaceControl.Transaction tx) { if (isInPipDirection(direction)) { mPipTransitionState.setTransitionState(PipTransitionState.ENTERED_PIP); } - WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareFinishResizeTransaction(taskInfo, destinationBounds, - direction, tx, wct); - mFinishCallback.onTransitionFinished(wct, new WindowContainerTransactionCallback() { - @Override - public void onTransactionReady(int id, @NonNull SurfaceControl.Transaction t) { - t.merge(tx); - t.apply(); + // If there is an expected exit transition, then the exit will be "merged" into this + // transition so don't fire the finish-callback in that case. + if (mExitTransition == null && mFinishCallback != null) { + WindowContainerTransaction wct = new WindowContainerTransaction(); + prepareFinishResizeTransaction(taskInfo, destinationBounds, + direction, wct); + if (tx != null) { + wct.setBoundsChangeTransaction(taskInfo.token, tx); } - }); + mFinishCallback.onTransitionFinished(wct, null /* wctCallback */); + mFinishCallback = null; + } finishResizeForMenu(destinationBounds); } + @Override + public void forceFinishTransition() { + if (mFinishCallback == null) return; + mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCallback */); + mFinishCallback = null; + } + private boolean startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash, final Rect destinationBounds) { PipAnimationController.PipTransitionAnimator animator = @@ -243,7 +284,7 @@ public class PipTransition extends PipTransitionController { startTransaction.merge(tx); startTransaction.apply(); mPipBoundsState.setBounds(destinationBounds); - onFinishResize(taskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, tx); + onFinishResize(taskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, null /* tx */); sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); mFinishCallback = null; mPipTransitionState.setInSwipePipToHomeTransition(false); @@ -292,7 +333,6 @@ public class PipTransition extends PipTransitionController { private void prepareFinishResizeTransaction(TaskInfo taskInfo, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, - SurfaceControl.Transaction tx, WindowContainerTransaction wct) { Rect taskBounds = null; if (isInPipDirection(direction)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index dbf603ca72d9..376f3298a83c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -49,6 +49,7 @@ public abstract class PipTransitionController implements Transitions.TransitionH protected final Transitions mTransitions; private final Handler mMainHandler; private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>(); + protected PipTaskOrganizer mPipOrganizer; protected final PipAnimationController.PipAnimationCallback mPipAnimationCallback = new PipAnimationController.PipAnimationCallback() { @@ -103,6 +104,13 @@ public abstract class PipTransitionController implements Transitions.TransitionH // Default implementation does nothing. } + /** + * Called when the transition animation can't continue (eg. task is removed during + * animation) + */ + public void forceFinishTransition() { + } + public PipTransitionController(PipBoundsState pipBoundsState, PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm, PipAnimationController pipAnimationController, Transitions transitions, @@ -119,6 +127,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH } } + void setPipOrganizer(PipTaskOrganizer pto) { + mPipOrganizer = pto; + } + /** * Registers {@link PipTransitionCallback} to receive transition callbacks. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java index d0998eb57633..a47a15287dda 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java @@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import android.annotation.Nullable; import android.graphics.Rect; import android.view.SurfaceSession; import android.window.WindowContainerToken; @@ -38,33 +39,34 @@ class MainStage extends StageTaskListener { MainStage(ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession) { - super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession); + SurfaceSession surfaceSession, + @Nullable StageTaskUnfoldController stageTaskUnfoldController) { + super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, + stageTaskUnfoldController); } boolean isActive() { return mIsActive; } - void activate(Rect rootBounds, WindowContainerTransaction wct) { + void activate(Rect rootBounds, WindowContainerTransaction wct, boolean includingTopTask) { if (mIsActive) return; final WindowContainerToken rootToken = mRootTaskInfo.token; wct.setBounds(rootToken, rootBounds) .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW) - .setLaunchRoot( - rootToken, - CONTROLLED_WINDOWING_MODES, - CONTROLLED_ACTIVITY_TYPES) - .reparentTasks( - null /* currentParent */, - rootToken, - CONTROLLED_WINDOWING_MODES, - CONTROLLED_ACTIVITY_TYPES, - true /* onTop */) // Moving the root task to top after the child tasks were re-parented , or the root // task cannot be visible and focused. .reorder(rootToken, true /* onTop */); + if (includingTopTask) { + wct.reparentTasks( + null /* currentParent */, + rootToken, + CONTROLLED_WINDOWING_MODES, + CONTROLLED_ACTIVITY_TYPES, + true /* onTop */, + true /* reparentTopOnly */); + } mIsActive = true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index 0e7ccd3515c4..dc8fb9fbd7a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java @@ -46,8 +46,10 @@ class SideStage extends StageTaskListener implements SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession) { - super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession); + SurfaceSession surfaceSession, + @Nullable StageTaskUnfoldController stageTaskUnfoldController) { + super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, + stageTaskUnfoldController); mContext = context; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index ac68b3b8a6a9..ec71fbee9a29 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -16,6 +16,8 @@ package com.android.wm.shell.splitscreen; +import static android.app.ActivityManager.START_SUCCESS; +import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; @@ -68,8 +70,11 @@ import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; import java.util.Arrays; +import java.util.Optional; import java.util.concurrent.Executor; +import javax.inject.Provider; + /** * Class manages split-screen multitasking mode and implements the main interface * {@link SplitScreen}. @@ -91,6 +96,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private final Transitions mTransitions; private final TransactionPool mTransactionPool; private final SplitscreenEventLogger mLogger; + private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider; private StageCoordinator mStageCoordinator; @@ -99,7 +105,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellExecutor mainExecutor, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, - Transitions transitions, TransactionPool transactionPool) { + Transitions transitions, TransactionPool transactionPool, + Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { mTaskOrganizer = shellTaskOrganizer; mSyncQueue = syncQueue; mContext = context; @@ -109,6 +116,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mDisplayInsetsController = displayInsetsController; mTransitions = transitions; mTransactionPool = transactionPool; + mUnfoldControllerProvider = unfoldControllerProvider; mLogger = new SplitscreenEventLogger(); } @@ -131,7 +139,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, // TODO: Multi-display mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController, - mDisplayInsetsController, mTransitions, mTransactionPool, mLogger); + mDisplayInsetsController, mTransitions, mTransactionPool, mLogger, + mUnfoldControllerProvider); } } @@ -206,7 +215,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */); try { - ActivityTaskManager.getService().startActivityFromRecents(taskId, options); + final int result = + ActivityTaskManager.getService().startActivityFromRecents(taskId, options); + if (result == START_SUCCESS || result == START_TASK_TO_FRONT) { + mStageCoordinator.evictOccludedChildren(position); + } } catch (RemoteException e) { Slog.e(TAG, "Failed to launch task", e); } @@ -222,6 +235,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mContext.getSystemService(LauncherApps.class); launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */, options, user); + mStageCoordinator.evictOccludedChildren(position); } catch (ActivityNotFoundException e) { Slog.e(TAG, "Failed to launch shortcut", e); } @@ -265,6 +279,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, Slog.e(TAG, "Error finishing legacy transition: ", e); } } + + // Launching a new app into a specific split evicts tasks previously in the same + // split. + mStageCoordinator.evictOccludedChildren(position); } }; WindowContainerTransaction wct = new WindowContainerTransaction(); 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 d7a6cfff6c6f..0cff18e2ba85 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 @@ -95,6 +95,9 @@ import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Optional; + +import javax.inject.Provider; /** * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and @@ -121,8 +124,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, private final MainStage mMainStage; private final StageListenerImpl mMainStageListener = new StageListenerImpl(); + private final StageTaskUnfoldController mMainUnfoldController; private final SideStage mSideStage; private final StageListenerImpl mSideStageListener = new StageListenerImpl(); + private final StageTaskUnfoldController mSideUnfoldController; @SplitPosition private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -179,26 +184,32 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, Transitions transitions, - TransactionPool transactionPool, SplitscreenEventLogger logger) { + TransactionPool transactionPool, SplitscreenEventLogger logger, + Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; mRootTDAOrganizer = rootTDAOrganizer; mTaskOrganizer = taskOrganizer; mLogger = logger; + mMainUnfoldController = unfoldControllerProvider.get().orElse(null); + mSideUnfoldController = unfoldControllerProvider.get().orElse(null); + mMainStage = new MainStage( mTaskOrganizer, mDisplayId, mMainStageListener, mSyncQueue, - mSurfaceSession); + mSurfaceSession, + mMainUnfoldController); mSideStage = new SideStage( mContext, mTaskOrganizer, mDisplayId, mSideStageListener, mSyncQueue, - mSurfaceSession); + mSurfaceSession, + mSideUnfoldController); mDisplayImeController = displayImeController; mDisplayInsetsController = displayInsetsController; mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage); @@ -218,7 +229,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, - SplitscreenEventLogger logger) { + SplitscreenEventLogger logger, + Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -232,6 +244,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout = splitLayout; mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, mOnTransitionAnimationComplete); + mMainUnfoldController = unfoldControllerProvider.get().orElse(null); + mSideUnfoldController = unfoldControllerProvider.get().orElse(null); mLogger = logger; transitions.addHandler(this); } @@ -249,7 +263,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @SplitPosition int sideStagePosition) { final WindowContainerTransaction wct = new WindowContainerTransaction(); setSideStagePosition(sideStagePosition, wct); - mMainStage.activate(getMainStageBounds(), wct); + mMainStage.activate(getMainStageBounds(), wct, true /* reparent */); mSideStage.addTask(task, getSideStageBounds(), wct); mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> updateSurfaceBounds(null /* layout */, t)); @@ -285,7 +299,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Build a request WCT that will launch both apps such that task 0 is on the main stage // while task 1 is on the side stage. - mMainStage.activate(getMainStageBounds(), wct); + mMainStage.activate(getMainStageBounds(), wct, false /* reparent */); mSideStage.setBounds(getSideStageBounds(), wct); // Make sure the launch options will put tasks in the corresponding split roots @@ -354,7 +368,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Build a request WCT that will launch both apps such that task 0 is on the main stage // while task 1 is on the side stage. - mMainStage.activate(getMainStageBounds(), wct); + mMainStage.activate(getMainStageBounds(), wct, false /* reparent */); mSideStage.setBounds(getSideStageBounds(), wct); // Make sure the launch options will put tasks in the corresponding split roots @@ -380,6 +394,12 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this); } + void evictOccludedChildren(@SplitPosition int position) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + (position == mSideStagePosition ? mSideStage : mMainStage).evictOccludedChildren(wct); + mTaskOrganizer.applyTransaction(wct); + } + Bundle resolveStartStage(@SplitScreen.StageType int stage, @SplitPosition int position, @androidx.annotation.Nullable Bundle options, @androidx.annotation.Nullable WindowContainerTransaction wct) { @@ -457,9 +477,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mSideStageListener.mVisible && updateBounds) { if (wct == null) { // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds. - onLayoutChanged(mSplitLayout); + onLayoutSizeChanged(mSplitLayout); } else { updateWindowBounds(mSplitLayout, wct); + updateUnfoldBounds(); } } } @@ -515,6 +536,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStage.removeAllTasks(wct, childrenToTop == mSideStage); mMainStage.deactivate(wct, childrenToTop == mMainStage); mTaskOrganizer.applyTransaction(wct); + mSyncQueue.runInSync(t -> t + .setWindowCrop(mMainStage.mRootLeash, null) + .setWindowCrop(mSideStage.mRootLeash, null)); // Hide divider and reset its position. setDividerVisibility(false); mSplitLayout.resetDividerPosition(); @@ -603,6 +627,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final SplitScreen.SplitScreenListener l = mListeners.get(i); l.onSplitVisibilityChanged(mDividerVisible); } + + if (mMainUnfoldController != null && mSideUnfoldController != null) { + mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible); + mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible); + } } private void onStageRootTaskAppeared(StageListenerImpl stageListener) { @@ -641,6 +670,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDividerVisible = visible; if (visible) { mSplitLayout.init(); + updateUnfoldBounds(); } else { mSplitLayout.release(); } @@ -732,7 +762,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } else if (isSideStage) { final WindowContainerTransaction wct = new WindowContainerTransaction(); // Make sure the main stage is active. - mMainStage.activate(getMainStageBounds(), wct); + mMainStage.activate(getMainStageBounds(), wct, true /* reparent */); mSideStage.setBounds(getSideStageBounds(), wct); mTaskOrganizer.applyTransaction(wct); } @@ -775,21 +805,34 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override - public void onLayoutChanging(SplitLayout layout) { + public void onLayoutPositionChanging(SplitLayout layout) { + mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t)); + } + + @Override + public void onLayoutSizeChanging(SplitLayout layout) { mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t)); mSideStage.setOutlineVisibility(false); } @Override - public void onLayoutChanged(SplitLayout layout) { + public void onLayoutSizeChanged(SplitLayout layout) { final WindowContainerTransaction wct = new WindowContainerTransaction(); updateWindowBounds(layout, wct); + updateUnfoldBounds(); mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t)); mSideStage.setOutlineVisibility(true); mLogger.logResize(mSplitLayout.getDividerPositionAsFraction()); } + private void updateUnfoldBounds() { + if (mMainUnfoldController != null && mSideUnfoldController != null) { + mMainUnfoldController.onLayoutChanged(getMainStageBounds()); + mSideUnfoldController.onLayoutChanged(getSideStageBounds()); + } + } + /** * Populates `wct` with operations that match the split windows to the current layout. * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied @@ -827,13 +870,13 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override - public void onLayoutShifted(int offsetX, int offsetY, SplitLayout layout) { + public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) { final StageTaskListener topLeftStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; final StageTaskListener bottomRightStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; final WindowContainerTransaction wct = new WindowContainerTransaction(); - layout.applyLayoutShifted(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo, + layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo); mTaskOrganizer.applyTransaction(wct); } @@ -846,6 +889,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDisplayAreaInfo.configuration, this, mParentContainerCallbacks, mDisplayImeController, mTaskOrganizer); mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout); + + if (mMainUnfoldController != null && mSideUnfoldController != null) { + mMainUnfoldController.init(); + mSideUnfoldController.init(); + } } } @@ -860,7 +908,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mSplitLayout != null && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration) && mMainStage.isActive()) { - onLayoutChanged(mSplitLayout); + onLayoutSizeChanged(mSplitLayout); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 4140332f50a3..071badf2bc23 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; import android.annotation.CallSuper; +import android.annotation.Nullable; import android.app.ActivityManager; import android.graphics.Point; import android.graphics.Rect; @@ -67,6 +68,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { void onChildTaskStatusChanged(int taskId, boolean present, boolean visible); void onRootTaskVanished(); + void onNoLongerSupportMultiWindow(); } @@ -80,12 +82,16 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>(); private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>(); + private final StageTaskUnfoldController mStageTaskUnfoldController; + StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession) { + SurfaceSession surfaceSession, + @Nullable StageTaskUnfoldController stageTaskUnfoldController) { mCallbacks = callbacks; mSyncQueue = syncQueue; mSurfaceSession = surfaceSession; + mStageTaskUnfoldController = stageTaskUnfoldController; taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); } @@ -158,6 +164,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + "\n mRootTaskInfo: " + mRootTaskInfo); } + + if (mStageTaskUnfoldController != null) { + mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash); + } } @Override @@ -210,6 +220,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + "\n mRootTaskInfo: " + mRootTaskInfo); } + + if (mStageTaskUnfoldController != null) { + mStageTaskUnfoldController.onTaskVanished(taskInfo); + } } @Override @@ -234,6 +248,15 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */); } + void evictOccludedChildren(WindowContainerTransaction wct) { + for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { + final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); + if (!taskInfo.isVisible) { + wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); + } + } + } + void setVisibility(boolean visible, WindowContainerTransaction wct) { wct.reorder(mRootTaskInfo.token, visible /* onTop */); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java new file mode 100644 index 000000000000..e904f6a9e22c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java @@ -0,0 +1,224 @@ +/* + * 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.splitscreen; + +import static android.view.Display.DEFAULT_DISPLAY; + +import android.animation.RectEvaluator; +import android.animation.TypeEvaluator; +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.content.Context; +import android.graphics.Rect; +import android.util.SparseArray; +import android.view.InsetsSource; +import android.view.InsetsState; +import android.view.SurfaceControl; + +import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; +import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; +import com.android.wm.shell.unfold.UnfoldBackgroundController; + +import java.util.concurrent.Executor; + +/** + * Controls transformations of the split screen task surfaces in response + * to the unfolding/folding action on foldable devices + */ +public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener { + + private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect()); + private static final float CROPPING_START_MARGIN_FRACTION = 0.05f; + + private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>(); + private final ShellUnfoldProgressProvider mUnfoldProgressProvider; + private final DisplayInsetsController mDisplayInsetsController; + private final UnfoldBackgroundController mBackgroundController; + private final Executor mExecutor; + private final int mExpandedTaskBarHeight; + private final float mWindowCornerRadiusPx; + private final Rect mStageBounds = new Rect(); + private final TransactionPool mTransactionPool; + + private InsetsSource mTaskbarInsetsSource; + private boolean mBothStagesVisible; + + public StageTaskUnfoldController(@NonNull Context context, + @NonNull TransactionPool transactionPool, + @NonNull ShellUnfoldProgressProvider unfoldProgressProvider, + @NonNull DisplayInsetsController displayInsetsController, + @NonNull UnfoldBackgroundController backgroundController, + @NonNull Executor executor) { + mUnfoldProgressProvider = unfoldProgressProvider; + mTransactionPool = transactionPool; + mExecutor = executor; + mBackgroundController = backgroundController; + mDisplayInsetsController = displayInsetsController; + mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); + mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.taskbar_frame_height); + } + + /** + * Initializes the controller, starts listening for the external events + */ + public void init() { + mUnfoldProgressProvider.addListener(mExecutor, this); + mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this); + } + + @Override + public void insetsChanged(InsetsState insetsState) { + mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + AnimationContext context = mAnimationContextByTaskId.valueAt(i); + context.update(); + } + } + + /** + * Called when split screen task appeared + * @param taskInfo info for the appeared task + * @param leash surface leash for the appeared task + */ + public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { + AnimationContext context = new AnimationContext(leash); + mAnimationContextByTaskId.put(taskInfo.taskId, context); + } + + /** + * Called when a split screen task vanished + * @param taskInfo info for the vanished task + */ + public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId); + if (context != null) { + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + resetSurface(transaction, context); + transaction.apply(); + mTransactionPool.release(transaction); + } + mAnimationContextByTaskId.remove(taskInfo.taskId); + } + + @Override + public void onStateChangeProgress(float progress) { + if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return; + + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + mBackgroundController.ensureBackground(transaction); + + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + AnimationContext context = mAnimationContextByTaskId.valueAt(i); + + context.mCurrentCropRect.set(RECT_EVALUATOR + .evaluate(progress, context.mStartCropRect, context.mEndCropRect)); + + transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect) + .setCornerRadius(context.mLeash, mWindowCornerRadiusPx); + } + + transaction.apply(); + + mTransactionPool.release(transaction); + } + + @Override + public void onStateChangeFinished() { + resetTransformations(); + } + + /** + * Called when split screen visibility changes + * @param bothStagesVisible true if both stages of the split screen are visible + */ + public void onSplitVisibilityChanged(boolean bothStagesVisible) { + mBothStagesVisible = bothStagesVisible; + if (!bothStagesVisible) { + resetTransformations(); + } + } + + /** + * Called when split screen stage bounds changed + * @param bounds new bounds for this stage + */ + public void onLayoutChanged(Rect bounds) { + mStageBounds.set(bounds); + + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + final AnimationContext context = mAnimationContextByTaskId.valueAt(i); + context.update(); + } + } + + private void resetTransformations() { + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + final AnimationContext context = mAnimationContextByTaskId.valueAt(i); + resetSurface(transaction, context); + } + mBackgroundController.removeBackground(transaction); + transaction.apply(); + + mTransactionPool.release(transaction); + } + + private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) { + transaction + .setWindowCrop(context.mLeash, null) + .setCornerRadius(context.mLeash, 0.0F); + } + + private class AnimationContext { + final SurfaceControl mLeash; + final Rect mStartCropRect = new Rect(); + final Rect mEndCropRect = new Rect(); + final Rect mCurrentCropRect = new Rect(); + + private AnimationContext(SurfaceControl leash) { + this.mLeash = leash; + update(); + } + + private void update() { + mStartCropRect.set(mStageBounds); + + if (mTaskbarInsetsSource != null) { + // Only insets the cropping window with taskbar when taskbar is expanded + if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) { + mStartCropRect.inset(mTaskbarInsetsSource + .calculateVisibleInsets(mStartCropRect)); + } + } + + // Offset to surface coordinates as layout bounds are in screen coordinates + mStartCropRect.offsetTo(0, 0); + + mEndCropRect.set(mStartCropRect); + + int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height()); + int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION); + mStartCropRect.inset(margin, margin, margin, margin); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl new file mode 100644 index 000000000000..45f6d3c8b154 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl @@ -0,0 +1,103 @@ +/* + * 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.stagesplit; + +import android.app.PendingIntent; +import android.content.Intent; +import android.os.Bundle; +import android.os.UserHandle; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationTarget; +import android.window.RemoteTransition; + +import com.android.wm.shell.stagesplit.ISplitScreenListener; + +/** + * Interface that is exposed to remote callers to manipulate the splitscreen feature. + */ +interface ISplitScreen { + + /** + * Registers a split screen listener. + */ + oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1; + + /** + * Unregisters a split screen listener. + */ + oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2; + + /** + * Hides the side-stage if it is currently visible. + */ + oneway void setSideStageVisibility(boolean visible) = 3; + + /** + * Removes a task from the side stage. + */ + oneway void removeFromSideStage(int taskId) = 4; + + /** + * Removes the split-screen stages and leaving indicated task to top. Passing INVALID_TASK_ID + * to indicate leaving no top task after leaving split-screen. + */ + oneway void exitSplitScreen(int toTopTaskId) = 5; + + /** + * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. + */ + oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6; + + /** + * Starts a task in a stage. + */ + oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7; + + /** + * Starts a shortcut in a stage. + */ + oneway void startShortcut(String packageName, String shortcutId, int stage, int position, + in Bundle options, in UserHandle user) = 8; + + /** + * Starts an activity in a stage. + */ + oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage, + int position, in Bundle options) = 9; + + /** + * Starts tasks simultaneously in one transition. + */ + oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId, + in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10; + + /** + * Version of startTasks using legacy transition system. + */ + oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions, + int sideTaskId, in Bundle sideOptions, int sidePosition, + in RemoteAnimationAdapter adapter) = 11; + + /** + * Blocking call that notifies and gets additional split-screen targets when entering + * recents (for example: the dividerBar). + * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled). + * @param appTargets apps that will be re-parented to display area + */ + RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, + in RemoteAnimationTarget[] appTargets) = 12; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl new file mode 100644 index 000000000000..46e4299f99fa --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl @@ -0,0 +1,33 @@ +/* + * 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.stagesplit; + +/** + * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks. + */ +oneway interface ISplitScreenListener { + + /** + * Called when the stage position changes. + */ + void onStagePositionChanged(int stage, int position); + + /** + * Called when a task changes stages. + */ + void onTaskStageChanged(int taskId, int stage, boolean visible); +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java new file mode 100644 index 000000000000..83855be91e04 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java @@ -0,0 +1,104 @@ +/* + * 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.wm.shell.stagesplit; + +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; + +import android.annotation.Nullable; +import android.graphics.Rect; +import android.view.SurfaceSession; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.SyncTransactionQueue; + +/** + * Main stage for split-screen mode. When split-screen is active all standard activity types launch + * on the main stage, except for task that are explicitly pinned to the {@link SideStage}. + * @see StageCoordinator + */ +class MainStage extends StageTaskListener { + private static final String TAG = MainStage.class.getSimpleName(); + + private boolean mIsActive = false; + + MainStage(ShellTaskOrganizer taskOrganizer, int displayId, + StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, + SurfaceSession surfaceSession, + @Nullable StageTaskUnfoldController stageTaskUnfoldController) { + super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, + stageTaskUnfoldController); + } + + boolean isActive() { + return mIsActive; + } + + void activate(Rect rootBounds, WindowContainerTransaction wct) { + if (mIsActive) return; + + final WindowContainerToken rootToken = mRootTaskInfo.token; + wct.setBounds(rootToken, rootBounds) + .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW) + .setLaunchRoot( + rootToken, + CONTROLLED_WINDOWING_MODES, + CONTROLLED_ACTIVITY_TYPES) + .reparentTasks( + null /* currentParent */, + rootToken, + CONTROLLED_WINDOWING_MODES, + CONTROLLED_ACTIVITY_TYPES, + true /* onTop */) + // Moving the root task to top after the child tasks were re-parented , or the root + // task cannot be visible and focused. + .reorder(rootToken, true /* onTop */); + + mIsActive = true; + } + + void deactivate(WindowContainerTransaction wct) { + deactivate(wct, false /* toTop */); + } + + void deactivate(WindowContainerTransaction wct, boolean toTop) { + if (!mIsActive) return; + mIsActive = false; + + if (mRootTaskInfo == null) return; + final WindowContainerToken rootToken = mRootTaskInfo.token; + wct.setLaunchRoot( + rootToken, + null, + null) + .reparentTasks( + rootToken, + null /* newParent */, + CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, + CONTROLLED_ACTIVITY_TYPES, + toTop) + // We want this re-order to the bottom regardless since we are re-parenting + // all its tasks. + .reorder(rootToken, false /* onTop */); + } + + void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) { + wct.setBounds(mRootTaskInfo.token, bounds) + .setWindowingMode(mRootTaskInfo.token, windowingMode); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java new file mode 100644 index 000000000000..8fbad52c630f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java @@ -0,0 +1,181 @@ +/* + * 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.stagesplit; + +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.Binder; +import android.view.IWindow; +import android.view.InsetsSource; +import android.view.InsetsState; +import android.view.LayoutInflater; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.WindowlessWindowManager; +import android.widget.FrameLayout; + +import com.android.wm.shell.R; + +/** + * Handles drawing outline of the bounds of provided root surface. The outline will be drown with + * the consideration of display insets like status bar, navigation bar and display cutout. + */ +class OutlineManager extends WindowlessWindowManager { + private static final String WINDOW_NAME = "SplitOutlineLayer"; + private final Context mContext; + private final Rect mRootBounds = new Rect(); + private final Rect mTempRect = new Rect(); + private final Rect mLastOutlineBounds = new Rect(); + private final InsetsState mInsetsState = new InsetsState(); + private final int mExpandedTaskBarHeight; + private OutlineView mOutlineView; + private SurfaceControlViewHost mViewHost; + private SurfaceControl mHostLeash; + private SurfaceControl mLeash; + + OutlineManager(Context context, Configuration configuration) { + super(configuration, null /* rootSurface */, null /* hostInputToken */); + mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY, + null /* options */); + mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.taskbar_frame_height); + } + + @Override + protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { + b.setParent(mHostLeash); + } + + void inflate(SurfaceControl rootLeash, Rect rootBounds) { + if (mLeash != null || mViewHost != null) return; + + mHostLeash = rootLeash; + mRootBounds.set(rootBounds); + mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); + + final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext) + .inflate(R.layout.split_outline, null); + mOutlineView = rootLayout.findViewById(R.id.split_outline); + + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY, + FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT); + lp.width = mRootBounds.width(); + lp.height = mRootBounds.height(); + lp.token = new Binder(); + lp.setTitle(WINDOW_NAME); + lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; + // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports + // TRUSTED_OVERLAY for windowless window without input channel. + mViewHost.setView(rootLayout, lp); + mLeash = getSurfaceControl(mViewHost.getWindowToken()); + + drawOutline(); + } + + void release() { + if (mViewHost != null) { + mViewHost.release(); + mViewHost = null; + } + mRootBounds.setEmpty(); + mLastOutlineBounds.setEmpty(); + mOutlineView = null; + mHostLeash = null; + mLeash = null; + } + + @Nullable + SurfaceControl getOutlineLeash() { + return mLeash; + } + + void setVisibility(boolean visible) { + if (mOutlineView != null) { + mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); + } + } + + void setRootBounds(Rect rootBounds) { + if (mViewHost == null || mViewHost.getView() == null) { + return; + } + + if (!mRootBounds.equals(rootBounds)) { + WindowManager.LayoutParams lp = + (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams(); + lp.width = rootBounds.width(); + lp.height = rootBounds.height(); + mViewHost.relayout(lp); + mRootBounds.set(rootBounds); + drawOutline(); + } + } + + void onInsetsChanged(InsetsState insetsState) { + if (!mInsetsState.equals(insetsState)) { + mInsetsState.set(insetsState); + drawOutline(); + } + } + + private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) { + outBounds.set(rootBounds); + final InsetsSource taskBarInsetsSource = + insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + // Only insets the divider bar with task bar when it's expanded so that the rounded corners + // will be drawn against task bar. + if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) { + outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds)); + } + + // Offset the coordinate from screen based to surface based. + outBounds.offset(-rootBounds.left, -rootBounds.top); + } + + void drawOutline() { + if (mOutlineView == null) { + return; + } + + computeOutlineBounds(mRootBounds, mInsetsState, mTempRect); + if (mTempRect.equals(mLastOutlineBounds)) { + return; + } + + ViewGroup.MarginLayoutParams lp = + (ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams(); + lp.leftMargin = mTempRect.left; + lp.topMargin = mTempRect.top; + lp.width = mTempRect.width(); + lp.height = mTempRect.height(); + mOutlineView.setLayoutParams(lp); + mLastOutlineBounds.set(mTempRect); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java new file mode 100644 index 000000000000..92b1381fc808 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java @@ -0,0 +1,82 @@ +/* + * 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.stagesplit; + +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.util.AttributeSet; +import android.view.RoundedCorner; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.R; + +/** View for drawing split outline. */ +public class OutlineView extends View { + private final Paint mPaint = new Paint(); + private final Path mPath = new Path(); + private final float[] mRadii = new float[8]; + + public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeWidth( + getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width)); + mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null)); + } + + @Override + protected void onAttachedToWindow() { + // TODO(b/200850654): match the screen corners with the actual display decor. + mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT); + mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT); + mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT); + mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT); + } + + private int getCornerRadius(@RoundedCorner.Position int position) { + final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position); + return roundedCorner == null ? 0 : roundedCorner.getRadius(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (changed) { + mPath.reset(); + mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW); + } + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.drawPath(mPath, mPaint); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java new file mode 100644 index 000000000000..55c4f3aea19a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java @@ -0,0 +1,144 @@ +/* + * 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.wm.shell.stagesplit; + +import android.annotation.CallSuper; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.content.Context; +import android.graphics.Rect; +import android.view.InsetsSourceControl; +import android.view.InsetsState; +import android.view.SurfaceControl; +import android.view.SurfaceSession; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.SyncTransactionQueue; + +/** + * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up + * here. All other task are launch in the {@link MainStage}. + * + * @see StageCoordinator + */ +class SideStage extends StageTaskListener implements + DisplayInsetsController.OnInsetsChangedListener { + private static final String TAG = SideStage.class.getSimpleName(); + private final Context mContext; + private OutlineManager mOutlineManager; + + SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId, + StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, + SurfaceSession surfaceSession, + @Nullable StageTaskUnfoldController stageTaskUnfoldController) { + super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, + stageTaskUnfoldController); + mContext = context; + } + + void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds, + WindowContainerTransaction wct) { + final WindowContainerToken rootToken = mRootTaskInfo.token; + wct.setBounds(rootToken, rootBounds) + .reparent(task.token, rootToken, true /* onTop*/) + // Moving the root task to top after the child tasks were reparented , or the root + // task cannot be visible and focused. + .reorder(rootToken, true /* onTop */); + } + + boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { + // No matter if the root task is empty or not, moving the root to bottom because it no + // longer preserves visible child task. + wct.reorder(mRootTaskInfo.token, false /* onTop */); + if (mChildrenTaskInfo.size() == 0) return false; + wct.reparentTasks( + mRootTaskInfo.token, + null /* newParent */, + CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, + CONTROLLED_ACTIVITY_TYPES, + toTop); + return true; + } + + boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) { + final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId); + if (task == null) return false; + wct.reparent(task.token, newParent, false /* onTop */); + return true; + } + + @Nullable + public SurfaceControl getOutlineLeash() { + return mOutlineManager.getOutlineLeash(); + } + + @Override + @CallSuper + public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { + super.onTaskAppeared(taskInfo, leash); + if (isRootTask(taskInfo)) { + mOutlineManager = new OutlineManager(mContext, taskInfo.configuration); + enableOutline(true); + } + } + + @Override + @CallSuper + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { + super.onTaskInfoChanged(taskInfo); + if (isRootTask(taskInfo)) { + mOutlineManager.setRootBounds(taskInfo.configuration.windowConfiguration.getBounds()); + } + } + + private boolean isRootTask(ActivityManager.RunningTaskInfo taskInfo) { + return mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId; + } + + void enableOutline(boolean enable) { + if (mOutlineManager == null) { + return; + } + + if (enable) { + if (mRootTaskInfo != null) { + mOutlineManager.inflate(mRootLeash, + mRootTaskInfo.configuration.windowConfiguration.getBounds()); + } + } else { + mOutlineManager.release(); + } + } + + void setOutlineVisibility(boolean visible) { + mOutlineManager.setVisibility(visible); + } + + @Override + public void insetsChanged(InsetsState insetsState) { + mOutlineManager.onInsetsChanged(insetsState); + } + + @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) { + insetsChanged(insetsState); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java new file mode 100644 index 000000000000..aec81a1ee86a --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java @@ -0,0 +1,99 @@ +/* + * 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.wm.shell.stagesplit; + +import android.annotation.IntDef; +import android.annotation.NonNull; + +import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.common.split.SplitLayout.SplitPosition; + +import java.util.concurrent.Executor; + +/** + * Interface to engage split-screen feature. + * TODO: Figure out which of these are actually needed outside of the Shell + */ +@ExternalThread +public interface SplitScreen { + /** + * Stage type isn't specified normally meaning to use what ever the default is. + * E.g. exit split-screen and launch the app in fullscreen. + */ + int STAGE_TYPE_UNDEFINED = -1; + /** + * The main stage type. + * @see MainStage + */ + int STAGE_TYPE_MAIN = 0; + + /** + * The side stage type. + * @see SideStage + */ + int STAGE_TYPE_SIDE = 1; + + @IntDef(prefix = { "STAGE_TYPE_" }, value = { + STAGE_TYPE_UNDEFINED, + STAGE_TYPE_MAIN, + STAGE_TYPE_SIDE + }) + @interface StageType {} + + /** Callback interface for listening to changes in a split-screen stage. */ + interface SplitScreenListener { + default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {} + default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {} + default void onSplitVisibilityChanged(boolean visible) {} + } + + /** Registers listener that gets split screen callback. */ + void registerSplitScreenListener(@NonNull SplitScreenListener listener, + @NonNull Executor executor); + + /** Unregisters listener that gets split screen callback. */ + void unregisterSplitScreenListener(@NonNull SplitScreenListener listener); + + /** + * Returns a binder that can be passed to an external process to manipulate SplitScreen. + */ + default ISplitScreen createExternalInterface() { + return null; + } + + /** + * Called when the keyguard occluded state changes. + * @param occluded Indicates if the keyguard is now occluded. + */ + void onKeyguardOccludedChanged(boolean occluded); + + /** + * Called when the visibility of the keyguard changes. + * @param showing Indicates if the keyguard is now visible. + */ + void onKeyguardVisibilityChanged(boolean showing); + + /** Get a string representation of a stage type */ + static String stageTypeToString(@StageType int stage) { + switch (stage) { + case STAGE_TYPE_UNDEFINED: return "UNDEFINED"; + case STAGE_TYPE_MAIN: return "MAIN"; + case STAGE_TYPE_SIDE: return "SIDE"; + default: return "UNKNOWN(" + stage + ")"; + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java new file mode 100644 index 000000000000..94db9cd958a3 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java @@ -0,0 +1,595 @@ +/* + * 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.wm.shell.stagesplit; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.RemoteAnimationTarget.MODE_OPENING; + +import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; +import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; + +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.PendingIntent; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.pm.LauncherApps; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.Slog; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.view.SurfaceSession; +import android.view.WindowManager; +import android.window.RemoteTransition; +import android.window.WindowContainerTransaction; + +import androidx.annotation.BinderThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.logging.InstanceId; +import com.android.internal.util.FrameworkStatsLog; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.RemoteCallable; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.common.annotations.ExternalThread; +import com.android.wm.shell.common.split.SplitLayout.SplitPosition; +import com.android.wm.shell.draganddrop.DragAndDropPolicy; +import com.android.wm.shell.transition.LegacyTransitions; +import com.android.wm.shell.transition.Transitions; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Optional; +import java.util.concurrent.Executor; + +import javax.inject.Provider; + +/** + * Class manages split-screen multitasking mode and implements the main interface + * {@link SplitScreen}. + * @see StageCoordinator + */ +// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen. +public class SplitScreenController implements DragAndDropPolicy.Starter, + RemoteCallable<SplitScreenController> { + private static final String TAG = SplitScreenController.class.getSimpleName(); + + private final ShellTaskOrganizer mTaskOrganizer; + private final SyncTransactionQueue mSyncQueue; + private final Context mContext; + private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer; + private final ShellExecutor mMainExecutor; + private final SplitScreenImpl mImpl = new SplitScreenImpl(); + private final DisplayImeController mDisplayImeController; + private final DisplayInsetsController mDisplayInsetsController; + private final Transitions mTransitions; + private final TransactionPool mTransactionPool; + private final SplitscreenEventLogger mLogger; + private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider; + + private StageCoordinator mStageCoordinator; + + public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer, + SyncTransactionQueue syncQueue, Context context, + RootTaskDisplayAreaOrganizer rootTDAOrganizer, + ShellExecutor mainExecutor, DisplayImeController displayImeController, + DisplayInsetsController displayInsetsController, + Transitions transitions, TransactionPool transactionPool, + Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { + mTaskOrganizer = shellTaskOrganizer; + mSyncQueue = syncQueue; + mContext = context; + mRootTDAOrganizer = rootTDAOrganizer; + mMainExecutor = mainExecutor; + mDisplayImeController = displayImeController; + mDisplayInsetsController = displayInsetsController; + mTransitions = transitions; + mTransactionPool = transactionPool; + mUnfoldControllerProvider = unfoldControllerProvider; + mLogger = new SplitscreenEventLogger(); + } + + public SplitScreen asSplitScreen() { + return mImpl; + } + + @Override + public Context getContext() { + return mContext; + } + + @Override + public ShellExecutor getRemoteCallExecutor() { + return mMainExecutor; + } + + public void onOrganizerRegistered() { + if (mStageCoordinator == null) { + // TODO: Multi-display + mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, + mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController, + mDisplayInsetsController, mTransitions, mTransactionPool, mLogger, + mUnfoldControllerProvider); + } + } + + public boolean isSplitScreenVisible() { + return mStageCoordinator.isSplitScreenVisible(); + } + + public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) { + final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId); + if (task == null) { + throw new IllegalArgumentException("Unknown taskId" + taskId); + } + return moveToSideStage(task, sideStagePosition); + } + + public boolean moveToSideStage(ActivityManager.RunningTaskInfo task, + @SplitPosition int sideStagePosition) { + return mStageCoordinator.moveToSideStage(task, sideStagePosition); + } + + public boolean removeFromSideStage(int taskId) { + return mStageCoordinator.removeFromSideStage(taskId); + } + + public void setSideStageOutline(boolean enable) { + mStageCoordinator.setSideStageOutline(enable); + } + + public void setSideStagePosition(@SplitPosition int sideStagePosition) { + mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */); + } + + public void setSideStageVisibility(boolean visible) { + mStageCoordinator.setSideStageVisibility(visible); + } + + public void enterSplitScreen(int taskId, boolean leftOrTop) { + moveToSideStage(taskId, + leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT); + } + + public void exitSplitScreen(int toTopTaskId, int exitReason) { + mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason); + } + + public void onKeyguardOccludedChanged(boolean occluded) { + mStageCoordinator.onKeyguardOccludedChanged(occluded); + } + + public void onKeyguardVisibilityChanged(boolean showing) { + mStageCoordinator.onKeyguardVisibilityChanged(showing); + } + + public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { + mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide); + } + + public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) { + mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds); + } + + public void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) { + mStageCoordinator.registerSplitScreenListener(listener); + } + + public void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) { + mStageCoordinator.unregisterSplitScreenListener(listener); + } + + public void startTask(int taskId, @SplitScreen.StageType int stage, + @SplitPosition int position, @Nullable Bundle options) { + options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */); + + try { + ActivityTaskManager.getService().startActivityFromRecents(taskId, options); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to launch task", e); + } + } + + public void startShortcut(String packageName, String shortcutId, + @SplitScreen.StageType int stage, @SplitPosition int position, + @Nullable Bundle options, UserHandle user) { + options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */); + + try { + LauncherApps launcherApps = + mContext.getSystemService(LauncherApps.class); + launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */, + options, user); + } catch (ActivityNotFoundException e) { + Slog.e(TAG, "Failed to launch shortcut", e); + } + } + + public void startIntent(PendingIntent intent, Intent fillInIntent, + @SplitScreen.StageType int stage, @SplitPosition int position, + @Nullable Bundle options) { + if (!Transitions.ENABLE_SHELL_TRANSITIONS) { + startIntentLegacy(intent, fillInIntent, stage, position, options); + return; + } + mStageCoordinator.startIntent(intent, fillInIntent, stage, position, options, + null /* remote */); + } + + private void startIntentLegacy(PendingIntent intent, Intent fillInIntent, + @SplitScreen.StageType int stage, @SplitPosition int position, + @Nullable Bundle options) { + LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() { + @Override + public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, + IRemoteAnimationFinishedCallback finishedCallback, + SurfaceControl.Transaction t) { + mStageCoordinator.updateSurfaceBounds(null /* layout */, t); + + if (apps != null) { + for (int i = 0; i < apps.length; ++i) { + if (apps[i].mode == MODE_OPENING) { + t.show(apps[i].leash); + } + } + } + + t.apply(); + if (finishedCallback != null) { + try { + finishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + Slog.e(TAG, "Error finishing legacy transition: ", e); + } + } + } + }; + WindowContainerTransaction wct = new WindowContainerTransaction(); + options = mStageCoordinator.resolveStartStage(stage, position, options, wct); + wct.sendPendingIntent(intent, fillInIntent, options); + mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); + } + + RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) { + if (!isSplitScreenVisible()) return null; + final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession()) + .setContainerLayer() + .setName("RecentsAnimationSplitTasks") + .setHidden(false) + .setCallsite("SplitScreenController#onGoingtoRecentsLegacy"); + mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder); + SurfaceControl sc = builder.build(); + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + + // Ensure that we order these in the parent in the right z-order as their previous order + Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex); + int layer = 1; + for (RemoteAnimationTarget appTarget : apps) { + transaction.reparent(appTarget.leash, sc); + transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left, + appTarget.screenSpaceBounds.top); + transaction.setLayer(appTarget.leash, layer++); + } + transaction.apply(); + transaction.close(); + return new RemoteAnimationTarget[]{ + mStageCoordinator.getDividerBarLegacyTarget(), + mStageCoordinator.getOutlineLegacyTarget()}; + } + + /** + * Sets drag info to be logged when splitscreen is entered. + */ + public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) { + mStageCoordinator.logOnDroppedToSplit(position, dragSessionId); + } + + public void dump(@NonNull PrintWriter pw, String prefix) { + pw.println(prefix + TAG); + if (mStageCoordinator != null) { + mStageCoordinator.dump(pw, prefix); + } + } + + /** + * The interface for calls from outside the Shell, within the host process. + */ + @ExternalThread + private class SplitScreenImpl implements SplitScreen { + private ISplitScreenImpl mISplitScreen; + private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>(); + private final SplitScreenListener mListener = new SplitScreenListener() { + @Override + public void onStagePositionChanged(int stage, int position) { + for (int i = 0; i < mExecutors.size(); i++) { + final int index = i; + mExecutors.valueAt(index).execute(() -> { + mExecutors.keyAt(index).onStagePositionChanged(stage, position); + }); + } + } + + @Override + public void onTaskStageChanged(int taskId, int stage, boolean visible) { + for (int i = 0; i < mExecutors.size(); i++) { + final int index = i; + mExecutors.valueAt(index).execute(() -> { + mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible); + }); + } + } + + @Override + public void onSplitVisibilityChanged(boolean visible) { + for (int i = 0; i < mExecutors.size(); i++) { + final int index = i; + mExecutors.valueAt(index).execute(() -> { + mExecutors.keyAt(index).onSplitVisibilityChanged(visible); + }); + } + } + }; + + @Override + public ISplitScreen createExternalInterface() { + if (mISplitScreen != null) { + mISplitScreen.invalidate(); + } + mISplitScreen = new ISplitScreenImpl(SplitScreenController.this); + return mISplitScreen; + } + + @Override + public void onKeyguardOccludedChanged(boolean occluded) { + mMainExecutor.execute(() -> { + SplitScreenController.this.onKeyguardOccludedChanged(occluded); + }); + } + + @Override + public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) { + if (mExecutors.containsKey(listener)) return; + + mMainExecutor.execute(() -> { + if (mExecutors.size() == 0) { + SplitScreenController.this.registerSplitScreenListener(mListener); + } + + mExecutors.put(listener, executor); + }); + + executor.execute(() -> { + mStageCoordinator.sendStatusToListener(listener); + }); + } + + @Override + public void unregisterSplitScreenListener(SplitScreenListener listener) { + mMainExecutor.execute(() -> { + mExecutors.remove(listener); + + if (mExecutors.size() == 0) { + SplitScreenController.this.unregisterSplitScreenListener(mListener); + } + }); + } + + @Override + public void onKeyguardVisibilityChanged(boolean showing) { + mMainExecutor.execute(() -> { + SplitScreenController.this.onKeyguardVisibilityChanged(showing); + }); + } + } + + /** + * The interface for calls from outside the host process. + */ + @BinderThread + private static class ISplitScreenImpl extends ISplitScreen.Stub { + private SplitScreenController mController; + private ISplitScreenListener mListener; + private final SplitScreen.SplitScreenListener mSplitScreenListener = + new SplitScreen.SplitScreenListener() { + @Override + public void onStagePositionChanged(int stage, int position) { + try { + if (mListener != null) { + mListener.onStagePositionChanged(stage, position); + } + } catch (RemoteException e) { + Slog.e(TAG, "onStagePositionChanged", e); + } + } + + @Override + public void onTaskStageChanged(int taskId, int stage, boolean visible) { + try { + if (mListener != null) { + mListener.onTaskStageChanged(taskId, stage, visible); + } + } catch (RemoteException e) { + Slog.e(TAG, "onTaskStageChanged", e); + } + } + }; + private final IBinder.DeathRecipient mListenerDeathRecipient = + new IBinder.DeathRecipient() { + @Override + @BinderThread + public void binderDied() { + final SplitScreenController controller = mController; + controller.getRemoteCallExecutor().execute(() -> { + mListener = null; + controller.unregisterSplitScreenListener(mSplitScreenListener); + }); + } + }; + + public ISplitScreenImpl(SplitScreenController controller) { + mController = controller; + } + + /** + * Invalidates this instance, preventing future calls from updating the controller. + */ + void invalidate() { + mController = null; + } + + @Override + public void registerSplitScreenListener(ISplitScreenListener listener) { + executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener", + (controller) -> { + if (mListener != null) { + mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } + if (listener != null) { + try { + listener.asBinder().linkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to death"); + return; + } + } + mListener = listener; + controller.registerSplitScreenListener(mSplitScreenListener); + }); + } + + @Override + public void unregisterSplitScreenListener(ISplitScreenListener listener) { + executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener", + (controller) -> { + if (mListener != null) { + mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, + 0 /* flags */); + } + mListener = null; + controller.unregisterSplitScreenListener(mSplitScreenListener); + }); + } + + @Override + public void exitSplitScreen(int toTopTaskId) { + executeRemoteCallWithTaskPermission(mController, "exitSplitScreen", + (controller) -> { + controller.exitSplitScreen(toTopTaskId, + FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT); + }); + } + + @Override + public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { + executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide", + (controller) -> { + controller.exitSplitScreenOnHide(exitSplitScreenOnHide); + }); + } + + @Override + public void setSideStageVisibility(boolean visible) { + executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility", + (controller) -> { + controller.setSideStageVisibility(visible); + }); + } + + @Override + public void removeFromSideStage(int taskId) { + executeRemoteCallWithTaskPermission(mController, "removeFromSideStage", + (controller) -> { + controller.removeFromSideStage(taskId); + }); + } + + @Override + public void startTask(int taskId, int stage, int position, @Nullable Bundle options) { + executeRemoteCallWithTaskPermission(mController, "startTask", + (controller) -> { + controller.startTask(taskId, stage, position, options); + }); + } + + @Override + public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions, + int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, + RemoteAnimationAdapter adapter) { + executeRemoteCallWithTaskPermission(mController, "startTasks", + (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition( + mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition, + adapter)); + } + + @Override + public void startTasks(int mainTaskId, @Nullable Bundle mainOptions, + int sideTaskId, @Nullable Bundle sideOptions, + @SplitPosition int sidePosition, + @Nullable RemoteTransition remoteTransition) { + executeRemoteCallWithTaskPermission(mController, "startTasks", + (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions, + sideTaskId, sideOptions, sidePosition, remoteTransition)); + } + + @Override + public void startShortcut(String packageName, String shortcutId, int stage, int position, + @Nullable Bundle options, UserHandle user) { + executeRemoteCallWithTaskPermission(mController, "startShortcut", + (controller) -> { + controller.startShortcut(packageName, shortcutId, stage, position, + options, user); + }); + } + + @Override + public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position, + @Nullable Bundle options) { + executeRemoteCallWithTaskPermission(mController, "startIntent", + (controller) -> { + controller.startIntent(intent, fillInIntent, stage, position, options); + }); + } + + @Override + public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, + RemoteAnimationTarget[] apps) { + final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null}; + executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy", + (controller) -> out[0] = controller.onGoingToRecentsLegacy(cancel, apps), + true /* blocking */); + return out[0]; + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java new file mode 100644 index 000000000000..af9a5aa501e8 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java @@ -0,0 +1,298 @@ +/* + * 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.stagesplit; + +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM; + +import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP; +import static com.android.wm.shell.transition.Transitions.isOpeningType; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.RemoteTransition; +import android.window.TransitionInfo; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.transition.OneShotRemoteHandler; +import com.android.wm.shell.transition.Transitions; + +import java.util.ArrayList; + +/** Manages transition animations for split-screen. */ +class SplitScreenTransitions { + private static final String TAG = "SplitScreenTransitions"; + + /** Flag applied to a transition change to identify it as a divider bar for animation. */ + public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM; + + private final TransactionPool mTransactionPool; + private final Transitions mTransitions; + private final Runnable mOnFinish; + + IBinder mPendingDismiss = null; + IBinder mPendingEnter = null; + + private IBinder mAnimatingTransition = null; + private OneShotRemoteHandler mRemoteHandler = null; + + private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> { + if (wct != null || wctCB != null) { + throw new UnsupportedOperationException("finish transactions not supported yet."); + } + onFinish(); + }; + + /** Keeps track of currently running animations */ + private final ArrayList<Animator> mAnimations = new ArrayList<>(); + + private Transitions.TransitionFinishCallback mFinishCallback = null; + private SurfaceControl.Transaction mFinishTransaction; + + SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions, + @NonNull Runnable onFinishCallback) { + mTransactionPool = pool; + mTransitions = transitions; + mOnFinish = onFinishCallback; + } + + void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) { + mFinishCallback = finishCallback; + mAnimatingTransition = transition; + if (mRemoteHandler != null) { + mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction, + mRemoteFinishCB); + mRemoteHandler = null; + return; + } + playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot); + } + + private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot, + @NonNull WindowContainerToken sideRoot) { + mFinishTransaction = mTransactionPool.acquire(); + + // Play some place-holder fade animations + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final SurfaceControl leash = change.getLeash(); + final int mode = info.getChanges().get(i).getMode(); + + if (mode == TRANSIT_CHANGE) { + if (change.getParent() != null) { + // This is probably reparented, so we want the parent to be immediately visible + final TransitionInfo.Change parentChange = info.getChange(change.getParent()); + t.show(parentChange.getLeash()); + t.setAlpha(parentChange.getLeash(), 1.f); + // and then animate this layer outside the parent (since, for example, this is + // the home task animating from fullscreen to part-screen). + t.reparent(leash, info.getRootLeash()); + t.setLayer(leash, info.getChanges().size() - i); + // build the finish reparent/reposition + mFinishTransaction.reparent(leash, parentChange.getLeash()); + mFinishTransaction.setPosition(leash, + change.getEndRelOffset().x, change.getEndRelOffset().y); + } + // TODO(shell-transitions): screenshot here + final Rect startBounds = new Rect(change.getStartAbsBounds()); + if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) { + // Dismissing split via snap which means the still-visible task has been + // dragged to its end position at animation start so reflect that here. + startBounds.offsetTo(change.getEndAbsBounds().left, + change.getEndAbsBounds().top); + } + final Rect endBounds = new Rect(change.getEndAbsBounds()); + startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); + endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); + startExampleResizeAnimation(leash, startBounds, endBounds); + } + if (change.getParent() != null) { + continue; + } + + if (transition == mPendingEnter && (mainRoot.equals(change.getContainer()) + || sideRoot.equals(change.getContainer()))) { + t.setWindowCrop(leash, change.getStartAbsBounds().width(), + change.getStartAbsBounds().height()); + } + boolean isOpening = isOpeningType(info.getType()); + if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { + // fade in + startExampleAnimation(leash, true /* show */); + } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) { + // fade out + if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) { + // Dismissing via snap-to-top/bottom means that the dismissed task is already + // not-visible (usually cropped to oblivion) so immediately set its alpha to 0 + // and don't animate it so it doesn't pop-in when reparented. + t.setAlpha(leash, 0.f); + } else { + startExampleAnimation(leash, false /* show */); + } + } + } + t.apply(); + onFinish(); + } + + /** Starts a transition to enter split with a remote transition animator. */ + IBinder startEnterTransition(@WindowManager.TransitionType int transitType, + @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, + @NonNull Transitions.TransitionHandler handler) { + if (remoteTransition != null) { + // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) + mRemoteHandler = new OneShotRemoteHandler( + mTransitions.getMainExecutor(), remoteTransition); + } + final IBinder transition = mTransitions.startTransition(transitType, wct, handler); + mPendingEnter = transition; + if (mRemoteHandler != null) { + mRemoteHandler.setTransition(transition); + } + return transition; + } + + /** Starts a transition for dismissing split after dragging the divider to a screen edge */ + IBinder startSnapToDismiss(@NonNull WindowContainerTransaction wct, + @NonNull Transitions.TransitionHandler handler) { + final IBinder transition = mTransitions.startTransition( + TRANSIT_SPLIT_DISMISS_SNAP, wct, handler); + mPendingDismiss = transition; + return transition; + } + + void onFinish() { + if (!mAnimations.isEmpty()) return; + mOnFinish.run(); + if (mFinishTransaction != null) { + mFinishTransaction.apply(); + mTransactionPool.release(mFinishTransaction); + mFinishTransaction = null; + } + mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + mFinishCallback = null; + if (mAnimatingTransition == mPendingEnter) { + mPendingEnter = null; + } + if (mAnimatingTransition == mPendingDismiss) { + mPendingDismiss = null; + } + mAnimatingTransition = null; + } + + // TODO(shell-transitions): real animations + private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) { + final float end = show ? 1.f : 0.f; + final float start = 1.f - end; + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + final ValueAnimator va = ValueAnimator.ofFloat(start, end); + va.setDuration(500); + va.addUpdateListener(animation -> { + float fraction = animation.getAnimatedFraction(); + transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction); + transaction.apply(); + }); + final Runnable finisher = () -> { + transaction.setAlpha(leash, end); + transaction.apply(); + mTransactionPool.release(transaction); + mTransitions.getMainExecutor().execute(() -> { + mAnimations.remove(va); + onFinish(); + }); + }; + va.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { } + + @Override + public void onAnimationEnd(Animator animation) { + finisher.run(); + } + + @Override + public void onAnimationCancel(Animator animation) { + finisher.run(); + } + + @Override + public void onAnimationRepeat(Animator animation) { } + }); + mAnimations.add(va); + mTransitions.getAnimExecutor().execute(va::start); + } + + // TODO(shell-transitions): real animations + private void startExampleResizeAnimation(@NonNull SurfaceControl leash, + @NonNull Rect startBounds, @NonNull Rect endBounds) { + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f); + va.setDuration(500); + va.addUpdateListener(animation -> { + float fraction = animation.getAnimatedFraction(); + transaction.setWindowCrop(leash, + (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction), + (int) (startBounds.height() * (1.f - fraction) + + endBounds.height() * fraction)); + transaction.setPosition(leash, + startBounds.left * (1.f - fraction) + endBounds.left * fraction, + startBounds.top * (1.f - fraction) + endBounds.top * fraction); + transaction.apply(); + }); + final Runnable finisher = () -> { + transaction.setWindowCrop(leash, 0, 0); + transaction.setPosition(leash, endBounds.left, endBounds.top); + transaction.apply(); + mTransactionPool.release(transaction); + mTransitions.getMainExecutor().execute(() -> { + mAnimations.remove(va); + onFinish(); + }); + }; + va.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + finisher.run(); + } + + @Override + public void onAnimationCancel(Animator animation) { + finisher.run(); + } + }); + mAnimations.add(va); + mTransitions.getAnimExecutor().execute(va::start); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java new file mode 100644 index 000000000000..aab7902232bf --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java @@ -0,0 +1,324 @@ +/* + * 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.stagesplit; + +import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW; +import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED; + +import com.android.internal.logging.InstanceId; +import com.android.internal.logging.InstanceIdSequence; +import com.android.internal.util.FrameworkStatsLog; +import com.android.wm.shell.common.split.SplitLayout.SplitPosition; + +/** + * Helper class that to log Drag & Drop UIEvents for a single session, see also go/uievent + */ +public class SplitscreenEventLogger { + + // Used to generate instance ids for this drag if one is not provided + private final InstanceIdSequence mIdSequence; + + // The instance id for the current splitscreen session (from start to end) + private InstanceId mLoggerSessionId; + + // Drag info + private @SplitPosition int mDragEnterPosition; + private InstanceId mDragEnterSessionId; + + // For deduping async events + private int mLastMainStagePosition = -1; + private int mLastMainStageUid = -1; + private int mLastSideStagePosition = -1; + private int mLastSideStageUid = -1; + private float mLastSplitRatio = -1f; + + public SplitscreenEventLogger() { + mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE); + } + + /** + * Return whether a splitscreen session has started. + */ + public boolean hasStartedSession() { + return mLoggerSessionId != null; + } + + /** + * May be called before logEnter() to indicate that the session was started from a drag. + */ + public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) { + mDragEnterPosition = position; + mDragEnterSessionId = dragSessionId; + } + + /** + * Logs when the user enters splitscreen. + */ + public void logEnter(float splitRatio, + @SplitPosition int mainStagePosition, int mainStageUid, + @SplitPosition int sideStagePosition, int sideStageUid, + boolean isLandscape) { + mLoggerSessionId = mIdSequence.newInstanceId(); + int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED + ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape) + : SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW; + updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape), + mainStageUid); + updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape), + sideStageUid); + updateSplitRatioState(splitRatio); + FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED, + FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__ENTER, + enterReason, + 0 /* exitReason */, + splitRatio, + mLastMainStagePosition, + mLastMainStageUid, + mLastSideStagePosition, + mLastSideStageUid, + mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0, + mLoggerSessionId.getId()); + } + + /** + * Logs when the user exits splitscreen. Only one of the main or side stages should be + * specified to indicate which position was focused as a part of exiting (both can be unset). + */ + public void logExit(int exitReason, @SplitPosition int mainStagePosition, int mainStageUid, + @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) { + if (mLoggerSessionId == null) { + // Ignore changes until we've started logging the session + return; + } + if ((mainStagePosition != SPLIT_POSITION_UNDEFINED + && sideStagePosition != SPLIT_POSITION_UNDEFINED) + || (mainStageUid != 0 && sideStageUid != 0)) { + throw new IllegalArgumentException("Only main or side stage should be set"); + } + FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED, + FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__EXIT, + 0 /* enterReason */, + exitReason, + 0f /* splitRatio */, + getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape), + mainStageUid, + getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape), + sideStageUid, + 0 /* dragInstanceId */, + mLoggerSessionId.getId()); + + // Reset states + mLoggerSessionId = null; + mDragEnterPosition = SPLIT_POSITION_UNDEFINED; + mDragEnterSessionId = null; + mLastMainStagePosition = -1; + mLastMainStageUid = -1; + mLastSideStagePosition = -1; + mLastSideStageUid = -1; + } + + /** + * Logs when an app in the main stage changes. + */ + public void logMainStageAppChange(@SplitPosition int mainStagePosition, int mainStageUid, + boolean isLandscape) { + if (mLoggerSessionId == null) { + // Ignore changes until we've started logging the session + return; + } + if (!updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, + isLandscape), mainStageUid)) { + // Ignore if there are no user perceived changes + return; + } + + FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED, + FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE, + 0 /* enterReason */, + 0 /* exitReason */, + 0f /* splitRatio */, + mLastMainStagePosition, + mLastMainStageUid, + 0 /* sideStagePosition */, + 0 /* sideStageUid */, + 0 /* dragInstanceId */, + mLoggerSessionId.getId()); + } + + /** + * Logs when an app in the side stage changes. + */ + public void logSideStageAppChange(@SplitPosition int sideStagePosition, int sideStageUid, + boolean isLandscape) { + if (mLoggerSessionId == null) { + // Ignore changes until we've started logging the session + return; + } + if (!updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, + isLandscape), sideStageUid)) { + // Ignore if there are no user perceived changes + return; + } + + FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED, + FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE, + 0 /* enterReason */, + 0 /* exitReason */, + 0f /* splitRatio */, + 0 /* mainStagePosition */, + 0 /* mainStageUid */, + mLastSideStagePosition, + mLastSideStageUid, + 0 /* dragInstanceId */, + mLoggerSessionId.getId()); + } + + /** + * Logs when the splitscreen ratio changes. + */ + public void logResize(float splitRatio) { + if (mLoggerSessionId == null) { + // Ignore changes until we've started logging the session + return; + } + if (splitRatio <= 0f || splitRatio >= 1f) { + // Don't bother reporting resizes that end up dismissing the split, that will be logged + // via the exit event + return; + } + if (!updateSplitRatioState(splitRatio)) { + // Ignore if there are no user perceived changes + return; + } + FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED, + FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__RESIZE, + 0 /* enterReason */, + 0 /* exitReason */, + mLastSplitRatio, + 0 /* mainStagePosition */, 0 /* mainStageUid */, + 0 /* sideStagePosition */, 0 /* sideStageUid */, + 0 /* dragInstanceId */, + mLoggerSessionId.getId()); + } + + /** + * Logs when the apps in splitscreen are swapped. + */ + public void logSwap(@SplitPosition int mainStagePosition, int mainStageUid, + @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) { + if (mLoggerSessionId == null) { + // Ignore changes until we've started logging the session + return; + } + + updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape), + mainStageUid); + updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape), + sideStageUid); + FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED, + FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__SWAP, + 0 /* enterReason */, + 0 /* exitReason */, + 0f /* splitRatio */, + mLastMainStagePosition, + mLastMainStageUid, + mLastSideStagePosition, + mLastSideStageUid, + 0 /* dragInstanceId */, + mLoggerSessionId.getId()); + } + + private boolean updateMainStageState(int mainStagePosition, int mainStageUid) { + boolean changed = (mLastMainStagePosition != mainStagePosition) + || (mLastMainStageUid != mainStageUid); + if (!changed) { + return false; + } + + mLastMainStagePosition = mainStagePosition; + mLastMainStageUid = mainStageUid; + return true; + } + + private boolean updateSideStageState(int sideStagePosition, int sideStageUid) { + boolean changed = (mLastSideStagePosition != sideStagePosition) + || (mLastSideStageUid != sideStageUid); + if (!changed) { + return false; + } + + mLastSideStagePosition = sideStagePosition; + mLastSideStageUid = sideStageUid; + return true; + } + + private boolean updateSplitRatioState(float splitRatio) { + boolean changed = Float.compare(mLastSplitRatio, splitRatio) != 0; + if (!changed) { + return false; + } + + mLastSplitRatio = splitRatio; + return true; + } + + public int getDragEnterReasonFromSplitPosition(@SplitPosition int position, + boolean isLandscape) { + if (isLandscape) { + return position == SPLIT_POSITION_TOP_OR_LEFT + ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_LEFT + : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_RIGHT; + } else { + return position == SPLIT_POSITION_TOP_OR_LEFT + ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_TOP + : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_BOTTOM; + } + } + + private int getMainStagePositionFromSplitPosition(@SplitPosition int position, + boolean isLandscape) { + if (position == SPLIT_POSITION_UNDEFINED) { + return 0; + } + if (isLandscape) { + return position == SPLIT_POSITION_TOP_OR_LEFT + ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__LEFT + : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__RIGHT; + } else { + return position == SPLIT_POSITION_TOP_OR_LEFT + ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__TOP + : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__BOTTOM; + } + } + + private int getSideStagePositionFromSplitPosition(@SplitPosition int position, + boolean isLandscape) { + if (position == SPLIT_POSITION_UNDEFINED) { + return 0; + } + if (isLandscape) { + return position == SPLIT_POSITION_TOP_OR_LEFT + ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__LEFT + : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__RIGHT; + } else { + return position == SPLIT_POSITION_TOP_OR_LEFT + ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__TOP + : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__BOTTOM; + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java new file mode 100644 index 000000000000..574e379921b1 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java @@ -0,0 +1,1330 @@ +/* + * 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.wm.shell.stagesplit; + +import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.view.WindowManager.transitTypeToString; +import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER; + +import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW; +import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED; +import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED; +import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER; +import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME; +import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP; +import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_MAIN; +import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_SIDE; +import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_UNDEFINED; +import static com.android.wm.shell.stagesplit.SplitScreen.stageTypeToString; +import static com.android.wm.shell.stagesplit.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR; +import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; +import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP; +import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE; +import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN; +import static com.android.wm.shell.transition.Transitions.isClosingType; +import static com.android.wm.shell.transition.Transitions.isOpeningType; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.ActivityTaskManager; +import android.app.PendingIntent; +import android.app.WindowConfiguration; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.hardware.devicestate.DeviceStateManager; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; +import android.view.RemoteAnimationAdapter; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.view.SurfaceSession; +import android.view.WindowManager; +import android.window.DisplayAreaInfo; +import android.window.RemoteTransition; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.InstanceId; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayInsetsController; +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; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import javax.inject.Provider; + +/** + * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and + * {@link SideStage} stages. + * Some high-level rules: + * - The {@link StageCoordinator} is only considered active if the {@link SideStage} contains at + * least one child task. + * - The {@link MainStage} should only have children if the coordinator is active. + * - The {@link SplitLayout} divider is only visible if both the {@link MainStage} + * and {@link SideStage} are visible. + * - The {@link MainStage} configuration is fullscreen when the {@link SideStage} isn't visible. + * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and + * {@link #onStageHasChildrenChanged(StageListenerImpl).} + */ +class StageCoordinator implements SplitLayout.SplitLayoutHandler, + RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler { + + private static final String TAG = StageCoordinator.class.getSimpleName(); + + /** internal value for mDismissTop that represents no dismiss */ + private static final int NO_DISMISS = -2; + + private final SurfaceSession mSurfaceSession = new SurfaceSession(); + + private final MainStage mMainStage; + private final StageListenerImpl mMainStageListener = new StageListenerImpl(); + private final StageTaskUnfoldController mMainUnfoldController; + private final SideStage mSideStage; + private final StageListenerImpl mSideStageListener = new StageListenerImpl(); + private final StageTaskUnfoldController mSideUnfoldController; + @SplitPosition + private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT; + + private final int mDisplayId; + private SplitLayout mSplitLayout; + private boolean mDividerVisible; + private final SyncTransactionQueue mSyncQueue; + private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer; + private final ShellTaskOrganizer mTaskOrganizer; + private DisplayAreaInfo mDisplayAreaInfo; + private final Context mContext; + private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>(); + private final DisplayImeController mDisplayImeController; + private final DisplayInsetsController mDisplayInsetsController; + private final SplitScreenTransitions mSplitTransitions; + private final SplitscreenEventLogger mLogger; + private boolean mExitSplitScreenOnHide; + private boolean mKeyguardOccluded; + + // TODO(b/187041611): remove this flag after totally deprecated legacy split + /** Whether the device is supporting legacy split or not. */ + private boolean mUseLegacySplit; + + @SplitScreen.StageType private int mDismissTop = NO_DISMISS; + + /** The target stage to dismiss to when unlock after folded. */ + @SplitScreen.StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; + + private final Runnable mOnTransitionAnimationComplete = () -> { + // If still playing, let it finish. + if (!isSplitScreenVisible()) { + // Update divider state after animation so that it is still around and positioned + // properly for the animation itself. + setDividerVisibility(false); + mSplitLayout.resetDividerPosition(); + } + 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, + DisplayInsetsController displayInsetsController, Transitions transitions, + TransactionPool transactionPool, SplitscreenEventLogger logger, + Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { + mContext = context; + mDisplayId = displayId; + mSyncQueue = syncQueue; + mRootTDAOrganizer = rootTDAOrganizer; + mTaskOrganizer = taskOrganizer; + mLogger = logger; + mMainUnfoldController = unfoldControllerProvider.get().orElse(null); + mSideUnfoldController = unfoldControllerProvider.get().orElse(null); + + mMainStage = new MainStage( + mTaskOrganizer, + mDisplayId, + mMainStageListener, + mSyncQueue, + mSurfaceSession, + mMainUnfoldController); + mSideStage = new SideStage( + mContext, + mTaskOrganizer, + mDisplayId, + mSideStageListener, + mSyncQueue, + mSurfaceSession, + mSideUnfoldController); + mDisplayImeController = displayImeController; + mDisplayInsetsController = displayInsetsController; + mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage); + mRootTDAOrganizer.registerListener(displayId, this); + final DeviceStateManager deviceStateManager = + mContext.getSystemService(DeviceStateManager.class); + deviceStateManager.registerCallback(taskOrganizer.getExecutor(), + new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged)); + mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, + mOnTransitionAnimationComplete); + transitions.addHandler(this); + } + + @VisibleForTesting + StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, + RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer, + MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController, + DisplayInsetsController displayInsetsController, SplitLayout splitLayout, + Transitions transitions, TransactionPool transactionPool, + SplitscreenEventLogger logger, + Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { + mContext = context; + mDisplayId = displayId; + mSyncQueue = syncQueue; + mRootTDAOrganizer = rootTDAOrganizer; + mTaskOrganizer = taskOrganizer; + mMainStage = mainStage; + mSideStage = sideStage; + mDisplayImeController = displayImeController; + mDisplayInsetsController = displayInsetsController; + mRootTDAOrganizer.registerListener(displayId, this); + mSplitLayout = splitLayout; + mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, + mOnTransitionAnimationComplete); + mMainUnfoldController = unfoldControllerProvider.get().orElse(null); + mSideUnfoldController = unfoldControllerProvider.get().orElse(null); + mLogger = logger; + transitions.addHandler(this); + } + + @VisibleForTesting + SplitScreenTransitions getSplitTransitions() { + return mSplitTransitions; + } + + boolean isSplitScreenVisible() { + return mSideStageListener.mVisible && mMainStageListener.mVisible; + } + + boolean moveToSideStage(ActivityManager.RunningTaskInfo task, + @SplitPosition int sideStagePosition) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + setSideStagePosition(sideStagePosition, wct); + mMainStage.activate(getMainStageBounds(), wct); + mSideStage.addTask(task, getSideStageBounds(), wct); + mSyncQueue.queue(wct); + mSyncQueue.runInSync(t -> updateSurfaceBounds(null /* layout */, t)); + return true; + } + + boolean removeFromSideStage(int taskId) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + + /** + * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the + * {@link SideStage} no longer has children. + */ + final boolean result = mSideStage.removeTask(taskId, + mMainStage.isActive() ? mMainStage.mRootTaskInfo.token : null, + wct); + mTaskOrganizer.applyTransaction(wct); + return result; + } + + void setSideStageOutline(boolean enable) { + mSideStage.enableOutline(enable); + } + + /** Starts 2 tasks in one transition. */ + void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, + @Nullable Bundle sideOptions, @SplitPosition int sidePosition, + @Nullable RemoteTransition remoteTransition) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + mainOptions = mainOptions != null ? mainOptions : new Bundle(); + sideOptions = sideOptions != null ? sideOptions : new Bundle(); + setSideStagePosition(sidePosition, wct); + + // Build a request WCT that will launch both apps such that task 0 is on the main stage + // while task 1 is on the side stage. + mMainStage.activate(getMainStageBounds(), wct); + mSideStage.setBounds(getSideStageBounds(), wct); + + // Make sure the launch options will put tasks in the corresponding split roots + addActivityOptions(mainOptions, mMainStage); + addActivityOptions(sideOptions, mSideStage); + + // Add task launch requests + wct.startTask(mainTaskId, mainOptions); + wct.startTask(sideTaskId, sideOptions); + + mSplitTransitions.startEnterTransition( + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this); + } + + /** Starts 2 tasks in one legacy transition. */ + void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions, + int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, + RemoteAnimationAdapter adapter) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + // Need to add another wrapper here in shell so that we can inject the divider bar + // and also manage the process elevation via setRunningRemote + IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { + @Override + public void onAnimationStart(@WindowManager.TransitionOldType int transit, + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + final IRemoteAnimationFinishedCallback finishedCallback) { + RemoteAnimationTarget[] augmentedNonApps = + new RemoteAnimationTarget[nonApps.length + 1]; + for (int i = 0; i < nonApps.length; ++i) { + augmentedNonApps[i] = nonApps[i]; + } + augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget(); + try { + ActivityTaskManager.getService().setRunningRemoteTransitionDelegate( + adapter.getCallingApplication()); + adapter.getRunner().onAnimationStart(transit, apps, wallpapers, nonApps, + finishedCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Error starting remote animation", e); + } + } + + @Override + public void onAnimationCancelled() { + try { + adapter.getRunner().onAnimationCancelled(); + } catch (RemoteException e) { + Slog.e(TAG, "Error starting remote animation", e); + } + } + }; + RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter( + wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay()); + + if (mainOptions == null) { + mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle(); + } else { + ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions); + mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter)); + } + + sideOptions = sideOptions != null ? sideOptions : new Bundle(); + setSideStagePosition(sidePosition, wct); + + // Build a request WCT that will launch both apps such that task 0 is on the main stage + // while task 1 is on the side stage. + mMainStage.activate(getMainStageBounds(), wct); + mSideStage.setBounds(getSideStageBounds(), wct); + + // Make sure the launch options will put tasks in the corresponding split roots + addActivityOptions(mainOptions, mMainStage); + addActivityOptions(sideOptions, mSideStage); + + // Add task launch requests + wct.startTask(mainTaskId, mainOptions); + wct.startTask(sideTaskId, sideOptions); + + // Using legacy transitions, so we can't use blast sync since it conflicts. + mTaskOrganizer.applyTransaction(wct); + } + + public void startIntent(PendingIntent intent, Intent fillInIntent, + @SplitScreen.StageType int stage, @SplitPosition int position, + @androidx.annotation.Nullable Bundle options, + @Nullable RemoteTransition remoteTransition) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + options = resolveStartStage(stage, position, options, wct); + wct.sendPendingIntent(intent, fillInIntent, options); + mSplitTransitions.startEnterTransition( + TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this); + } + + Bundle resolveStartStage(@SplitScreen.StageType int stage, + @SplitPosition int position, @androidx.annotation.Nullable Bundle options, + @androidx.annotation.Nullable WindowContainerTransaction wct) { + switch (stage) { + case STAGE_TYPE_UNDEFINED: { + // Use the stage of the specified position is valid. + if (position != SPLIT_POSITION_UNDEFINED) { + if (position == getSideStagePosition()) { + options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct); + } else { + options = resolveStartStage(STAGE_TYPE_MAIN, position, options, wct); + } + } else { + // Exit split-screen and launch fullscreen since stage wasn't specified. + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); + } + break; + } + case STAGE_TYPE_SIDE: { + if (position != SPLIT_POSITION_UNDEFINED) { + setSideStagePosition(position, wct); + } else { + position = getSideStagePosition(); + } + if (options == null) { + options = new Bundle(); + } + updateActivityOptions(options, position); + break; + } + case STAGE_TYPE_MAIN: { + if (position != SPLIT_POSITION_UNDEFINED) { + // Set the side stage opposite of what we want to the main stage. + final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT + ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT; + setSideStagePosition(sideStagePosition, wct); + } else { + position = getMainStagePosition(); + } + if (options == null) { + options = new Bundle(); + } + updateActivityOptions(options, position); + break; + } + default: + throw new IllegalArgumentException("Unknown stage=" + stage); + } + + return options; + } + + @SplitPosition + int getSideStagePosition() { + return mSideStagePosition; + } + + @SplitPosition + int getMainStagePosition() { + return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT + ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT; + } + + void setSideStagePosition(@SplitPosition int sideStagePosition, + @Nullable WindowContainerTransaction wct) { + setSideStagePosition(sideStagePosition, true /* updateBounds */, wct); + } + + private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds, + @Nullable WindowContainerTransaction wct) { + if (mSideStagePosition == sideStagePosition) return; + mSideStagePosition = sideStagePosition; + sendOnStagePositionChanged(); + + if (mSideStageListener.mVisible && updateBounds) { + if (wct == null) { + // onLayoutSizeChanged builds/applies a wct with the contents of updateWindowBounds. + onLayoutSizeChanged(mSplitLayout); + } else { + updateWindowBounds(mSplitLayout, wct); + updateUnfoldBounds(); + } + } + } + + void setSideStageVisibility(boolean visible) { + if (mSideStageListener.mVisible == visible) return; + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + mSideStage.setVisibility(visible, wct); + mTaskOrganizer.applyTransaction(wct); + } + + void onKeyguardOccludedChanged(boolean occluded) { + // Do not exit split directly, because it needs to wait for task info update to determine + // which task should remain on top after split dismissed. + mKeyguardOccluded = occluded; + } + + void onKeyguardVisibilityChanged(boolean showing) { + if (!showing && mMainStage.isActive() + && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) { + exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage, + SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED); + } + } + + void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { + mExitSplitScreenOnHide = exitSplitScreenOnHide; + } + + void exitSplitScreen(int toTopTaskId, int exitReason) { + StageTaskListener childrenToTop = null; + if (mMainStage.containsTask(toTopTaskId)) { + childrenToTop = mMainStage; + } else if (mSideStage.containsTask(toTopTaskId)) { + childrenToTop = mSideStage; + } + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (childrenToTop != null) { + childrenToTop.reorderChild(toTopTaskId, true /* onTop */, wct); + } + applyExitSplitScreen(childrenToTop, wct, exitReason); + } + + private void exitSplitScreen(StageTaskListener childrenToTop, int exitReason) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + applyExitSplitScreen(childrenToTop, wct, exitReason); + } + + private void applyExitSplitScreen( + StageTaskListener childrenToTop, + WindowContainerTransaction wct, int exitReason) { + mSideStage.removeAllTasks(wct, childrenToTop == mSideStage); + mMainStage.deactivate(wct, childrenToTop == mMainStage); + mTaskOrganizer.applyTransaction(wct); + mSyncQueue.runInSync(t -> t + .setWindowCrop(mMainStage.mRootLeash, null) + .setWindowCrop(mSideStage.mRootLeash, null)); + // Hide divider and reset its position. + setDividerVisibility(false); + mSplitLayout.resetDividerPosition(); + mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; + if (childrenToTop != null) { + logExitToStage(exitReason, childrenToTop == mMainStage); + } else { + logExit(exitReason); + } + } + + /** + * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates + * an existing WindowContainerTransaction (rather than applying immediately). This is intended + * to be used when exiting split might be bundled with other window operations. + */ + void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop, + @NonNull WindowContainerTransaction wct) { + mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE); + mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN); + } + + void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) { + outTopOrLeftBounds.set(mSplitLayout.getBounds1()); + outBottomOrRightBounds.set(mSplitLayout.getBounds2()); + } + + private void addActivityOptions(Bundle opts, StageTaskListener stage) { + opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token); + } + + void updateActivityOptions(Bundle opts, @SplitPosition int position) { + addActivityOptions(opts, position == mSideStagePosition ? mSideStage : mMainStage); + } + + void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) { + if (mListeners.contains(listener)) return; + mListeners.add(listener); + sendStatusToListener(listener); + } + + void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) { + mListeners.remove(listener); + } + + void sendStatusToListener(SplitScreen.SplitScreenListener listener) { + listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition()); + listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition()); + listener.onSplitVisibilityChanged(isSplitScreenVisible()); + mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE); + mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN); + } + + private void sendOnStagePositionChanged() { + for (int i = mListeners.size() - 1; i >= 0; --i) { + final SplitScreen.SplitScreenListener l = mListeners.get(i); + l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition()); + l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition()); + } + } + + private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId, + boolean present, boolean visible) { + int stage; + if (present) { + stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; + } else { + // No longer on any stage + stage = STAGE_TYPE_UNDEFINED; + } + if (stage == STAGE_TYPE_MAIN) { + mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(), + mSplitLayout.isLandscape()); + } else { + mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(), + mSplitLayout.isLandscape()); + } + + for (int i = mListeners.size() - 1; i >= 0; --i) { + mListeners.get(i).onTaskStageChanged(taskId, stage, visible); + } + } + + private void sendSplitVisibilityChanged() { + for (int i = mListeners.size() - 1; i >= 0; --i) { + final SplitScreen.SplitScreenListener l = mListeners.get(i); + l.onSplitVisibilityChanged(mDividerVisible); + } + + if (mMainUnfoldController != null && mSideUnfoldController != null) { + mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible); + mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible); + } + } + + private void onStageRootTaskAppeared(StageListenerImpl stageListener) { + if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) { + mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + // Make the stages adjacent to each other so they occlude what's behind them. + wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); + + // Only sets side stage as launch-adjacent-flag-root when the device is not using legacy + // split to prevent new split behavior confusing users. + if (!mUseLegacySplit) { + wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token); + } + + mTaskOrganizer.applyTransaction(wct); + } + } + + private void onStageRootTaskVanished(StageListenerImpl stageListener) { + if (stageListener == mMainStageListener || stageListener == mSideStageListener) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + // Deactivate the main stage if it no longer has a root task. + mMainStage.deactivate(wct); + + if (!mUseLegacySplit) { + wct.clearLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token); + } + + mTaskOrganizer.applyTransaction(wct); + } + } + + private void setDividerVisibility(boolean visible) { + if (mDividerVisible == visible) return; + mDividerVisible = visible; + if (visible) { + mSplitLayout.init(); + updateUnfoldBounds(); + } else { + mSplitLayout.release(); + } + sendSplitVisibilityChanged(); + } + + private void onStageVisibilityChanged(StageListenerImpl stageListener) { + final boolean sideStageVisible = mSideStageListener.mVisible; + final boolean mainStageVisible = mMainStageListener.mVisible; + 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 (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. + || (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) { + exitSplitScreen(null /* childrenToTop */, + SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME); + } + } else if (mKeyguardOccluded) { + // At least one of the stages is visible while keyguard occluded. Dismiss split because + // there's show-when-locked activity showing on top of keyguard. Also make sure the + // task contains show-when-locked activity remains on top after split dismissed. + final StageTaskListener toTop = + mainStageVisible ? mMainStage : (sideStageVisible ? mSideStage : null); + exitSplitScreen(toTop, SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP); + } + + mSyncQueue.runInSync(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(mSideStage.mRootLeash, bothStageVisible) + .setVisibility(mMainStage.mRootLeash, bothStageVisible); + applyDividerVisibility(t); + applyOutlineVisibility(t); + } + }); + } + + private void applyDividerVisibility(SurfaceControl.Transaction t) { + final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash(); + if (dividerLeash == null) { + return; + } + + if (mDividerVisible) { + t.show(dividerLeash) + .setLayer(dividerLeash, SPLIT_DIVIDER_LAYER) + .setPosition(dividerLeash, + mSplitLayout.getDividerBounds().left, + mSplitLayout.getDividerBounds().top); + } else { + t.hide(dividerLeash); + } + } + + private void applyOutlineVisibility(SurfaceControl.Transaction t) { + final SurfaceControl outlineLeash = mSideStage.getOutlineLeash(); + if (outlineLeash == null) { + return; + } + + if (mDividerVisible) { + t.show(outlineLeash).setLayer(outlineLeash, SPLIT_DIVIDER_LAYER); + } else { + t.hide(outlineLeash); + } + } + + private void onStageHasChildrenChanged(StageListenerImpl stageListener) { + final boolean hasChildren = stageListener.mHasChildren; + final boolean isSideStage = stageListener == mSideStageListener; + if (!hasChildren) { + if (isSideStage && mMainStageListener.mVisible) { + // Exit to main stage if side stage no longer has children. + exitSplitScreen(mMainStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED); + } else if (!isSideStage && mSideStageListener.mVisible) { + // Exit to side stage if main stage no longer has children. + exitSplitScreen(mSideStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED); + } + } else if (isSideStage) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + // Make sure the main stage is active. + mMainStage.activate(getMainStageBounds(), wct); + mSideStage.setBounds(getSideStageBounds(), wct); + mTaskOrganizer.applyTransaction(wct); + } + if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren + && mSideStageListener.mHasChildren) { + mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(), + getMainStagePosition(), mMainStage.getTopChildTaskUid(), + getSideStagePosition(), mSideStage.getTopChildTaskUid(), + mSplitLayout.isLandscape()); + } + } + + @VisibleForTesting + IBinder onSnappedToDismissTransition(boolean mainStageToTop) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + prepareExitSplitScreen(mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE, wct); + return mSplitTransitions.startSnapToDismiss(wct, this); + } + + @Override + public void onSnappedToDismiss(boolean bottomOrRight) { + final boolean mainStageToTop = + bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT + : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT; + if (ENABLE_SHELL_TRANSITIONS) { + onSnappedToDismissTransition(mainStageToTop); + return; + } + exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, + SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER); + } + + @Override + public void onDoubleTappedDivider() { + setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT + ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT, null /* wct */); + mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(), + getSideStagePosition(), mSideStage.getTopChildTaskUid(), + mSplitLayout.isLandscape()); + } + + @Override + public void onLayoutPositionChanging(SplitLayout layout) { + mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t)); + } + + @Override + public void onLayoutSizeChanging(SplitLayout layout) { + mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t)); + mSideStage.setOutlineVisibility(false); + } + + @Override + public void onLayoutSizeChanged(SplitLayout layout) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + updateWindowBounds(layout, wct); + updateUnfoldBounds(); + mSyncQueue.queue(wct); + mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t)); + mSideStage.setOutlineVisibility(true); + mLogger.logResize(mSplitLayout.getDividerPositionAsFraction()); + } + + private void updateUnfoldBounds() { + if (mMainUnfoldController != null && mSideUnfoldController != null) { + mMainUnfoldController.onLayoutChanged(getMainStageBounds()); + mSideUnfoldController.onLayoutChanged(getSideStageBounds()); + } + } + + /** + * Populates `wct` with operations that match the split windows to the current layout. + * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied + */ + private void updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) { + final StageTaskListener topLeftStage = + mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; + final StageTaskListener bottomRightStage = + mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; + layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo); + } + + void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t) { + final StageTaskListener topLeftStage = + mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; + final StageTaskListener bottomRightStage = + mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; + (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash, + bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer); + } + + @Override + public int getSplitItemPosition(WindowContainerToken token) { + if (token == null) { + return SPLIT_POSITION_UNDEFINED; + } + + if (token.equals(mMainStage.mRootTaskInfo.getToken())) { + return getMainStagePosition(); + } else if (token.equals(mSideStage.mRootTaskInfo.getToken())) { + return getSideStagePosition(); + } + + return SPLIT_POSITION_UNDEFINED; + } + + @Override + public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) { + final StageTaskListener topLeftStage = + mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; + final StageTaskListener bottomRightStage = + mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; + final WindowContainerTransaction wct = new WindowContainerTransaction(); + layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo, + bottomRightStage.mRootTaskInfo); + mTaskOrganizer.applyTransaction(wct); + } + + @Override + public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) { + mDisplayAreaInfo = displayAreaInfo; + if (mSplitLayout == null) { + mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext, + mDisplayAreaInfo.configuration, this, mParentContainerCallbacks, + mDisplayImeController, mTaskOrganizer); + mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout); + + if (mMainUnfoldController != null && mSideUnfoldController != null) { + mMainUnfoldController.init(); + mSideUnfoldController.init(); + } + } + } + + @Override + public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) { + throw new IllegalStateException("Well that was unexpected..."); + } + + @Override + public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) { + mDisplayAreaInfo = displayAreaInfo; + if (mSplitLayout != null + && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration) + && mMainStage.isActive()) { + onLayoutSizeChanged(mSplitLayout); + } + } + + private void onFoldedStateChanged(boolean folded) { + mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; + if (!folded) return; + + if (mMainStage.isFocused()) { + mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN; + } else if (mSideStage.isFocused()) { + mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE; + } + } + + private Rect getSideStageBounds() { + return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT + ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2(); + } + + private Rect getMainStageBounds() { + return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT + ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1(); + } + + /** + * Get the stage that should contain this `taskInfo`. The stage doesn't necessarily contain + * this task (yet) so this can also be used to identify which stage to put a task into. + */ + private StageTaskListener getStageOfTask(ActivityManager.RunningTaskInfo taskInfo) { + // TODO(b/184679596): Find a way to either include task-org information in the transition, + // or synchronize task-org callbacks so we can use stage.containsTask + if (mMainStage.mRootTaskInfo != null + && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) { + return mMainStage; + } else if (mSideStage.mRootTaskInfo != null + && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) { + return mSideStage; + } + return null; + } + + @SplitScreen.StageType + private int getStageType(StageTaskListener stage) { + return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + } + + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @Nullable TransitionRequestInfo request) { + final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); + if (triggerTask == null) { + // still want to monitor everything while in split-screen, so return non-null. + return isSplitScreenVisible() ? new WindowContainerTransaction() : null; + } + + WindowContainerTransaction out = null; + final @WindowManager.TransitionType int type = request.getType(); + if (isSplitScreenVisible()) { + // try to handle everything while in split-screen, so return a WCT even if it's empty. + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split" + + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d" + + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type), + mMainStage.getChildCount(), mSideStage.getChildCount()); + out = new WindowContainerTransaction(); + final StageTaskListener stage = getStageOfTask(triggerTask); + if (stage != null) { + // dismiss split if the last task in one of the stages is going away + if (isClosingType(type) && stage.getChildCount() == 1) { + // The top should be the opposite side that is closing: + mDismissTop = getStageType(stage) == STAGE_TYPE_MAIN + ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; + } + } else { + if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) { + // Going home so dismiss both. + mDismissTop = STAGE_TYPE_UNDEFINED; + } + } + if (mDismissTop != NO_DISMISS) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + + " deduced Dismiss from request. toTop=%s", + stageTypeToString(mDismissTop)); + prepareExitSplitScreen(mDismissTop, out); + mSplitTransitions.mPendingDismiss = transition; + } + } else { + // Not in split mode, so look for an open into a split stage just so we can whine and + // complain about how this isn't a supported operation. + if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)) { + if (getStageOfTask(triggerTask) != null) { + throw new IllegalStateException("Entering split implicitly with only one task" + + " isn't supported."); + } + } + } + return out; + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (transition != mSplitTransitions.mPendingDismiss + && transition != mSplitTransitions.mPendingEnter) { + // Not entering or exiting, so just do some house-keeping and validation. + + // If we're not in split-mode, just abort so something else can handle it. + if (!isSplitScreenVisible()) return false; + + for (int iC = 0; iC < info.getChanges().size(); ++iC) { + final TransitionInfo.Change change = info.getChanges().get(iC); + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo == null || !taskInfo.hasParentTask()) continue; + final StageTaskListener stage = getStageOfTask(taskInfo); + if (stage == null) continue; + if (isOpeningType(change.getMode())) { + if (!stage.containsTask(taskInfo.taskId)) { + Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called" + + " with " + taskInfo.taskId + " before startAnimation()."); + } + } else if (isClosingType(change.getMode())) { + if (stage.containsTask(taskInfo.taskId)) { + Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called" + + " with " + taskInfo.taskId + " before startAnimation()."); + } + } + } + if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { + // TODO(shell-transitions): Implement a fallback behavior for now. + throw new IllegalStateException("Somehow removed the last task in a stage" + + " outside of a proper transition"); + // This can happen in some pathological cases. For example: + // 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C] + // 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time + // In this case, the result *should* be that we leave split. + // TODO(b/184679596): Find a way to either include task-org information in + // the transition, or synchronize task-org callbacks. + } + + // Use normal animations. + return false; + } + + boolean shouldAnimate = true; + if (mSplitTransitions.mPendingEnter == transition) { + shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction); + } else if (mSplitTransitions.mPendingDismiss == transition) { + shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction); + } + if (!shouldAnimate) return false; + + mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction, + finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); + return true; + } + + private boolean startPendingEnterAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { + if (info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN) { + // First, verify that we actually have opened 2 apps in split. + TransitionInfo.Change mainChild = null; + TransitionInfo.Change sideChild = null; + for (int iC = 0; iC < info.getChanges().size(); ++iC) { + final TransitionInfo.Change change = info.getChanges().get(iC); + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo == null || !taskInfo.hasParentTask()) continue; + final @SplitScreen.StageType int stageType = getStageType(getStageOfTask(taskInfo)); + if (stageType == STAGE_TYPE_MAIN) { + mainChild = change; + } else if (stageType == STAGE_TYPE_SIDE) { + sideChild = change; + } + } + if (mainChild == null || sideChild == null) { + throw new IllegalStateException("Launched 2 tasks in split, but didn't receive" + + " 2 tasks in transition. Possibly one of them failed to launch"); + // TODO: fallback logic. Probably start a new transition to exit split before + // applying anything here. Ideally consolidate with transition-merging. + } + + // Update local states (before animating). + setDividerVisibility(true); + setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */, + null /* wct */); + setSplitsVisible(true); + + addDividerBarToTransition(info, t, true /* show */); + + // Make some noise if things aren't totally expected. These states shouldn't effect + // transitions locally, but remotes (like Launcher) may get confused if they were + // depending on listener callbacks. This can happen because task-organizer callbacks + // aren't serialized with transition callbacks. + // TODO(b/184679596): Find a way to either include task-org information in + // the transition, or synchronize task-org callbacks. + if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) { + Log.w(TAG, "Expected onTaskAppeared on " + mMainStage + + " to have been called with " + mainChild.getTaskInfo().taskId + + " before startAnimation()."); + } + if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) { + Log.w(TAG, "Expected onTaskAppeared on " + mSideStage + + " to have been called with " + sideChild.getTaskInfo().taskId + + " before startAnimation()."); + } + return true; + } else { + // TODO: other entry method animations + throw new RuntimeException("Unsupported split-entry"); + } + } + + private boolean startPendingDismissAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { + // Make some noise if things aren't totally expected. These states shouldn't effect + // transitions locally, but remotes (like Launcher) may get confused if they were + // depending on listener callbacks. This can happen because task-organizer callbacks + // aren't serialized with transition callbacks. + // TODO(b/184679596): Find a way to either include task-org information in + // the transition, or synchronize task-org callbacks. + if (mMainStage.getChildCount() != 0) { + final StringBuilder tasksLeft = new StringBuilder(); + for (int i = 0; i < mMainStage.getChildCount(); ++i) { + tasksLeft.append(i != 0 ? ", " : ""); + tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i)); + } + Log.w(TAG, "Expected onTaskVanished on " + mMainStage + + " to have been called with [" + tasksLeft.toString() + + "] before startAnimation()."); + } + if (mSideStage.getChildCount() != 0) { + final StringBuilder tasksLeft = new StringBuilder(); + for (int i = 0; i < mSideStage.getChildCount(); ++i) { + tasksLeft.append(i != 0 ? ", " : ""); + tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i)); + } + Log.w(TAG, "Expected onTaskVanished on " + mSideStage + + " to have been called with [" + tasksLeft.toString() + + "] before startAnimation()."); + } + + // Update local states. + setSplitsVisible(false); + // Wait until after animation to update divider + + if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) { + // Reset crops so they don't interfere with subsequent launches + t.setWindowCrop(mMainStage.mRootLeash, null); + t.setWindowCrop(mSideStage.mRootLeash, null); + } + + if (mDismissTop == STAGE_TYPE_UNDEFINED) { + // Going home (dismissing both splits) + + // TODO: Have a proper remote for this. Until then, though, reset state and use the + // normal animation stuff (which falls back to the normal launcher remote). + t.hide(mSplitLayout.getDividerLeash()); + setDividerVisibility(false); + mSplitTransitions.mPendingDismiss = null; + return false; + } + + addDividerBarToTransition(info, t, false /* show */); + // We're dismissing split by moving the other one to fullscreen. + // Since we don't have any animations for this yet, just use the internal example + // animations. + return true; + } + + private void addDividerBarToTransition(@NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, boolean show) { + final SurfaceControl leash = mSplitLayout.getDividerLeash(); + final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash); + final Rect bounds = mSplitLayout.getDividerBounds(); + barChange.setStartAbsBounds(bounds); + barChange.setEndAbsBounds(bounds); + barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK); + barChange.setFlags(FLAG_IS_DIVIDER_BAR); + // Technically this should be order-0, but this is running after layer assignment + // and it's a special case, so just add to end. + info.addChange(barChange); + // Be default, make it visible. The remote animator can adjust alpha if it plans to animate. + if (show) { + t.setAlpha(leash, 1.f); + t.setLayer(leash, SPLIT_DIVIDER_LAYER); + t.setPosition(leash, bounds.left, bounds.top); + t.show(leash); + } + } + + RemoteAnimationTarget getDividerBarLegacyTarget() { + final Rect bounds = mSplitLayout.getDividerBounds(); + return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */, + mSplitLayout.getDividerLeash(), false /* isTranslucent */, null /* clipRect */, + null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */, + new android.graphics.Point(0, 0) /* position */, bounds, bounds, + new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */, + null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER); + } + + RemoteAnimationTarget getOutlineLegacyTarget() { + final Rect bounds = mSideStage.mRootTaskInfo.configuration.windowConfiguration.getBounds(); + // Leverage TYPE_DOCK_DIVIDER type when wrapping outline remote animation target in order to + // distinguish as a split auxiliary target in Launcher. + return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */, + mSideStage.getOutlineLeash(), false /* isTranslucent */, null /* clipRect */, + null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */, + new android.graphics.Point(0, 0) /* position */, bounds, bounds, + new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */, + null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER); + } + + @Override + public void dump(@NonNull PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + final String childPrefix = innerPrefix + " "; + pw.println(prefix + TAG + " mDisplayId=" + mDisplayId); + pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible); + pw.println(innerPrefix + "MainStage"); + pw.println(childPrefix + "isActive=" + mMainStage.isActive()); + mMainStageListener.dump(pw, childPrefix); + pw.println(innerPrefix + "SideStage"); + mSideStageListener.dump(pw, childPrefix); + pw.println(innerPrefix + "mSplitLayout=" + mSplitLayout); + } + + /** + * Directly set the visibility of both splits. This assumes hasChildren matches visibility. + * This is intended for batch use, so it assumes other state management logic is already + * handled. + */ + private void setSplitsVisible(boolean visible) { + mMainStageListener.mVisible = mSideStageListener.mVisible = visible; + mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible; + } + + /** + * Sets drag info to be logged when splitscreen is next entered. + */ + public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) { + mLogger.enterRequestedByDrag(position, dragSessionId); + } + + /** + * Logs the exit of splitscreen. + */ + private void logExit(int exitReason) { + mLogger.logExit(exitReason, + SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */, + SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */, + mSplitLayout.isLandscape()); + } + + /** + * Logs the exit of splitscreen to a specific stage. This must be called before the exit is + * executed. + */ + private void logExitToStage(int exitReason, boolean toMainStage) { + mLogger.logExit(exitReason, + toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED, + toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */, + !toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED, + !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */, + mSplitLayout.isLandscape()); + } + + class StageListenerImpl implements StageTaskListener.StageListenerCallbacks { + boolean mHasRootTask = false; + boolean mVisible = false; + boolean mHasChildren = false; + + @Override + public void onRootTaskAppeared() { + mHasRootTask = true; + StageCoordinator.this.onStageRootTaskAppeared(this); + } + + @Override + public void onStatusChanged(boolean visible, boolean hasChildren) { + if (!mHasRootTask) return; + + if (mHasChildren != hasChildren) { + mHasChildren = hasChildren; + StageCoordinator.this.onStageHasChildrenChanged(this); + } + if (mVisible != visible) { + mVisible = visible; + StageCoordinator.this.onStageVisibilityChanged(this); + } + } + + @Override + public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) { + StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible); + } + + @Override + public void onRootTaskVanished() { + reset(); + StageCoordinator.this.onStageRootTaskVanished(this); + } + + @Override + public void onNoLongerSupportMultiWindow() { + if (mMainStage.isActive()) { + StageCoordinator.this.exitSplitScreen(null /* childrenToTop */, + SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW); + } + } + + private void reset() { + mHasRootTask = false; + mVisible = false; + mHasChildren = false; + } + + public void dump(@NonNull PrintWriter pw, String prefix) { + pw.println(prefix + "mHasRootTask=" + mHasRootTask); + pw.println(prefix + "mVisible=" + mVisible); + pw.println(prefix + "mHasChildren=" + mHasChildren); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java new file mode 100644 index 000000000000..8b36c9406b15 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java @@ -0,0 +1,288 @@ +/* + * 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.wm.shell.stagesplit; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; + +import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; + +import android.annotation.CallSuper; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.SparseArray; +import android.view.SurfaceControl; +import android.view.SurfaceSession; +import android.window.WindowContainerTransaction; + +import androidx.annotation.NonNull; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.SurfaceUtils; +import com.android.wm.shell.common.SyncTransactionQueue; + +import java.io.PrintWriter; + +/** + * Base class that handle common task org. related for split-screen stages. + * Note that this class and its sub-class do not directly perform hierarchy operations. + * They only serve to hold a collection of tasks and provide APIs like + * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator} + * to perform operations in-sync with other containers. + * + * @see StageCoordinator + */ +class StageTaskListener implements ShellTaskOrganizer.TaskListener { + private static final String TAG = StageTaskListener.class.getSimpleName(); + + protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD}; + protected static final int[] CONTROLLED_WINDOWING_MODES = + {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED}; + protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE = + {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW}; + + /** Callback interface for listening to changes in a split-screen stage. */ + public interface StageListenerCallbacks { + void onRootTaskAppeared(); + + void onStatusChanged(boolean visible, boolean hasChildren); + + void onChildTaskStatusChanged(int taskId, boolean present, boolean visible); + + void onRootTaskVanished(); + void onNoLongerSupportMultiWindow(); + } + + private final StageListenerCallbacks mCallbacks; + private final SurfaceSession mSurfaceSession; + protected final SyncTransactionQueue mSyncQueue; + + protected ActivityManager.RunningTaskInfo mRootTaskInfo; + protected SurfaceControl mRootLeash; + protected SurfaceControl mDimLayer; + protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>(); + private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>(); + + private final StageTaskUnfoldController mStageTaskUnfoldController; + + StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId, + StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, + SurfaceSession surfaceSession, + @Nullable StageTaskUnfoldController stageTaskUnfoldController) { + mCallbacks = callbacks; + mSyncQueue = syncQueue; + mSurfaceSession = surfaceSession; + mStageTaskUnfoldController = stageTaskUnfoldController; + taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); + } + + int getChildCount() { + return mChildrenTaskInfo.size(); + } + + boolean containsTask(int taskId) { + return mChildrenTaskInfo.contains(taskId); + } + + /** + * Returns the top activity uid for the top child task. + */ + int getTopChildTaskUid() { + for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { + final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i); + if (info.topActivityInfo == null) { + continue; + } + return info.topActivityInfo.applicationInfo.uid; + } + return 0; + } + + /** @return {@code true} if this listener contains the currently focused task. */ + boolean isFocused() { + if (mRootTaskInfo == null) { + return false; + } + + if (mRootTaskInfo.isFocused) { + return true; + } + + for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { + if (mChildrenTaskInfo.valueAt(i).isFocused) { + return true; + } + } + + return false; + } + + @Override + @CallSuper + public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { + if (mRootTaskInfo == null && !taskInfo.hasParentTask()) { + mRootLeash = leash; + mRootTaskInfo = taskInfo; + mCallbacks.onRootTaskAppeared(); + sendStatusChanged(); + mSyncQueue.runInSync(t -> { + t.hide(mRootLeash); + mDimLayer = + SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession); + }); + } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) { + final int taskId = taskInfo.taskId; + mChildrenLeashes.put(taskId, leash); + mChildrenTaskInfo.put(taskId, taskInfo); + updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); + mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible); + if (ENABLE_SHELL_TRANSITIONS) { + // Status is managed/synchronized by the transition lifecycle. + return; + } + sendStatusChanged(); + } else { + throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + + "\n mRootTaskInfo: " + mRootTaskInfo); + } + + if (mStageTaskUnfoldController != null) { + mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash); + } + } + + @Override + @CallSuper + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { + if (!taskInfo.supportsMultiWindow) { + // Leave split screen if the task no longer supports multi window. + mCallbacks.onNoLongerSupportMultiWindow(); + return; + } + if (mRootTaskInfo.taskId == taskInfo.taskId) { + mRootTaskInfo = taskInfo; + } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) { + mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); + mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */, + taskInfo.isVisible); + if (!ENABLE_SHELL_TRANSITIONS) { + updateChildTaskSurface( + taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */); + } + } else { + throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + + "\n mRootTaskInfo: " + mRootTaskInfo); + } + if (ENABLE_SHELL_TRANSITIONS) { + // Status is managed/synchronized by the transition lifecycle. + return; + } + sendStatusChanged(); + } + + @Override + @CallSuper + public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + final int taskId = taskInfo.taskId; + if (mRootTaskInfo.taskId == taskId) { + mCallbacks.onRootTaskVanished(); + mSyncQueue.runInSync(t -> t.remove(mDimLayer)); + mRootTaskInfo = null; + } else if (mChildrenTaskInfo.contains(taskId)) { + mChildrenTaskInfo.remove(taskId); + mChildrenLeashes.remove(taskId); + mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible); + if (ENABLE_SHELL_TRANSITIONS) { + // Status is managed/synchronized by the transition lifecycle. + return; + } + sendStatusChanged(); + } else { + throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + + "\n mRootTaskInfo: " + mRootTaskInfo); + } + + if (mStageTaskUnfoldController != null) { + mStageTaskUnfoldController.onTaskVanished(taskInfo); + } + } + + @Override + public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { + if (mRootTaskInfo.taskId == taskId) { + b.setParent(mRootLeash); + } else if (mChildrenLeashes.contains(taskId)) { + b.setParent(mChildrenLeashes.get(taskId)); + } else { + throw new IllegalArgumentException("There is no surface for taskId=" + taskId); + } + } + + void setBounds(Rect bounds, WindowContainerTransaction wct) { + wct.setBounds(mRootTaskInfo.token, bounds); + } + + void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) { + if (!containsTask(taskId)) { + return; + } + wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */); + } + + void setVisibility(boolean visible, WindowContainerTransaction wct) { + wct.reorder(mRootTaskInfo.token, visible /* onTop */); + } + + void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener, + @SplitScreen.StageType int stage) { + for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { + int taskId = mChildrenTaskInfo.keyAt(i); + listener.onTaskStageChanged(taskId, stage, + mChildrenTaskInfo.get(taskId).isVisible); + } + } + + private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo, + SurfaceControl leash, boolean firstAppeared) { + final Point taskPositionInParent = taskInfo.positionInParent; + mSyncQueue.runInSync(t -> { + t.setWindowCrop(leash, null); + t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y); + if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) { + t.setAlpha(leash, 1f); + t.setMatrix(leash, 1, 0, 0, 1); + t.show(leash); + } + }); + } + + private void sendStatusChanged() { + mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0); + } + + @Override + @CallSuper + public void dump(@NonNull PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + final String childPrefix = innerPrefix + " "; + pw.println(prefix + this); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java new file mode 100644 index 000000000000..62b9da6d4715 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java @@ -0,0 +1,224 @@ +/* + * 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.stagesplit; + +import static android.view.Display.DEFAULT_DISPLAY; + +import android.animation.RectEvaluator; +import android.animation.TypeEvaluator; +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.content.Context; +import android.graphics.Rect; +import android.util.SparseArray; +import android.view.InsetsSource; +import android.view.InsetsState; +import android.view.SurfaceControl; + +import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; +import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; +import com.android.wm.shell.unfold.UnfoldBackgroundController; + +import java.util.concurrent.Executor; + +/** + * Controls transformations of the split screen task surfaces in response + * to the unfolding/folding action on foldable devices + */ +public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener { + + private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect()); + private static final float CROPPING_START_MARGIN_FRACTION = 0.05f; + + private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>(); + private final ShellUnfoldProgressProvider mUnfoldProgressProvider; + private final DisplayInsetsController mDisplayInsetsController; + private final UnfoldBackgroundController mBackgroundController; + private final Executor mExecutor; + private final int mExpandedTaskBarHeight; + private final float mWindowCornerRadiusPx; + private final Rect mStageBounds = new Rect(); + private final TransactionPool mTransactionPool; + + private InsetsSource mTaskbarInsetsSource; + private boolean mBothStagesVisible; + + public StageTaskUnfoldController(@NonNull Context context, + @NonNull TransactionPool transactionPool, + @NonNull ShellUnfoldProgressProvider unfoldProgressProvider, + @NonNull DisplayInsetsController displayInsetsController, + @NonNull UnfoldBackgroundController backgroundController, + @NonNull Executor executor) { + mUnfoldProgressProvider = unfoldProgressProvider; + mTransactionPool = transactionPool; + mExecutor = executor; + mBackgroundController = backgroundController; + mDisplayInsetsController = displayInsetsController; + mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); + mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.taskbar_frame_height); + } + + /** + * Initializes the controller, starts listening for the external events + */ + public void init() { + mUnfoldProgressProvider.addListener(mExecutor, this); + mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this); + } + + @Override + public void insetsChanged(InsetsState insetsState) { + mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + AnimationContext context = mAnimationContextByTaskId.valueAt(i); + context.update(); + } + } + + /** + * Called when split screen task appeared + * @param taskInfo info for the appeared task + * @param leash surface leash for the appeared task + */ + public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { + AnimationContext context = new AnimationContext(leash); + mAnimationContextByTaskId.put(taskInfo.taskId, context); + } + + /** + * Called when a split screen task vanished + * @param taskInfo info for the vanished task + */ + public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId); + if (context != null) { + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + resetSurface(transaction, context); + transaction.apply(); + mTransactionPool.release(transaction); + } + mAnimationContextByTaskId.remove(taskInfo.taskId); + } + + @Override + public void onStateChangeProgress(float progress) { + if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return; + + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + mBackgroundController.ensureBackground(transaction); + + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + AnimationContext context = mAnimationContextByTaskId.valueAt(i); + + context.mCurrentCropRect.set(RECT_EVALUATOR + .evaluate(progress, context.mStartCropRect, context.mEndCropRect)); + + transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect) + .setCornerRadius(context.mLeash, mWindowCornerRadiusPx); + } + + transaction.apply(); + + mTransactionPool.release(transaction); + } + + @Override + public void onStateChangeFinished() { + resetTransformations(); + } + + /** + * Called when split screen visibility changes + * @param bothStagesVisible true if both stages of the split screen are visible + */ + public void onSplitVisibilityChanged(boolean bothStagesVisible) { + mBothStagesVisible = bothStagesVisible; + if (!bothStagesVisible) { + resetTransformations(); + } + } + + /** + * Called when split screen stage bounds changed + * @param bounds new bounds for this stage + */ + public void onLayoutChanged(Rect bounds) { + mStageBounds.set(bounds); + + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + final AnimationContext context = mAnimationContextByTaskId.valueAt(i); + context.update(); + } + } + + private void resetTransformations() { + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + final AnimationContext context = mAnimationContextByTaskId.valueAt(i); + resetSurface(transaction, context); + } + mBackgroundController.removeBackground(transaction); + transaction.apply(); + + mTransactionPool.release(transaction); + } + + private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) { + transaction + .setWindowCrop(context.mLeash, null) + .setCornerRadius(context.mLeash, 0.0F); + } + + private class AnimationContext { + final SurfaceControl mLeash; + final Rect mStartCropRect = new Rect(); + final Rect mEndCropRect = new Rect(); + final Rect mCurrentCropRect = new Rect(); + + private AnimationContext(SurfaceControl leash) { + this.mLeash = leash; + update(); + } + + private void update() { + mStartCropRect.set(mStageBounds); + + if (mTaskbarInsetsSource != null) { + // Only insets the cropping window with taskbar when taskbar is expanded + if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) { + mStartCropRect.inset(mTaskbarInsetsSource + .calculateVisibleInsets(mStartCropRect)); + } + } + + // Offset to surface coordinates as layout bounds are in screen coordinates + mStartCropRect.offsetTo(0, 0); + + mEndCropRect.set(mStartCropRect); + + int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height()); + int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION); + mStartCropRect.inset(margin, margin, margin, margin); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java index f0685a81d25f..38122ffc032b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java @@ -38,6 +38,7 @@ import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Trace; +import android.util.Log; import android.util.PathParser; import android.window.SplashScreenView; @@ -50,6 +51,8 @@ import com.android.internal.R; */ public class SplashscreenIconDrawableFactory { + private static final String TAG = "SplashscreenIconDrawableFactory"; + /** * @return An array containing the foreground drawable at index 0 and if needed a background * drawable at index 1. @@ -282,7 +285,12 @@ public class SplashscreenIconDrawableFactory { if (startListener != null) { startListener.run(); } - mAnimatableIcon.start(); + try { + mAnimatableIcon.start(); + } catch (Exception ex) { + Log.e(TAG, "Error while running the splash screen animated icon", ex); + animation.cancel(); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 663d6477c3fe..7abda994bb5e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -70,6 +70,7 @@ import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.Transformation; import android.window.TransitionInfo; +import android.window.TransitionMetrics; import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -362,6 +363,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } startTransaction.apply(); + TransitionMetrics.getInstance().reportAnimationStart(transition); // run finish now in-case there are no animations onAnimFinish.run(); return true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 27201572d3e8..c36983189a71 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -44,6 +44,7 @@ import android.window.ITransitionPlayer; import android.window.RemoteTransition; import android.window.TransitionFilter; import android.window.TransitionInfo; +import android.window.TransitionMetrics; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; import android.window.WindowContainerTransactionCallback; @@ -192,6 +193,8 @@ public class Transitions implements RemoteCallable<Transitions> { public void register(ShellTaskOrganizer taskOrganizer) { if (mPlayerImpl == null) return; taskOrganizer.registerTransitionPlayer(mPlayerImpl); + // Pre-load the instance. + TransitionMetrics.getInstance(); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java new file mode 100644 index 000000000000..9faf454261d3 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java @@ -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.wm.shell.unfold; + +import static android.graphics.Color.blue; +import static android.graphics.Color.green; +import static android.graphics.Color.red; +import static android.view.Display.DEFAULT_DISPLAY; + +import android.annotation.NonNull; +import android.content.Context; +import android.view.SurfaceControl; + +import com.android.wm.shell.R; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; + +/** + * Controls background color layer for the unfold animations + */ +public class UnfoldBackgroundController { + + private static final int BACKGROUND_LAYER_Z_INDEX = -1; + + private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + private final float[] mBackgroundColor; + private SurfaceControl mBackgroundLayer; + + public UnfoldBackgroundController( + @NonNull Context context, + @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { + mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; + mBackgroundColor = getBackgroundColor(context); + } + + /** + * Ensures that unfold animation background color layer is present, + * @param transaction where we should add the background if it is not added + */ + public void ensureBackground(@NonNull SurfaceControl.Transaction transaction) { + if (mBackgroundLayer != null) return; + + SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder() + .setName("app-unfold-background") + .setCallsite("AppUnfoldTransitionController") + .setColorLayer(); + mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder); + mBackgroundLayer = colorLayerBuilder.build(); + + transaction + .setColor(mBackgroundLayer, mBackgroundColor) + .show(mBackgroundLayer) + .setLayer(mBackgroundLayer, BACKGROUND_LAYER_Z_INDEX); + } + + /** + * Ensures that the background is not visible + * @param transaction as part of which the removal will happen if needed + */ + public void removeBackground(@NonNull SurfaceControl.Transaction transaction) { + if (mBackgroundLayer == null) return; + if (mBackgroundLayer.isValid()) { + transaction.remove(mBackgroundLayer); + } + mBackgroundLayer = null; + } + + private float[] getBackgroundColor(Context context) { + int colorInt = context.getResources().getColor(R.color.unfold_transition_background); + return new float[]{ + (float) red(colorInt) / 255.0F, + (float) green(colorInt) / 255.0F, + (float) blue(colorInt) / 255.0F + }; + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml index e6d32ff1166f..06df9568e01a 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml @@ -42,6 +42,9 @@ <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/> <!-- ATM.removeRootTasksWithActivityTypes() --> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" /> + <!-- Enable bubble notification--> + <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> + <!-- Allow the test to write directly to /sdcard/ --> <application android:requestLegacyExternalStorage="true"> <uses-library android:name="android.test.runner"/> diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index b36468b7e9a5..c07f0eb11510 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -14,13 +14,14 @@ * limitations under the License. */ +@file:JvmName("CommonAssertions") package com.android.wm.shell.flicker -import android.content.ComponentName import android.graphics.Region import android.view.Surface import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.traces.common.FlickerComponentName fun FlickerTestParameter.appPairsDividerIsVisibleAtEnd() { assertLayersEnd { @@ -72,7 +73,7 @@ fun FlickerTestParameter.dockedStackDividerNotExistsAtEnd() { fun FlickerTestParameter.appPairsPrimaryBoundsIsVisibleAtEnd( rotation: Int, - primaryComponent: ComponentName + primaryComponent: FlickerComponentName ) { assertLayersEnd { val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region @@ -83,7 +84,7 @@ fun FlickerTestParameter.appPairsPrimaryBoundsIsVisibleAtEnd( fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisibleAtEnd( rotation: Int, - primaryComponent: ComponentName + primaryComponent: FlickerComponentName ) { assertLayersEnd { val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region @@ -94,7 +95,7 @@ fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisibleAtEnd( fun FlickerTestParameter.appPairsSecondaryBoundsIsVisibleAtEnd( rotation: Int, - secondaryComponent: ComponentName + secondaryComponent: FlickerComponentName ) { assertLayersEnd { val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region @@ -105,7 +106,7 @@ fun FlickerTestParameter.appPairsSecondaryBoundsIsVisibleAtEnd( fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd( rotation: Int, - secondaryComponent: ComponentName + secondaryComponent: FlickerComponentName ) { assertLayersEnd { val dividerRegion = layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt index ff1a6e6d9d90..40891f36a5da 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt @@ -17,8 +17,8 @@ @file:JvmName("CommonConstants") package com.android.wm.shell.flicker -import android.content.ComponentName +import com.android.server.wm.traces.common.FlickerComponentName const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" -val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentName("", "AppPairSplitDivider#") -val DOCKED_STACK_DIVIDER_COMPONENT = ComponentName("", "DockedStackDivider#")
\ No newline at end of file +val APP_PAIR_SPLIT_DIVIDER_COMPONENT = FlickerComponentName("", "AppPairSplitDivider#") +val DOCKED_STACK_DIVIDER_COMPONENT = FlickerComponentName("", "DockedStackDivider#")
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt index a6d67355f271..b63d9fffdb61 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:JvmName("WaitUtils") package com.android.wm.shell.flicker import android.os.SystemClock diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt index 19374ed04be5..038be9c190c2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt @@ -100,8 +100,8 @@ class AppPairsTestCannotPairNonResizeableApps( "Non resizeable app not initialized" } testSpec.assertWmEnd { - isVisible(nonResizeableApp.component) - isInvisible(primaryApp.component) + isAppWindowVisible(nonResizeableApp.component) + isAppWindowInvisible(primaryApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt index 46ee89295a4e..bbc6b2dbece8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt @@ -77,8 +77,8 @@ class AppPairsTestPairPrimaryAndSecondaryApps( @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { - isVisible(primaryApp.component) - isVisible(secondaryApp.component) + isAppWindowVisible(primaryApp.component) + isAppWindowVisible(secondaryApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt index f7ced71afe8a..bb784a809b7e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt @@ -100,8 +100,8 @@ class AppPairsTestSupportPairNonResizeableApps( "Non resizeable app not initialized" } testSpec.assertWmEnd { - isVisible(nonResizeableApp.component) - isVisible(primaryApp.component) + isAppWindowVisible(nonResizeableApp.component) + isAppWindowVisible(primaryApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt index 3debdd3276e4..a1a4db112dfd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt @@ -81,8 +81,8 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( @Test fun bothAppWindowsInvisible() { testSpec.assertWmEnd { - isInvisible(primaryApp.component) - isInvisible(secondaryApp.component) + isAppWindowInvisible(primaryApp.component) + isAppWindowInvisible(secondaryApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt index cdf89a57fde8..9e20bbbc1a1b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt @@ -20,13 +20,11 @@ import android.app.Instrumentation import android.content.Context import android.platform.test.annotations.Presubmit import android.system.helpers.ActivityHelper -import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.isRotated import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen @@ -38,6 +36,7 @@ import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.wm.shell.flicker.helpers.AppPairsHelper import com.android.wm.shell.flicker.helpers.BaseAppHelper import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow @@ -55,7 +54,7 @@ abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) protected val activityHelper = ActivityHelper.getInstance() protected val appPairsHelper = AppPairsHelper(instrumentation, Components.SplitScreenActivity.LABEL, - Components.SplitScreenActivity.COMPONENT) + Components.SplitScreenActivity.COMPONENT.toFlickerComponent()) protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation) protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation) @@ -178,15 +177,9 @@ abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) @Presubmit @Test - open fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, - testSpec.config.endRotation) - } + open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - open fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, - testSpec.config.endRotation) - } + open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt index 3e782e608c86..56a2531a3fe1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt @@ -73,8 +73,8 @@ class RotateTwoLaunchedAppsInAppPairsMode( @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { - isVisible(primaryApp.component) - .isVisible(secondaryApp.component) + isAppWindowVisible(primaryApp.component) + isAppWindowVisible(secondaryApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt index ee28c7aa6beb..0699a4fd0512 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt @@ -85,8 +85,8 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode( @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { - isVisible(primaryApp.component) - isVisible(secondaryApp.component) + isAppWindowVisible(primaryApp.component) + isAppWindowVisible(secondaryApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt new file mode 100644 index 000000000000..322d8b5e4dac --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt @@ -0,0 +1,118 @@ +/* + * 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.flicker.bubble + +import android.app.INotificationManager +import android.app.Instrumentation +import android.app.NotificationManager +import android.content.Context +import android.os.ServiceManager +import android.view.Surface +import androidx.test.filters.FlakyTest +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE +import com.android.server.wm.flicker.repetitions +import com.android.wm.shell.flicker.helpers.LaunchBubbleHelper +import org.junit.Test +import org.junit.runners.Parameterized + +/** + * Base configurations for Bubble flicker tests + */ +abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) { + + protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + protected val context: Context = instrumentation.context + protected val testApp = LaunchBubbleHelper(instrumentation) + + protected val notifyManager = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)) + + protected val packageManager = context.getPackageManager() + protected val uid = packageManager.getApplicationInfo( + testApp.component.packageName, 0).uid + + protected lateinit var addBubbleBtn: UiObject2 + protected lateinit var cancelAllBtn: UiObject2 + + protected abstract val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + + @JvmOverloads + protected open fun buildTransition( + extraSpec: FlickerBuilder.(Map<String, Any?>) -> Unit = {} + ): FlickerBuilder.(Map<String, Any?>) -> Unit { + return { configuration -> + + setup { + test { + notifyManager.setBubblesAllowed(testApp.component.packageName, + uid, NotificationManager.BUBBLE_PREFERENCE_ALL) + testApp.launchViaIntent(wmHelper) + addBubbleBtn = device.wait(Until.findObject( + By.text("Add Bubble")), FIND_OBJECT_TIMEOUT) + cancelAllBtn = device.wait(Until.findObject( + By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT) + } + } + + teardown { + notifyManager.setBubblesAllowed(testApp.component.packageName, + uid, NotificationManager.BUBBLE_PREFERENCE_NONE) + testApp.exit() + } + + extraSpec(this, configuration) + } + } + + @FlakyTest + @Test + fun testAppIsAlwaysVisible() { + testSpec.assertLayers { + this.isVisible(testApp.component) + } + } + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + repeat { testSpec.config.repetitions } + transition(this, testSpec.config) + } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0), + repetitions = 5) + } + + const val FIND_OBJECT_TIMEOUT = 2000L + const val SYSTEM_UI_PACKAGE = SYSTEMUI_PACKAGE + const val BUBBLE_RES_NAME = "bubble_view" + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt new file mode 100644 index 000000000000..bfdcb363a818 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt @@ -0,0 +1,65 @@ +/* + * 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.flicker.bubble + +import android.content.Context +import android.graphics.Point +import android.util.DisplayMetrics +import android.view.WindowManager +import androidx.test.filters.RequiresDevice +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +/** + * Test launching a new activity from bubble. + * + * To run this test: `atest WMShellFlickerTests:DismissBubbleScreen` + * + * Actions: + * Dismiss a bubble notification + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { + + val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + val displaySize = DisplayMetrics() + + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = buildTransition() { + setup { + eachRun { + addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Add Bubble not found") + } + } + transitions { + wm?.run { wm.getDefaultDisplay().getMetrics(displaySize) } ?: error("WM not found") + val dist = Point((displaySize.widthPixels / 2), displaySize.heightPixels) + val showBubble = device.wait(Until.findObject( + By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT) + showBubble?.run { drag(dist, 1000) } ?: error("Show bubble not found") + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt new file mode 100644 index 000000000000..42eeadf3ddd9 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt @@ -0,0 +1,59 @@ +/* + * 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.flicker.bubble + +import androidx.test.filters.RequiresDevice +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +/** + * Test launching a new activity from bubble. + * + * To run this test: `atest WMShellFlickerTests:ExpandBubbleScreen` + * + * Actions: + * Launch an app and enable app's bubble notification + * Send a bubble notification + * The activity for the bubble is launched + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { + + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = buildTransition() { + setup { + test { + addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Bubble widget not found") + } + } + transitions { + val showBubble = device.wait(Until.findObject( + By.res("com.android.systemui", "bubble_view")), FIND_OBJECT_TIMEOUT) + showBubble?.run { showBubble.click() } ?: error("Bubble notify not found") + device.pressBack() + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt new file mode 100644 index 000000000000..47e8c0c047a8 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt @@ -0,0 +1,48 @@ +/* + * 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.flicker.bubble + +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +/** + * Test creating a bubble notification + * + * To run this test: `atest WMShellFlickerTests:LaunchBubbleScreen` + * + * Actions: + * Launch an app and enable app's bubble notification + * Send a bubble notification + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { + + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = buildTransition() { + transitions { + addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Bubble widget not found") + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt new file mode 100644 index 000000000000..194e28fd6e8a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt @@ -0,0 +1,66 @@ +/* + * 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.flicker.bubble + +import android.os.SystemClock +import androidx.test.filters.RequiresDevice +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +/** + * Test launching a new activity from bubble. + * + * To run this test: `atest WMShellFlickerTests:MultiBubblesScreen` + * + * Actions: + * Switch in different bubble notifications + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 +class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) { + + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = buildTransition() { + setup { + test { + for (i in 1..3) { + addBubbleBtn?.run { addBubbleBtn.click() } ?: error("Add Bubble not found") + } + val showBubble = device.wait(Until.findObject( + By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT) + showBubble?.run { showBubble.click() } ?: error("Show bubble not found") + SystemClock.sleep(1000) + } + } + transitions { + val bubbles = device.wait(Until.findObjects( + By.res(SYSTEM_UI_PACKAGE, BUBBLE_RES_NAME)), FIND_OBJECT_TIMEOUT) + for (entry in bubbles) { + entry?.run { entry.click() } ?: error("Bubble not found") + SystemClock.sleep(1000) + } + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt index 5a438af0b1f1..623055f659b9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt @@ -17,15 +17,15 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.graphics.Region import com.android.server.wm.flicker.Flicker import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.traces.common.FlickerComponentName class AppPairsHelper( instrumentation: Instrumentation, activityLabel: String, - component: ComponentName + component: FlickerComponentName ) : BaseAppHelper(instrumentation, activityLabel, component) { fun getPrimaryBounds(dividerBounds: Region): android.graphics.Region { val primaryAppBounds = Region(0, 0, dividerBounds.bounds.right, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt index f15044ef37af..57bc0d580d72 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.content.pm.PackageManager.FEATURE_LEANBACK import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY import android.os.SystemProperties @@ -28,13 +27,13 @@ import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until import com.android.compatibility.common.util.SystemUtil import com.android.server.wm.flicker.helpers.StandardAppHelper -import com.android.server.wm.traces.parser.toWindowName +import com.android.server.wm.traces.common.FlickerComponentName import java.io.IOException abstract class BaseAppHelper( instrumentation: Instrumentation, launcherName: String, - component: ComponentName + component: FlickerComponentName ) : StandardAppHelper( instrumentation, launcherName, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt index b4ae18749b34..471e010cf560 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt @@ -17,10 +17,11 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.wm.shell.flicker.testapp.Components class FixedAppHelper(instrumentation: Instrumentation) : BaseAppHelper( instrumentation, Components.FixedActivity.LABEL, - Components.FixedActivity.COMPONENT + Components.FixedActivity.COMPONENT.toFlickerComponent() )
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt index 086e8b792e0e..0f00edea136f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt @@ -21,13 +21,14 @@ import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.FIND_TIMEOUT +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.testapp.Components open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper( instrumentation, Components.ImeActivity.LABEL, - Components.ImeActivity.COMPONENT + Components.ImeActivity.COMPONENT.toFlickerComponent() ) { /** * Opens the IME and wait for it to be displayed diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt new file mode 100644 index 000000000000..6695c17ed514 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt @@ -0,0 +1,33 @@ +/* + * 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.flicker.helpers + +import android.app.Instrumentation +import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.wm.shell.flicker.testapp.Components + +class LaunchBubbleHelper(instrumentation: Instrumentation) : BaseAppHelper( + instrumentation, + Components.LaunchBubbleActivity.LABEL, + Components.LaunchBubbleActivity.COMPONENT.toFlickerComponent() +) { + + companion object { + const val TEST_REPETITIONS = 1 + const val TIMEOUT_MS = 3_000L + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt index 7f99e62b36b0..12ccbafce651 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt @@ -17,14 +17,14 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.content.Context import android.provider.Settings +import com.android.server.wm.traces.common.FlickerComponentName class MultiWindowHelper( instrumentation: Instrumentation, activityLabel: String, - componentsInfo: ComponentName + componentsInfo: FlickerComponentName ) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) { companion object { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt index 1529f5bcd7e1..2357b0debb33 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt @@ -26,6 +26,7 @@ import androidx.test.uiautomator.BySelector import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.FIND_TIMEOUT import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild @@ -34,7 +35,7 @@ import com.android.wm.shell.flicker.testapp.Components class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( instrumentation, Components.PipActivity.LABEL, - Components.PipActivity.COMPONENT + Components.PipActivity.COMPONENT.toFlickerComponent() ) { private val mediaSessionManager: MediaSessionManager get() = context.getSystemService(MediaSessionManager::class.java) @@ -129,7 +130,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( } /** - * Expands the pip window and dismisses it by clicking on the X button. + * Taps the pip window and dismisses it by clicking on the X button. */ fun closePipWindow(wmHelper: WindowManagerStateHelper) { if (isTelevision) { @@ -137,9 +138,12 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( } else { val windowRect = getWindowRect(wmHelper) uiDevice.click(windowRect.centerX(), windowRect.centerY()) - val exitPipObject = uiDevice.findObject(By.res(SYSTEMUI_PACKAGE, "dismiss")) + // search and interact with the dismiss button + val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss") + uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT) + val dismissPipObject = uiDevice.findObject(dismissSelector) ?: error("PIP window dismiss button not found") - val dismissButtonBounds = exitPipObject.visibleBounds + val dismissButtonBounds = dismissPipObject.visibleBounds uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY()) } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt index ba13e38ae9e3..4d0fbc4a0e38 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt @@ -17,10 +17,11 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.wm.shell.flicker.testapp.Components class SimpleAppHelper(instrumentation: Instrumentation) : BaseAppHelper( instrumentation, Components.SimpleActivity.LABEL, - Components.SimpleActivity.COMPONENT + Components.SimpleActivity.COMPONENT.toFlickerComponent() )
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt index 2d996ca1d6f7..0ec9b2d869a8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt @@ -17,14 +17,15 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.content.res.Resources +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.wm.shell.flicker.testapp.Components class SplitScreenHelper( instrumentation: Instrumentation, activityLabel: String, - componentsInfo: ComponentName + componentsInfo: FlickerComponentName ) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) { companion object { @@ -39,16 +40,16 @@ class SplitScreenHelper( fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper = SplitScreenHelper(instrumentation, Components.SplitScreenActivity.LABEL, - Components.SplitScreenActivity.COMPONENT) + Components.SplitScreenActivity.COMPONENT.toFlickerComponent()) fun getSecondary(instrumentation: Instrumentation): SplitScreenHelper = SplitScreenHelper(instrumentation, Components.SplitScreenSecondaryActivity.LABEL, - Components.SplitScreenSecondaryActivity.COMPONENT) + Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent()) fun getNonResizeable(instrumentation: Instrumentation): SplitScreenHelper = SplitScreenHelper(instrumentation, Components.NonResizeableActivity.LABEL, - Components.NonResizeableActivity.COMPONENT) + Components.NonResizeableActivity.COMPONENT.toFlickerComponent()) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt index 508e93988aa6..bd44d082a1aa 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.content.ComponentName import android.platform.test.annotations.Presubmit import android.view.Surface import android.view.WindowManagerPolicyConstants @@ -25,13 +24,13 @@ import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper @@ -50,7 +49,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 +@Group4 class EnterSplitScreenDockActivity( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { @@ -62,10 +61,10 @@ class EnterSplitScreenDockActivity( } } - override val ignoredWindows: List<ComponentName> + override val ignoredWindows: List<FlickerComponentName> get() = listOf(LAUNCHER_COMPONENT, LIVE_WALLPAPER_COMPONENT, - splitScreenApp.component, WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT, LAUNCHER_COMPONENT) + splitScreenApp.component, FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT, LAUNCHER_COMPONENT) @Presubmit @Test @@ -89,7 +88,7 @@ class EnterSplitScreenDockActivity( @Test fun appWindowIsVisible() { testSpec.assertWmEnd { - isVisible(splitScreenApp.component) + isAppWindowVisible(splitScreenApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt index 12f3909b6c34..625d48b8ab5a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt @@ -16,16 +16,16 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.content.ComponentName import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder @@ -43,6 +43,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@Group4 class EnterSplitScreenFromDetachedRecentTask( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { @@ -62,10 +63,10 @@ class EnterSplitScreenFromDetachedRecentTask( } } - override val ignoredWindows: List<ComponentName> + override val ignoredWindows: List<FlickerComponentName> get() = listOf(LAUNCHER_COMPONENT, - WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT, splitScreenApp.component) @Presubmit @@ -76,7 +77,7 @@ class EnterSplitScreenFromDetachedRecentTask( @Test fun appWindowIsVisible() { testSpec.assertWmEnd { - isVisible(splitScreenApp.component) + isAppWindowVisible(splitScreenApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt index ac85c4857c76..2ed2806af528 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt @@ -16,21 +16,20 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.content.ComponentName import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd @@ -49,7 +48,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 +@Group4 class EnterSplitScreenLaunchToSide( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { @@ -62,10 +61,10 @@ class EnterSplitScreenLaunchToSide( } } - override val ignoredWindows: List<ComponentName> + override val ignoredWindows: List<FlickerComponentName> get() = listOf(LAUNCHER_COMPONENT, splitScreenApp.component, - secondaryApp.component, WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT) + secondaryApp.component, FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) @Presubmit @Test @@ -92,7 +91,7 @@ class EnterSplitScreenLaunchToSide( // Because we log WM once per frame, sometimes the activity and the window // become visible in the same entry, sometimes not, thus it is not possible to // assert the visibility of the activity here - this.isAppWindowInvisible(secondaryApp.component, ignoreActivity = true) + this.isAppWindowInvisible(secondaryApp.component) .then() // during re-parenting, the window may disappear and reappear from the // trace, this occurs because we log only 1x per frame diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt index 964af2341439..ee6cf341c9ff 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt @@ -16,17 +16,16 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.content.ComponentName import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.canSplitScreen -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow @@ -51,7 +50,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@Group1 +@Group4 class EnterSplitScreenNotSupportNonResizable( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { @@ -71,10 +70,10 @@ class EnterSplitScreenNotSupportNonResizable( } } - override val ignoredWindows: List<ComponentName> + override val ignoredWindows: List<FlickerComponentName> get() = listOf(LAUNCHER_COMPONENT, - WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT, nonResizeableApp.component, splitScreenApp.component) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt index 1b8afa668802..163b6ffda6e2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.content.ComponentName import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice @@ -26,7 +25,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow @@ -68,12 +67,12 @@ class EnterSplitScreenSupportNonResizable( } } - override val ignoredWindows: List<ComponentName> + override val ignoredWindows: List<FlickerComponentName> get() = listOf(LAUNCHER_COMPONENT, - WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT, - nonResizeableApp.component, - splitScreenApp.component) + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT, + nonResizeableApp.component, + splitScreenApp.component) @Before override fun setup() { @@ -95,7 +94,7 @@ class EnterSplitScreenSupportNonResizable( @Test fun appWindowIsVisible() { testSpec.assertWmEnd { - isVisible(nonResizeableApp.component) + isAppWindowVisible(nonResizeableApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt index 247965f8071d..2b629b0a7eb5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.content.ComponentName import android.platform.test.annotations.Postsubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -30,7 +29,7 @@ import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder @@ -70,10 +69,10 @@ class ExitLegacySplitScreenFromBottom( } } - override val ignoredWindows: List<ComponentName> - get() = listOf(LAUNCHER_COMPONENT, WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN, splitScreenApp.component, secondaryApp.component, - WindowManagerStateHelper.SNAPSHOT_COMPONENT) + FlickerComponentName.SNAPSHOT) @Postsubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt index ff34364261f2..95fe3bef4852 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.content.ComponentName import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -30,7 +29,7 @@ import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder @@ -70,10 +69,10 @@ class ExitPrimarySplitScreenShowSecondaryFullscreen( } } - override val ignoredWindows: List<ComponentName> - get() = listOf(LAUNCHER_COMPONENT, WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN, splitScreenApp.component, secondaryApp.component, - WindowManagerStateHelper.SNAPSHOT_COMPONENT) + FlickerComponentName.SNAPSHOT) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt index 95e4085db4eb..f7d628d48769 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.content.ComponentName import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice @@ -26,7 +25,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig @@ -69,11 +68,11 @@ class LegacySplitScreenFromIntentNotSupportNonResizable( } } - override val ignoredWindows: List<ComponentName> + override val ignoredWindows: List<FlickerComponentName> get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT, - nonResizeableApp.component, splitScreenApp.component, - WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT) + nonResizeableApp.component, splitScreenApp.component, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) @Before override fun setup() { @@ -120,12 +119,12 @@ class LegacySplitScreenFromIntentNotSupportNonResizable( // when the activity gets PAUSED the window may still be marked as visible // it will be updated in the next log entry. This occurs because we record 1x // per frame, thus ignore activity check here - this.isAppWindowVisible(splitScreenApp.component, ignoreActivity = true) + this.isAppWindowVisible(splitScreenApp.component) .then() // immediately after the window (after onResume and before perform relayout) // the activity is invisible. This may or not be logged, since we record 1x // per frame, thus ignore activity check here - .isAppWindowInvisible(splitScreenApp.component, ignoreActivity = true) + .isAppWindowInvisible(splitScreenApp.component) } } @@ -142,13 +141,12 @@ class LegacySplitScreenFromIntentNotSupportNonResizable( .then() // we log once per frame, upon logging, window may be visible or not depending // on what was processed until that moment. Both behaviors are correct - .isAppWindowInvisible(nonResizeableApp.component, - ignoreActivity = true, isOptional = true) + .isAppWindowInvisible(nonResizeableApp.component, isOptional = true) .then() // immediately after the window (after onResume and before perform relayout) // the activity is invisible. This may or not be logged, since we record 1x // per frame, thus ignore activity check here - .isAppWindowVisible(nonResizeableApp.component, ignoreActivity = true) + .isAppWindowVisible(nonResizeableApp.component) } } @@ -159,7 +157,7 @@ class LegacySplitScreenFromIntentNotSupportNonResizable( @Test fun nonResizableAppWindowBecomesVisibleAtEnd() { testSpec.assertWmEnd { - this.isVisible(nonResizeableApp.component) + isAppWindowVisible(nonResizeableApp.component) } } @@ -171,8 +169,8 @@ class LegacySplitScreenFromIntentNotSupportNonResizable( @Test fun onlyNonResizableAppWindowIsVisibleAtEnd() { testSpec.assertWmEnd { - isInvisible(splitScreenApp.component) - isVisible(nonResizeableApp.component) + isAppWindowInvisible(splitScreenApp.component) + isAppWindowVisible(nonResizeableApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt index 65346aa8ea5d..a5c6571f68de 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.content.ComponentName import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice @@ -26,7 +25,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig @@ -69,11 +68,11 @@ class LegacySplitScreenFromIntentSupportNonResizable( } } - override val ignoredWindows: List<ComponentName> + override val ignoredWindows: List<FlickerComponentName> get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT, nonResizeableApp.component, splitScreenApp.component, - WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT) + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) @Before override fun setup() { @@ -110,13 +109,12 @@ class LegacySplitScreenFromIntentSupportNonResizable( .then() // we log once per frame, upon logging, window may be visible or not depending // on what was processed until that moment. Both behaviors are correct - .isAppWindowInvisible(nonResizeableApp.component, - ignoreActivity = true, isOptional = true) + .isAppWindowInvisible(nonResizeableApp.component, isOptional = true) .then() // immediately after the window (after onResume and before perform relayout) // the activity is invisible. This may or not be logged, since we record 1x // per frame, thus ignore activity check here - .isAppWindowVisible(nonResizeableApp.component, ignoreActivity = true) + .isAppWindowVisible(nonResizeableApp.component) } } @@ -128,8 +126,8 @@ class LegacySplitScreenFromIntentSupportNonResizable( @Test fun bothAppsWindowsAreVisibleAtEnd() { testSpec.assertWmEnd { - isVisible(splitScreenApp.component) - isVisible(nonResizeableApp.component) + isAppWindowVisible(splitScreenApp.component) + isAppWindowVisible(nonResizeableApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt index 547341a14cdd..6f486b0ddfea 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.content.ComponentName import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface @@ -28,7 +27,7 @@ import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig @@ -71,11 +70,11 @@ class LegacySplitScreenFromRecentNotSupportNonResizable( } } - override val ignoredWindows: List<ComponentName> + override val ignoredWindows: List<FlickerComponentName> get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT, - TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component, - WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT) + TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) @Before override fun setup() { @@ -116,12 +115,12 @@ class LegacySplitScreenFromRecentNotSupportNonResizable( // when the activity gets PAUSED the window may still be marked as visible // it will be updated in the next log entry. This occurs because we record 1x // per frame, thus ignore activity check here - this.isAppWindowVisible(splitScreenApp.component, ignoreActivity = true) + this.isAppWindowVisible(splitScreenApp.component) .then() // immediately after the window (after onResume and before perform relayout) // the activity is invisible. This may or not be logged, since we record 1x // per frame, thus ignore activity check here - .isAppWindowInvisible(splitScreenApp.component, ignoreActivity = true) + .isAppWindowInvisible(splitScreenApp.component) } } @@ -143,8 +142,8 @@ class LegacySplitScreenFromRecentNotSupportNonResizable( @Test fun onlyNonResizableAppWindowIsVisibleAtEnd() { testSpec.assertWmEnd { - isInvisible(splitScreenApp.component) - isVisible(nonResizeableApp.component) + isAppWindowInvisible(splitScreenApp.component) + isAppWindowVisible(nonResizeableApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt index 3f86658297fe..f03c927b8d58 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.content.ComponentName import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice @@ -27,7 +26,7 @@ import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig @@ -70,11 +69,11 @@ class LegacySplitScreenFromRecentSupportNonResizable( } } - override val ignoredWindows: List<ComponentName> + override val ignoredWindows: List<FlickerComponentName> get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT, - TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component, - WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT) + TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) @Before override fun setup() { @@ -107,7 +106,7 @@ class LegacySplitScreenFromRecentSupportNonResizable( // Because we log WM once per frame, sometimes the activity and the window // become visible in the same entry, sometimes not, thus it is not possible to // assert the visibility of the activity here - this.isAppWindowInvisible(nonResizeableApp.component, ignoreActivity = true) + this.isAppWindowInvisible(nonResizeableApp.component) .then() // during re-parenting, the window may disappear and reappear from the // trace, this occurs because we log only 1x per frame @@ -129,8 +128,8 @@ class LegacySplitScreenFromRecentSupportNonResizable( @Test fun bothAppsWindowsAreVisibleAtEnd() { testSpec.assertWmEnd { - isVisible(splitScreenApp.component) - isVisible(nonResizeableApp.component) + isAppWindowVisible(splitScreenApp.component) + isAppWindowVisible(nonResizeableApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt index 5fb6f1fa1076..2ccd03bf1d6a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.content.ComponentName import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface @@ -39,7 +38,7 @@ import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible import com.android.wm.shell.flicker.helpers.SimpleAppHelper import org.junit.FixMethodOrder @@ -86,9 +85,9 @@ class LegacySplitScreenToLauncher( } } - override val ignoredWindows: List<ComponentName> - get() = listOf(LAUNCHER_COMPONENT, WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT) + override val ignoredWindows: List<FlickerComponentName> + get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) @Presubmit @Test @@ -108,13 +107,11 @@ class LegacySplitScreenToLauncher( @Presubmit @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.endRotation) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.endRotation) + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt index 311769313a7a..661c8b69068e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.app.Instrumentation -import android.content.ComponentName import android.content.Context import android.support.test.launcherhelper.LauncherStrategyFactory import android.view.Surface @@ -32,7 +31,7 @@ import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setDevEnableNonResizableMultiWindow @@ -50,7 +49,7 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation) protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation) protected val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation) - protected val LAUNCHER_COMPONENT = ComponentName("", + protected val LAUNCHER_COMPONENT = FlickerComponentName("", LauncherStrategyFactory.getInstance(instrumentation) .launcherStrategy.supportedLauncherPackage) private var prevDevEnableNonResizableMultiWindow = 0 @@ -79,9 +78,9 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa * * b/182720234 */ - open val ignoredWindows: List<ComponentName> = listOf( - WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT) + open val ignoredWindows: List<FlickerComponentName> = listOf( + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> @@ -148,9 +147,9 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa } companion object { - internal val LIVE_WALLPAPER_COMPONENT = ComponentName("", + internal val LIVE_WALLPAPER_COMPONENT = FlickerComponentName("", "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2") - internal val LETTERBOX_COMPONENT = ComponentName("", "Letterbox") - internal val TOAST_COMPONENT = ComponentName("", "Toast") + internal val LETTERBOX_COMPONENT = FlickerComponentName("", "Letterbox") + internal val TOAST_COMPONENT = FlickerComponentName("", "Toast") } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt index a7ac6a77d425..34eff80a04bc 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.content.ComponentName import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -29,7 +28,7 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.appPairsDividerBecomesVisible import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder @@ -59,10 +58,10 @@ class OpenAppToLegacySplitScreen( } } - override val ignoredWindows: List<ComponentName> + override val ignoredWindows: List<FlickerComponentName> get() = listOf(LAUNCHER_COMPONENT, splitScreenApp.component, - WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT) + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT) @FlakyTest @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt index cd150518f21f..58e1def6f37a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt @@ -27,7 +27,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.WindowUtils @@ -42,6 +41,7 @@ import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT import com.android.wm.shell.flicker.helpers.SimpleAppHelper import com.android.wm.shell.flicker.testapp.Components @@ -110,7 +110,7 @@ class ResizeLegacySplitScreen( @Test fun topAppWindowIsAlwaysVisible() { testSpec.assertWm { - this.isAppWindowVisible(Components.SimpleActivity.COMPONENT) + this.isAppWindowVisible(Components.SimpleActivity.COMPONENT.toFlickerComponent()) } } @@ -118,7 +118,7 @@ class ResizeLegacySplitScreen( @Test fun bottomAppWindowIsAlwaysVisible() { testSpec.assertWm { - this.isAppWindowVisible(Components.ImeActivity.COMPONENT) + this.isAppWindowVisible(Components.ImeActivity.COMPONENT.toFlickerComponent()) } } @@ -132,24 +132,22 @@ class ResizeLegacySplitScreen( fun entireScreenCovered() = testSpec.entireScreenCovered() @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.endRotation) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Test - fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.endRotation) + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Test fun topAppLayerIsAlwaysVisible() { testSpec.assertLayers { - this.isVisible(Components.SimpleActivity.COMPONENT) + this.isVisible(Components.SimpleActivity.COMPONENT.toFlickerComponent()) } } @Test fun bottomAppLayerIsAlwaysVisible() { testSpec.assertLayers { - this.isVisible(Components.ImeActivity.COMPONENT) + this.isVisible(Components.ImeActivity.COMPONENT.toFlickerComponent()) } } @@ -174,8 +172,10 @@ class ResizeLegacySplitScreen( dividerBounds.bottom - WindowUtils.dockedStackDividerInset, displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) - visibleRegion(Components.SimpleActivity.COMPONENT).coversExactly(topAppBounds) - visibleRegion(Components.ImeActivity.COMPONENT).coversExactly(bottomAppBounds) + visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent()) + .coversExactly(topAppBounds) + visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent()) + .coversExactly(bottomAppBounds) } } @@ -194,8 +194,10 @@ class ResizeLegacySplitScreen( displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) - visibleRegion(Components.SimpleActivity.COMPONENT).coversExactly(topAppBounds) - visibleRegion(Components.ImeActivity.COMPONENT).coversExactly(bottomAppBounds) + visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent()) + .coversExactly(topAppBounds) + visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent()) + .coversExactly(bottomAppBounds) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt index 8a2b55b6fce0..8a50bc0b20cf 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt @@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerRotatesAndScales @@ -75,15 +74,11 @@ class RotateOneLaunchedAppAndEnterSplitScreen( @Presubmit @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, - testSpec.config.endRotation) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, - testSpec.config.endRotation) + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt index b3251573c942..84676a9186be 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt @@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerRotatesAndScales @@ -74,13 +73,11 @@ class RotateOneLaunchedAppInSplitScreenMode( @Presubmit @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales( - testSpec.config.startRotation, testSpec.config.endRotation) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales( - testSpec.config.startRotation, testSpec.config.endRotation) + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt index 2be693631b26..2abdca9216f9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt @@ -24,7 +24,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation @@ -83,14 +82,11 @@ class RotateTwoLaunchedAppAndEnterSplitScreen( @Presubmit @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, - testSpec.config.endRotation) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales( - testSpec.config.startRotation, testSpec.config.endRotation) + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test @@ -101,7 +97,7 @@ class RotateTwoLaunchedAppAndEnterSplitScreen( // Because we log WM once per frame, sometimes the activity and the window // become visible in the same entry, sometimes not, thus it is not possible to // assert the visibility of the activity here - this.isAppWindowInvisible(secondaryApp.component, ignoreActivity = true) + this.isAppWindowInvisible(secondaryApp.component) .then() // during re-parenting, the window may disappear and reappear from the // trace, this occurs because we log only 1x per frame diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt index 5782f145c00f..fe9b9f514015 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt @@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation @@ -89,15 +88,11 @@ class RotateTwoLaunchedAppInSplitScreenMode( @Presubmit @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, - testSpec.config.endRotation) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, - testSpec.config.endRotation) + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @FlakyTest @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt index 443204c245db..f9b08000290f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:JvmName("CommonAssertions") package com.android.wm.shell.flicker.pip internal const val PIP_WINDOW_COMPONENT = "PipMenuActivity" diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index 046972d246e6..52a744f3897d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -26,7 +26,6 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.traces.parser.toLayerName import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt index 097ccb8cd734..c8c3f4d64294 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt @@ -29,7 +29,7 @@ import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.helpers.FixedAppHelper import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT @@ -106,22 +106,20 @@ class EnterPipToOtherOrientationTest( } /** - * Checks that the [WindowManagerStateHelper.NAV_BAR_COMPONENT] has the correct position at + * Checks that the [FlickerComponentName.NAV_BAR] has the correct position at * the start and end of the transition */ @FlakyTest @Test - override fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_90, Surface.ROTATION_0) + override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() /** - * Checks that the [WindowManagerStateHelper.STATUS_BAR_COMPONENT] has the correct position at + * Checks that the [FlickerComponentName.STATUS_BAR] has the correct position at * the start and end of the transition */ @Presubmit @Test - override fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(Surface.ROTATION_90, Surface.ROTATION_0) + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() /** * Checks that all parts of the screen are covered at the start and end of the transition @@ -150,7 +148,7 @@ class EnterPipToOtherOrientationTest( @Test fun testAppWindowInvisibleOnStart() { testSpec.assertWmStart { - isInvisible(testApp.component) + isAppWindowInvisible(testApp.component) } } @@ -161,7 +159,7 @@ class EnterPipToOtherOrientationTest( @Test fun testAppWindowVisibleOnEnd() { testSpec.assertWmEnd { - isVisible(testApp.component) + isAppWindowVisible(testApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt index faaaecb8da46..64b7eb53bd6f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt @@ -18,7 +18,6 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.traces.parser.toLayerName import com.android.wm.shell.flicker.helpers.FixedAppHelper import org.junit.Test @@ -63,7 +62,7 @@ abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTrans // when the activity is STOPPING, sometimes it becomes invisible in an entry before // the window, sometimes in the same entry. This occurs because we log 1x per frame // thus we ignore activity here - isAppWindowVisible(testApp.component, ignoreActivity = true) + isAppWindowVisible(testApp.component) .isAppWindowOnTop(pipApp.component) .then() .isAppWindowInvisible(testApp.component) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt index 3414031d3532..5207fed59208 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt @@ -54,9 +54,9 @@ abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition open fun pipWindowBecomesInvisible() { testSpec.assertWm { this.invoke("hasPipWindow") { - it.isPinned(pipApp.component).isVisible(pipApp.component) + it.isPinned(pipApp.component).isAppWindowVisible(pipApp.component) }.then().invoke("!hasPipWindow") { - it.isNotPinned(pipApp.component).isInvisible(pipApp.component) + it.isNotPinned(pipApp.component).isAppWindowInvisible(pipApp.component) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt index fa100b5b25ab..b53342d6f2f7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt @@ -23,7 +23,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.traces.parser.toWindowName import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt index 617ef221ee9a..1fec3cf85214 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt @@ -23,7 +23,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.traces.parser.toWindowName import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt index e3d099f6fdb5..73626c23065a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.Postsubmit import android.view.Surface import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory @@ -25,7 +24,6 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -62,14 +60,6 @@ class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTran } } - @Postsubmit - @Test - override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible() - - @Postsubmit - @Test - override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible() - companion object { /** * Creates the test configurations. diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt index 2cdfc2bf0654..9e43deef8d99 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt @@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerRotatesScales import org.junit.FixMethodOrder import org.junit.Test @@ -97,8 +96,7 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti @Presubmit @Test - override fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index 89b2c400e80a..d0fee9a82093 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -27,7 +26,6 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.traces.parser.toLayerName import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -91,7 +89,7 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition /** * Checks [pipApp] window remains visible throughout the animation */ - @Postsubmit + @Presubmit @Test fun pipWindowIsAlwaysVisible() { testSpec.assertWm { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt index ed04fc947435..6e0324c17272 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt @@ -20,8 +20,6 @@ import android.platform.test.annotations.Presubmit import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.traces.RegionSubject -import com.android.server.wm.traces.parser.toLayerName -import com.android.server.wm.traces.parser.toWindowName import com.android.wm.shell.flicker.helpers.FixedAppHelper import org.junit.Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt index 5719413aff25..aba8aced298f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt @@ -22,12 +22,12 @@ import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.startRotation -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.helpers.ImeAppHelper import org.junit.FixMethodOrder import org.junit.Test @@ -43,7 +43,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 +@Group4 class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val imeApp = ImeAppHelper(instrumentation) @@ -90,7 +90,7 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) @Test fun pipIsAboveAppWindow() { testSpec.assertWmTag(TAG_IME_VISIBLE) { - isAboveWindow(WindowManagerStateHelper.IME_COMPONENT, pipApp.component) + isAboveWindow(FlickerComponentName.IME, pipApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt index 086165289d2d..9bea5c03dadb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt @@ -23,7 +23,7 @@ import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen @@ -51,7 +51,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 +@Group4 class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val imeApp = ImeAppHelper(instrumentation) private val testApp = FixedAppHelper(instrumentation) @@ -104,9 +104,9 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t @Test fun bothAppWindowsVisible() { testSpec.assertWmEnd { - isVisible(testApp.component) - isVisible(imeApp.component) - noWindowsOverlap(testApp.component, imeApp.component) + isAppWindowVisible(testApp.component) + isAppWindowVisible(imeApp.component) + doNotOverlap(testApp.component, imeApp.component) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt index b1054600d702..669f37ad1e72 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt @@ -23,7 +23,7 @@ import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.entireScreenCovered @@ -62,7 +62,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 +@Group4 class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val fixedApp = FixedAppHelper(instrumentation) private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.config.startRotation) @@ -95,18 +95,14 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) */ @FlakyTest @Test - override fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, - testSpec.config.endRotation) + override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() /** * Checks the position of the status bar at the start and end of the transition */ @Presubmit @Test - override fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, - testSpec.config.endRotation) + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() /** * Checks that [fixedApp] layer is within [screenBoundsStart] at the start of the transition diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index ce89fb6af67d..e8a61e8a1dae 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -177,13 +177,11 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) { @Presubmit @Test - open fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) + open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - open fun statusBarLayerRotatesScales() = - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) + open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt index 02a3eb1e070a..d6dbc366aec0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt @@ -22,7 +22,7 @@ import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE @@ -43,7 +43,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 +@Group4 class SetRequestedOrientationWhilePinnedTest( testSpec: FlickerTestParameter ) : PipTransition(testSpec) { diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml index 5549330df766..2cdbffa7589c 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml @@ -107,5 +107,20 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity + android:name=".LaunchBubbleActivity" + android:label="LaunchBubbleApp" + android:exported="true" + android:launchMode="singleTop"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <action android:name="android.intent.action.VIEW" /> + </intent-filter> + </activity> + <activity + android:name=".BubbleActivity" + android:label="BubbleApp" + android:exported="false" + android:resizeableActivity="true" /> </application> </manifest> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png Binary files differnew file mode 100644 index 000000000000..d424a17b4157 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml new file mode 100644 index 000000000000..b43f31da748d --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/> + <path + android:fillColor="#FF000000" + android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/> + <path + android:fillColor="#FF000000" + android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0"/> +</vector> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml new file mode 100644 index 000000000000..0e8c7a0fe64a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M12,4c-4.97,0 -9,3.58 -9,8c0,1.53 0.49,2.97 1.33,4.18c0.12,0.18 0.2,0.46 0.1,0.66c-0.33,0.68 -0.79,1.52 -1.38,2.39c-0.12,0.17 0.01,0.41 0.21,0.39c0.63,-0.05 1.86,-0.26 3.38,-0.91c0.17,-0.07 0.36,-0.06 0.52,0.03C8.55,19.54 10.21,20 12,20c4.97,0 9,-3.58 9,-8S16.97,4 12,4zM16.94,11.63l-3.29,3.29c-0.13,0.13 -0.34,0.04 -0.34,-0.14v-1.57c0,-0.11 -0.1,-0.21 -0.21,-0.2c-2.19,0.06 -3.65,0.65 -5.14,1.95c-0.15,0.13 -0.38,0 -0.33,-0.19c0.7,-2.57 2.9,-4.57 5.5,-4.75c0.1,-0.01 0.18,-0.09 0.18,-0.19V8.2c0,-0.18 0.22,-0.27 0.34,-0.14l3.29,3.29C17.02,11.43 17.02,11.55 16.94,11.63z" + android:fillColor="#000000" + android:fillType="evenOdd"/> +</vector> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml new file mode 100644 index 000000000000..f8b0ca3da26e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <Button + android:id="@+id/button_finish" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_marginStart="8dp" + android:text="Finish" /> + <Button + android:id="@+id/button_new_task" + android:layout_width="wrap_content" + android:layout_height="46dp" + android:layout_marginStart="8dp" + android:text="New Task" /> + <Button + android:id="@+id/button_new_bubble" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:text="New Bubble" /> + + <Button + android:id="@+id/button_activity_for_result" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:layout_marginStart="8dp" + android:text="Activity For Result" /> +</LinearLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml new file mode 100644 index 000000000000..f23c46455c63 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@android:color/black"> + + <Button + android:id="@+id/button_create" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:layout_centerVertical="true" + android:text="Add Bubble" /> + + <Button + android:id="@+id/button_cancel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/button_create" + android:layout_centerHorizontal="true" + android:layout_marginTop="20dp" + android:text="Cancel Bubble" /> + + <Button + android:id="@+id/button_cancel_all" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/button_cancel" + android:layout_centerHorizontal="true" + android:layout_marginTop="20dp" + android:text="Cancel All Bubble" /> +</RelativeLayout> diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java new file mode 100644 index 000000000000..bc3bc75ab903 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java @@ -0,0 +1,77 @@ +/* + * 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.wm.shell.flicker.testapp; + + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.widget.Toast; + +public class BubbleActivity extends Activity { + private int mNotifId = 0; + + public BubbleActivity() { + super(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + if (intent != null) { + mNotifId = intent.getIntExtra(BubbleHelper.EXTRA_BUBBLE_NOTIF_ID, -1); + } else { + mNotifId = -1; + } + + setContentView(R.layout.activity_bubble); + } + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + protected void onStop() { + super.onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + String result = resultCode == Activity.RESULT_OK ? "OK" : "CANCELLED"; + Toast.makeText(this, "Activity result: " + result, Toast.LENGTH_SHORT).show(); + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java new file mode 100644 index 000000000000..d743dffd3c9e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java @@ -0,0 +1,178 @@ +/* + * 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.flicker.testapp; + + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Person; +import android.app.RemoteInput; +import android.content.Context; +import android.content.Intent; +import android.graphics.Point; +import android.graphics.drawable.Icon; +import android.os.SystemClock; +import android.service.notification.StatusBarNotification; +import android.view.WindowManager; + +import java.util.HashMap; + +public class BubbleHelper { + + static final String EXTRA_BUBBLE_NOTIF_ID = "EXTRA_BUBBLE_NOTIF_ID"; + static final String CHANNEL_ID = "bubbles"; + static final String CHANNEL_NAME = "Bubbles"; + static final int DEFAULT_HEIGHT_DP = 300; + + private static BubbleHelper sInstance; + + private final Context mContext; + private NotificationManager mNotificationManager; + private float mDisplayHeight; + + private HashMap<Integer, BubbleInfo> mBubbleMap = new HashMap<>(); + + private int mNextNotifyId = 0; + private int mColourIndex = 0; + + public static class BubbleInfo { + public int id; + public int height; + public Icon icon; + + public BubbleInfo(int id, int height, Icon icon) { + this.id = id; + this.height = height; + this.icon = icon; + } + } + + public static BubbleHelper getInstance(Context context) { + if (sInstance == null) { + sInstance = new BubbleHelper(context); + } + return sInstance; + } + + private BubbleHelper(Context context) { + mContext = context; + mNotificationManager = context.getSystemService(NotificationManager.class); + + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, + NotificationManager.IMPORTANCE_DEFAULT); + channel.setDescription("Channel that posts bubbles"); + channel.setAllowBubbles(true); + mNotificationManager.createNotificationChannel(channel); + + Point p = new Point(); + WindowManager wm = context.getSystemService(WindowManager.class); + wm.getDefaultDisplay().getRealSize(p); + mDisplayHeight = p.y; + + } + + private int getNextNotifyId() { + int id = mNextNotifyId; + mNextNotifyId++; + return id; + } + + private Icon getIcon() { + return Icon.createWithResource(mContext, R.drawable.bg); + } + + public int addNewBubble(boolean autoExpand, boolean suppressNotif) { + int id = getNextNotifyId(); + BubbleInfo info = new BubbleInfo(id, DEFAULT_HEIGHT_DP, getIcon()); + mBubbleMap.put(info.id, info); + + Notification.BubbleMetadata data = getBubbleBuilder(info) + .setSuppressNotification(suppressNotif) + .setAutoExpandBubble(false) + .build(); + Notification notification = getNotificationBuilder(info.id) + .setBubbleMetadata(data).build(); + + mNotificationManager.notify(info.id, notification); + return info.id; + } + + private Notification.Builder getNotificationBuilder(int id) { + Person chatBot = new Person.Builder() + .setBot(true) + .setName("BubbleBot") + .setImportant(true) + .build(); + + RemoteInput remoteInput = new RemoteInput.Builder("key") + .setLabel("Reply") + .build(); + + String shortcutId = "BubbleChat"; + return new Notification.Builder(mContext, CHANNEL_ID) + .setChannelId(CHANNEL_ID) + .setShortcutId(shortcutId) + .setContentIntent(PendingIntent.getActivity(mContext, 0, + new Intent(mContext, LaunchBubbleActivity.class), + PendingIntent.FLAG_UPDATE_CURRENT)) + .setStyle(new Notification.MessagingStyle(chatBot) + .setConversationTitle("Bubble Chat") + .addMessage("Hello? This is bubble: " + id, + SystemClock.currentThreadTimeMillis() - 300000, chatBot) + .addMessage("Is it me, " + id + ", you're looking for?", + SystemClock.currentThreadTimeMillis(), chatBot) + ) + .setSmallIcon(R.drawable.ic_bubble); + } + + private Notification.BubbleMetadata.Builder getBubbleBuilder(BubbleInfo info) { + Intent target = new Intent(mContext, BubbleActivity.class); + target.putExtra(EXTRA_BUBBLE_NOTIF_ID, info.id); + PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, info.id, target, + PendingIntent.FLAG_UPDATE_CURRENT); + + return new Notification.BubbleMetadata.Builder() + .setIntent(bubbleIntent) + .setIcon(info.icon) + .setDesiredHeight(info.height); + } + + public void cancel(int id) { + mNotificationManager.cancel(id); + } + + public void cancelAll() { + mNotificationManager.cancelAll(); + } + + public void cancelLast() { + StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications(); + if (activeNotifications.length > 0) { + mNotificationManager.cancel( + activeNotifications[activeNotifications.length - 1].getId()); + } + } + + public void cancelFirst() { + StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications(); + if (activeNotifications.length > 0) { + mNotificationManager.cancel(activeNotifications[0].getId()); + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java index 0ead91bb37de..0ed59bdafd1d 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java @@ -87,4 +87,16 @@ public class Components { public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".SplitScreenSecondaryActivity"); } + + public static class LaunchBubbleActivity { + public static final String LABEL = "LaunchBubbleApp"; + public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, + PACKAGE_NAME + ".LaunchBubbleActivity"); + } + + public static class BubbleActivity { + public static final String LABEL = "BubbleApp"; + public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME, + PACKAGE_NAME + ".BubbleActivity"); + } } diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java new file mode 100644 index 000000000000..71fa66d8a61c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java @@ -0,0 +1,82 @@ +/* + * 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.flicker.testapp; + + +import android.app.Activity; +import android.app.Person; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.view.View; + +import java.util.Arrays; + +public class LaunchBubbleActivity extends Activity { + + private BubbleHelper mBubbleHelper; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addInboxShortcut(getApplicationContext()); + mBubbleHelper = BubbleHelper.getInstance(this); + setContentView(R.layout.activity_main); + findViewById(R.id.button_create).setOnClickListener(this::add); + findViewById(R.id.button_cancel).setOnClickListener(this::cancel); + findViewById(R.id.button_cancel_all).setOnClickListener(this::cancelAll); + } + + private void add(View v) { + mBubbleHelper.addNewBubble(false /* autoExpand */, false /* suppressNotif */); + } + + private void cancel(View v) { + mBubbleHelper.cancelLast(); + } + + private void cancelAll(View v) { + mBubbleHelper.cancelAll(); + } + + private void addInboxShortcut(Context context) { + Icon icon = Icon.createWithResource(this, R.drawable.bg); + Person[] persons = new Person[4]; + for (int i = 0; i < persons.length; i++) { + persons[i] = new Person.Builder() + .setBot(false) + .setIcon(icon) + .setName("google" + i) + .setImportant(true) + .build(); + } + + ShortcutInfo shortcut = new ShortcutInfo.Builder(context, "BubbleChat") + .setShortLabel("BubbleChat") + .setLongLived(true) + .setIntent(new Intent(Intent.ACTION_VIEW)) + .setIcon(Icon.createWithResource(context, R.drawable.ic_message)) + .setPersons(persons) + .build(); + ShortcutManager scmanager = context.getSystemService(ShortcutManager.class); + scmanager.addDynamicShortcuts(Arrays.asList(shortcut)); + } + +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index b0312e6d6f3c..bc701d0c70bc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -131,7 +131,7 @@ public class BubbleDataTest extends ShellTestCase { NotificationListenerService.Ranking ranking = mock(NotificationListenerService.Ranking.class); - when(ranking.visuallyInterruptive()).thenReturn(true); + when(ranking.isTextChanged()).thenReturn(true); mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking); mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null, mMainExecutor); @@ -793,7 +793,7 @@ public class BubbleDataTest extends ShellTestCase { } @Test - public void test_expanded_removeLastBubble_collapsesStack() { + public void test_expanded_removeLastBubble_showsOverflowIfNotEmpty() { // Setup sendUpdatedEntryAtTime(mEntryA1, 1000); changeExpandedStateAtTime(true, 2000); @@ -802,6 +802,21 @@ public class BubbleDataTest extends ShellTestCase { // Test mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE); verifyUpdateReceived(); + assertThat(mBubbleData.getOverflowBubbles().size()).isGreaterThan(0); + assertSelectionChangedTo(mBubbleData.getOverflow()); + } + + @Test + public void test_expanded_removeLastBubble_collapsesIfOverflowEmpty() { + // Setup + sendUpdatedEntryAtTime(mEntryA1, 1000); + changeExpandedStateAtTime(true, 2000); + mBubbleData.setListener(mListener); + + // Test + mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_NO_BUBBLE_UP); + verifyUpdateReceived(); + assertThat(mBubbleData.getOverflowBubbles()).isEmpty(); assertExpandedChangedTo(false); } @@ -999,15 +1014,15 @@ public class BubbleDataTest extends ShellTestCase { } private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime) { - sendUpdatedEntryAtTime(entry, postTime, true /* visuallyInterruptive */); + sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */); } private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime, - boolean visuallyInterruptive) { + boolean textChanged) { setPostTime(entry, postTime); // BubbleController calls this: Bubble b = mBubbleData.getOrCreateBubble(entry, null /* persistedBubble */); - b.setVisuallyInterruptiveForTest(visuallyInterruptive); + b.setTextChangedForTest(textChanged); // And then this mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/, true /* showInShade */); 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 defa58d7fe93..b4caeb5de4ec 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 @@ -95,13 +95,13 @@ public class SplitLayoutTests extends ShellTestCase { @Test public void testUpdateDivideBounds() { mSplitLayout.updateDivideBounds(anyInt()); - verify(mSplitLayoutHandler).onLayoutChanging(any(SplitLayout.class)); + verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class)); } @Test public void testSetDividePosition() { mSplitLayout.setDividePosition(anyInt()); - verify(mSplitLayoutHandler).onLayoutChanged(any(SplitLayout.class)); + verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java index 1bb5fd1e49e7..2bcc45e2587d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java @@ -56,13 +56,14 @@ public class MainStageTests { MockitoAnnotations.initMocks(this); mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue, - mSurfaceSession); + mSurfaceSession, null); mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash); } @Test public void testActiveDeactivate() { - mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct); + mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct, + true /* reparent */); assertThat(mMainStage.isActive()).isTrue(); mMainStage.deactivate(mWct); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java index 3a2516ec9366..838aa811bb87 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java @@ -64,7 +64,7 @@ public class SideStageTests extends ShellTestCase { MockitoAnnotations.initMocks(this); mRootTask = new TestRunningTaskInfoBuilder().build(); mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, - mSyncQueue, mSurfaceSession); + mSyncQueue, mSurfaceSession, null); mSideStage.onTaskAppeared(mRootTask, mRootLeash); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java index 736566e5b4d3..f90af239db01 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java @@ -18,7 +18,6 @@ package com.android.wm.shell.splitscreen; import static android.view.Display.DEFAULT_DISPLAY; import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; - import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -39,6 +38,10 @@ import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.transition.Transitions; +import java.util.Optional; + +import javax.inject.Provider; + public class SplitTestUtils { static SplitLayout createMockSplitLayout() { @@ -68,10 +71,11 @@ public class SplitTestUtils { MainStage mainStage, SideStage sideStage, DisplayImeController imeController, DisplayInsetsController insetsController, SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, - SplitscreenEventLogger logger) { + SplitscreenEventLogger logger, + Provider<Optional<StageTaskUnfoldController>> unfoldController) { super(context, displayId, syncQueue, rootTDAOrganizer, taskOrganizer, mainStage, sideStage, imeController, insetsController, splitLayout, transitions, - transactionPool, logger); + transactionPool, logger, unfoldController); // Prepare default TaskDisplayArea for testing. mDisplayAreaInfo = new DisplayAreaInfo( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 8dce454eb078..05496b059030 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -74,6 +74,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; +import java.util.Optional; + /** Tests for {@link StageCoordinator} */ @SmallTest @RunWith(AndroidJUnit4.class) @@ -106,16 +108,16 @@ public class SplitTransitionTests extends ShellTestCase { doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire(); mSplitLayout = SplitTestUtils.createMockSplitLayout(); mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mock( - StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession); + StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, null); mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( - StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession); + StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, null); mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, - mLogger); + mLogger, Optional::empty); mSplitScreenTransitions = mStageCoordinator.getSplitTransitions(); doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class)) .when(mTransitions).startTransition(anyInt(), any(), any()); @@ -314,7 +316,8 @@ public class SplitTransitionTests extends ShellTestCase { mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class), mock(Transitions.TransitionFinishCallback.class)); - mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction()); + mMainStage.activate(new Rect(0, 0, 100, 100), new WindowContainerTransaction(), + true /* includingTopTask */); } private boolean containsSplitExit(@NonNull WindowContainerTransaction wct) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index d930d4ca5dd4..cd29220bb96a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -21,15 +21,19 @@ import static android.view.Display.DEFAULT_DISPLAY; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME; import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.graphics.Rect; +import android.window.DisplayAreaInfo; import android.window.WindowContainerTransaction; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -43,6 +47,7 @@ import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; 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.transition.Transitions; import org.junit.Before; @@ -51,29 +56,55 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -/** Tests for {@link StageCoordinator} */ +import java.util.Optional; + +import javax.inject.Provider; + +/** + * Tests for {@link StageCoordinator} + */ @SmallTest @RunWith(AndroidJUnit4.class) public class StageCoordinatorTests extends ShellTestCase { - @Mock private ShellTaskOrganizer mTaskOrganizer; - @Mock private SyncTransactionQueue mSyncQueue; - @Mock private RootTaskDisplayAreaOrganizer mRootTDAOrganizer; - @Mock private MainStage mMainStage; - @Mock private SideStage mSideStage; - @Mock private DisplayImeController mDisplayImeController; - @Mock private DisplayInsetsController mDisplayInsetsController; - @Mock private Transitions mTransitions; - @Mock private TransactionPool mTransactionPool; - @Mock private SplitscreenEventLogger mLogger; + @Mock + private ShellTaskOrganizer mTaskOrganizer; + @Mock + private SyncTransactionQueue mSyncQueue; + @Mock + private RootTaskDisplayAreaOrganizer mRootTDAOrganizer; + @Mock + private MainStage mMainStage; + @Mock + private SideStage mSideStage; + @Mock + private StageTaskUnfoldController mMainUnfoldController; + @Mock + private StageTaskUnfoldController mSideUnfoldController; + @Mock + private SplitLayout mSplitLayout; + @Mock + private DisplayImeController mDisplayImeController; + @Mock + private DisplayInsetsController mDisplayInsetsController; + @Mock + private Transitions mTransitions; + @Mock + private TransactionPool mTransactionPool; + @Mock + private SplitscreenEventLogger mLogger; + + private final Rect mBounds1 = new Rect(10, 20, 30, 40); + private final Rect mBounds2 = new Rect(5, 10, 15, 20); + private StageCoordinator mStageCoordinator; @Before public void setup() { MockitoAnnotations.initMocks(this); - mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, - mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage, - mDisplayImeController, mDisplayInsetsController, null /* splitLayout */, - mTransitions, mTransactionPool, mLogger); + mStageCoordinator = createStageCoordinator(/* splitLayout */ null); + + when(mSplitLayout.getBounds1()).thenReturn(mBounds1); + when(mSplitLayout.getBounds2()).thenReturn(mBounds2); } @Test @@ -82,12 +113,45 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator.moveToSideStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT); - verify(mMainStage).activate(any(Rect.class), any(WindowContainerTransaction.class)); + verify(mMainStage).activate(any(Rect.class), any(WindowContainerTransaction.class), + eq(true /* includingTopTask */)); verify(mSideStage).addTask(eq(task), any(Rect.class), any(WindowContainerTransaction.class)); } @Test + public void testDisplayAreaAppeared_initializesUnfoldControllers() { + mStageCoordinator.onDisplayAreaAppeared(mock(DisplayAreaInfo.class)); + + verify(mMainUnfoldController).init(); + verify(mSideUnfoldController).init(); + } + + @Test + public void testLayoutChanged_topLeftSplitPosition_updatesUnfoldStageBounds() { + mStageCoordinator = createStageCoordinator(mSplitLayout); + mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null); + clearInvocations(mMainUnfoldController, mSideUnfoldController); + + mStageCoordinator.onLayoutSizeChanged(mSplitLayout); + + verify(mMainUnfoldController).onLayoutChanged(mBounds2); + verify(mSideUnfoldController).onLayoutChanged(mBounds1); + } + + @Test + public void testLayoutChanged_bottomRightSplitPosition_updatesUnfoldStageBounds() { + mStageCoordinator = createStageCoordinator(mSplitLayout); + mStageCoordinator.setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, null); + clearInvocations(mMainUnfoldController, mSideUnfoldController); + + mStageCoordinator.onLayoutSizeChanged(mSplitLayout); + + verify(mMainUnfoldController).onLayoutChanged(mBounds1); + verify(mSideUnfoldController).onLayoutChanged(mBounds2); + } + + @Test public void testRemoveFromSideStage() { final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); @@ -131,4 +195,27 @@ public class StageCoordinatorTests extends ShellTestCase { verify(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(true)); verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false)); } + + private StageCoordinator createStageCoordinator(SplitLayout splitLayout) { + return new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, + mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage, + mDisplayImeController, mDisplayInsetsController, splitLayout, + mTransitions, mTransactionPool, mLogger, new UnfoldControllerProvider()); + } + + private class UnfoldControllerProvider implements + Provider<Optional<StageTaskUnfoldController>> { + + private boolean isMain = true; + + @Override + public Optional<StageTaskUnfoldController> get() { + if (isMain) { + isMain = false; + return Optional.of(mMainUnfoldController); + } else { + return Optional.of(mSideUnfoldController); + } + } + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java index 0916dd1f71bd..a5746a49da2b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -60,6 +61,7 @@ public final class StageTaskListenerTests { @Mock private ShellTaskOrganizer mTaskOrganizer; @Mock private StageTaskListener.StageListenerCallbacks mCallbacks; @Mock private SyncTransactionQueue mSyncQueue; + @Mock private StageTaskUnfoldController mStageTaskUnfoldController; @Captor private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor; private SurfaceSession mSurfaceSession = new SurfaceSession(); private SurfaceControl mSurfaceControl; @@ -74,7 +76,8 @@ public final class StageTaskListenerTests { DEFAULT_DISPLAY, mCallbacks, mSyncQueue, - mSurfaceSession); + mSurfaceSession, + mStageTaskUnfoldController); mRootTask = new TestRunningTaskInfoBuilder().build(); mRootTask.parentTaskId = INVALID_TASK_ID; mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build(); @@ -111,6 +114,28 @@ public final class StageTaskListenerTests { verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true)); } + @Test + public void testTaskAppeared_notifiesUnfoldListener() { + final ActivityManager.RunningTaskInfo task = + new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build(); + + mStageTaskListener.onTaskAppeared(task, mSurfaceControl); + + verify(mStageTaskUnfoldController).onTaskAppeared(eq(task), eq(mSurfaceControl)); + } + + @Test + public void testTaskVanished_notifiesUnfoldListener() { + final ActivityManager.RunningTaskInfo task = + new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build(); + mStageTaskListener.onTaskAppeared(task, mSurfaceControl); + clearInvocations(mStageTaskUnfoldController); + + mStageTaskListener.onTaskVanished(task); + + verify(mStageTaskUnfoldController).onTaskVanished(eq(task)); + } + @Test(expected = IllegalArgumentException.class) public void testUnknownTaskVanished() { final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 2c299fa32315..2b31bcf78890 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -570,6 +570,7 @@ cc_defaults { "renderthread/DrawFrameTask.cpp", "renderthread/EglManager.cpp", "renderthread/ReliableSurface.cpp", + "renderthread/RenderEffectCapabilityQuery.cpp", "renderthread/VulkanManager.cpp", "renderthread/VulkanSurface.cpp", "renderthread/RenderProxy.cpp", @@ -696,6 +697,7 @@ cc_test { "tests/unit/MatrixTests.cpp", "tests/unit/OpBufferTests.cpp", "tests/unit/PathInterpolatorTests.cpp", + "tests/unit/RenderEffectCapabilityQueryTests.cpp", "tests/unit/RenderNodeDrawableTests.cpp", "tests/unit/RenderNodeTests.cpp", "tests/unit/RenderPropertiesTests.cpp", diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 35449875d324..475fd700ccc9 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -50,7 +50,8 @@ bool Properties::showDirtyRegions = false; bool Properties::skipEmptyFrames = true; bool Properties::useBufferAge = true; bool Properties::enablePartialUpdates = true; -bool Properties::enableRenderEffectCache = false; +// Default true unless otherwise specified in RenderThread Configuration +bool Properties::enableRenderEffectCache = true; DebugLevel Properties::debugLevel = kDebugDisabled; OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default; diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 383c79b27918..c7d7a17a23eb 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -28,6 +28,7 @@ #include "Frame.h" #include "Properties.h" +#include "RenderEffectCapabilityQuery.h" #include "utils/Color.h" #include "utils/StringUtils.h" @@ -148,7 +149,11 @@ void EglManager::initialize() { mHasWideColorGamutSupport = EglExtensions.glColorSpace && hasWideColorSpaceExtension; auto* vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR)); - Properties::enableRenderEffectCache = (strcmp(vendor, "Qualcomm") != 0); + auto* version = reinterpret_cast<const char*>(glGetString(GL_VERSION)); + Properties::enableRenderEffectCache = supportsRenderEffectCache( + vendor, version); + ALOGV("RenderEffectCache supported %d on driver version %s", + Properties::enableRenderEffectCache, version); } EGLConfig EglManager::load8BitsConfig(EGLDisplay display, EglManager::SwapBehavior swapBehavior) { diff --git a/libs/hwui/renderthread/RenderEffectCapabilityQuery.cpp b/libs/hwui/renderthread/RenderEffectCapabilityQuery.cpp new file mode 100644 index 000000000000..a003988575c8 --- /dev/null +++ b/libs/hwui/renderthread/RenderEffectCapabilityQuery.cpp @@ -0,0 +1,39 @@ +/* + * 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. + */ +#include <stdio.h> +#include <string.h> +#include <utils/Log.h> + +bool supportsRenderEffectCache(const char* vendor, const char* version) { + if (strcmp(vendor, "Qualcomm") != 0) { + return true; + } + + int major; + int minor; + int driverMajor; + int driverMinor; + int n = sscanf(version,"OpenGL ES %d.%d V@%d.%d", + &major, + &minor, + &driverMajor, + &driverMinor); + // Ensure we have parsed the vendor string properly and we have either + // a newer major driver version, or the minor version is rev'ed + // Based on b/198227600#comment5 it appears that the corresponding fix + // is in driver version 571.0 + return n == 4 && driverMajor >= 571; +}
\ No newline at end of file diff --git a/libs/hwui/renderthread/RenderEffectCapabilityQuery.h b/libs/hwui/renderthread/RenderEffectCapabilityQuery.h new file mode 100644 index 000000000000..ea673dd0386d --- /dev/null +++ b/libs/hwui/renderthread/RenderEffectCapabilityQuery.h @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#pragma once + +/** + * Verify if the provided vendor and version supports RenderEffect caching + * behavior. + * + * Certain Open GL Driver implementations run into blocking scenarios + * with Fence::waitForever without a corresponding signal to unblock + * This happens during attempts to cache SkImage instances across frames + * especially in circumstances using RenderEffect/SkImageFilter internally. + * So detect the corresponding GL Vendor and driver version to determine if + * caching SkImage instances across frames is supported. + * See b/197263715 & b/193145089 + * @param vendor Vendor of the GL driver + * @param version Version of the GL driver from the given vendor + * @return True if a RenderEffect result can be cached across frames, + * false otherwise + */ +bool supportsRenderEffectCache(const char* vendor, const char* version); diff --git a/libs/hwui/tests/unit/EglManagerTests.cpp b/libs/hwui/tests/unit/EglManagerTests.cpp index f7f240663397..7f2e1589ae6c 100644 --- a/libs/hwui/tests/unit/EglManagerTests.cpp +++ b/libs/hwui/tests/unit/EglManagerTests.cpp @@ -17,6 +17,7 @@ #include <gtest/gtest.h> #include "renderthread/EglManager.h" +#include "renderthread/RenderEffectCapabilityQuery.h" #include "tests/common/TestContext.h" using namespace android; @@ -41,4 +42,17 @@ TEST(EglManager, doesSurfaceLeak) { } eglManager.destroy(); +} + +TEST(EglManager, verifyRenderEffectCacheSupported) { + EglManager eglManager; + eglManager.initialize(); + auto* vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR)); + auto* version = reinterpret_cast<const char*>(glGetString(GL_VERSION)); + // Make sure that EglManager initializes Properties::enableRenderEffectCache + // based on the given gl vendor and version within EglManager->initialize() + bool renderEffectCacheSupported = supportsRenderEffectCache(vendor, version); + EXPECT_EQ(renderEffectCacheSupported, + Properties::enableRenderEffectCache); + eglManager.destroy(); }
\ No newline at end of file diff --git a/libs/hwui/tests/unit/RenderEffectCapabilityQueryTests.cpp b/libs/hwui/tests/unit/RenderEffectCapabilityQueryTests.cpp new file mode 100644 index 000000000000..0ee654929b3b --- /dev/null +++ b/libs/hwui/tests/unit/RenderEffectCapabilityQueryTests.cpp @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#include <gtest/gtest.h> +#include "renderthread/RenderEffectCapabilityQuery.h" +#include "tests/common/TestContext.h" + +TEST(RenderEffectCapabilityQuery, testSupportedVendor) { + ASSERT_TRUE(supportsRenderEffectCache("Google", "OpenGL ES 1.4 V@0.0")); +} + +TEST(RenderEffectCapabilityQuery, testSupportedVendorWithDifferentVersion) { + ASSERT_TRUE(supportsRenderEffectCache("Google", "OpenGL ES 1.3 V@571.0")); +} + +TEST(RenderEffectCapabilityQuery, testVendorWithSupportedVersion) { + ASSERT_TRUE(supportsRenderEffectCache("Qualcomm", "OpenGL ES 1.5 V@571.0")); +} + +TEST(RenderEffectCapabilityQuery, testVendorWithSupportedPatchVersion) { + ASSERT_TRUE(supportsRenderEffectCache("Qualcomm", "OpenGL ES 1.5 V@571.1")); +} + +TEST(RenderEffectCapabilityQuery, testVendorWithNewerThanSupportedMajorVersion) { + ASSERT_TRUE(supportsRenderEffectCache("Qualcomm", "OpenGL ES 1.5 V@572.0")); +} + +TEST(RenderEffectCapabilityQuery, testVendorWithNewerThanSupportedMinorVersion) { + ASSERT_TRUE(supportsRenderEffectCache("Qualcomm", "OpenGL ES 1.5 V@571.2")); +} + +TEST(RenderEffectCapabilityQuery, testVendorWithUnsupportedMajorVersion) { + ASSERT_FALSE(supportsRenderEffectCache("Qualcomm", "OpenGL ES 1.0 V@570.1")); +} + +TEST(RenderEffectCapabilityQuery, testVendorWithUnsupportedVersion) { + ASSERT_FALSE(supportsRenderEffectCache("Qualcomm", "OpenGL ES 1.1 V@570.0")); +} + diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index aa72d965f793..476a9a58ef4b 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -1805,9 +1805,15 @@ public class AudioTrack extends PlayerBase return false; } final int channelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig); - final int channelCountLimit = AudioFormat.isEncodingLinearFrames(encoding) - ? AudioSystem.OUT_CHANNEL_COUNT_MAX // PCM limited to OUT_CHANNEL_COUNT_MAX - : AudioSystem.FCC_24; // Compressed limited to 24 channels + final int channelCountLimit; + try { + channelCountLimit = AudioFormat.isEncodingLinearFrames(encoding) + ? AudioSystem.OUT_CHANNEL_COUNT_MAX // PCM limited to OUT_CHANNEL_COUNT_MAX + : AudioSystem.FCC_24; // Compressed limited to 24 channels + } catch (IllegalArgumentException iae) { + loge("Unsupported encoding " + iae); + return false; + } if (channelCount > channelCountLimit) { loge("Channel configuration contains too many channels for encoding " + encoding + "(" + channelCount + " > " + channelCountLimit + ")"); diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 6d52b664cee9..abd067cfe2f3 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -38,6 +38,7 @@ import android.media.IStrategyPreferredDevicesDispatcher; import android.media.ISpatializerCallback; import android.media.ISpatializerHeadTrackingModeCallback; import android.media.ISpatializerHeadToSoundStagePoseCallback; +import android.media.ISpatializerOutputCallback; import android.media.IVolumeController; import android.media.IVolumeController; import android.media.PlayerBase; @@ -440,4 +441,10 @@ interface IAudioService { void setSpatializerParameter(int key, in byte[] value); void getSpatializerParameter(int key, inout byte[] value); + + int getSpatializerOutput(); + + void registerSpatializerOutputCallback(in ISpatializerOutputCallback cb); + + void unregisterSpatializerOutputCallback(in ISpatializerOutputCallback cb); } diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index 48289ecde9e0..25b582d2fc8d 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -39,6 +39,7 @@ interface IMediaRouterService { MediaRouterClientState getState(IMediaRouterClient client); boolean isPlaybackActive(IMediaRouterClient client); + void setBluetoothA2dpOn(IMediaRouterClient client, boolean on); void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan); void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit); void requestSetVolume(IMediaRouterClient client, String routeId, int volume); diff --git a/media/java/android/media/ISpatializerOutputCallback.aidl b/media/java/android/media/ISpatializerOutputCallback.aidl new file mode 100644 index 000000000000..57572a81a366 --- /dev/null +++ b/media/java/android/media/ISpatializerOutputCallback.aidl @@ -0,0 +1,27 @@ +/* + * 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 output changes. + * + * {@hide} + */ +oneway interface ISpatializerOutputCallback { + + void dispatchSpatializerOutputChanged(int output); +} diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 9bf0db52f66d..028ae2b3487c 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -683,6 +683,19 @@ public final class MediaFormat { public static final String KEY_CHANNEL_MASK = "channel-mask"; /** + * A key describing the maximum number of channels that can be output by an audio decoder. + * By default, the decoder will output the same number of channels as present in the encoded + * stream, if supported. Set this value to limit the number of output channels, and use + * the downmix information in the stream, if available. + * <p>Values larger than the number of channels in the content to decode behave like the number + * of channels in the content (if applicable), for instance passing 99 for a 5.1 audio stream + * behaves like passing 6. + * <p>This key is only used during decoding. + */ + public static final String KEY_MAX_OUTPUT_CHANNEL_COUNT = + "max-output-channel_count"; + + /** * A key describing the number of frames to trim from the start of the decoded audio stream. * The associated value is an integer. */ diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index 345d9b27c8a8..748ae52d6c0c 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -1087,7 +1087,8 @@ public class MediaRouter { && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 && (route.isBluetooth() || route.isDefault())) { try { - sStatic.mAudioService.setBluetoothA2dpOn(route.isBluetooth()); + sStatic.mMediaRouterService.setBluetoothA2dpOn(sStatic.mClient, + route.isBluetooth()); } catch (RemoteException e) { Log.e(TAG, "Error changing Bluetooth A2DP state", e); } diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java index 8b1624b5eb16..e6fff392c264 100644 --- a/media/java/android/media/Spatializer.java +++ b/media/java/android/media/Spatializer.java @@ -18,6 +18,7 @@ package android.media; import android.annotation.CallbackExecutor; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -63,7 +64,9 @@ public class Spatializer { /** * Returns whether spatialization is enabled or not. * A false value can originate for instance from the user electing to - * disable the feature.<br> + * disable the feature, or when the feature is not supported on the device (indicated + * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}). + * <br> * Note that this state reflects a platform-wide state of the "desire" to use spatialization, * but availability of the audio processing is still dictated by the compatibility between * the effect and the hardware configuration, as indicated by {@link #isAvailable()}. @@ -85,7 +88,10 @@ public class Spatializer { * incompatible with sound spatialization, such as playback on a monophonic speaker.<br> * Note that spatialization can be available, but disabled by the user, in which case this * method would still return {@code true}, whereas {@link #isEnabled()} - * would return {@code false}. + * would return {@code false}.<br> + * Also when the feature is not supported on the device (indicated + * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}), + * the return value will be false. * @return {@code true} if the spatializer effect is available and capable * of processing the audio for the current configuration of the device, * {@code false} otherwise. @@ -293,6 +299,24 @@ public class Spatializer { @HeadTrackingModeSet int mode); } + + /** + * @hide + * An interface to be notified of changes to the output stream used by the spatializer + * effect. + * @see #getOutput() + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + public interface OnSpatializerOutputChangedListener { + /** + * Called when the id of the output stream of the spatializer effect changed. + * @param spatializer the {@code Spatializer} instance whose output is updated + * @param output the id of the output stream, or 0 when there is no spatializer output + */ + void onSpatializerOutputChanged(@NonNull Spatializer spatializer, + @IntRange(from = 0) int output); + } + /** * @hide * An interface to be notified of updates to the head to soundstage pose, as represented by the @@ -839,6 +863,73 @@ public class Spatializer { } } + /** + * @hide + * Returns the id of the output stream used for the spatializer effect playback + * @return id of the output stream, or 0 if no spatializer playback is active + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public @IntRange(from = 0) int getOutput() { + try { + return mAm.getService().getSpatializerOutput(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling getSpatializerOutput", e); + return 0; + } + } + + /** + * @hide + * Sets the listener to receive spatializer effect output updates + * @param executor the {@code Executor} handling the callbacks + * @param listener the listener to register + * @see #clearOnSpatializerOutputChangedListener() + * @see #getOutput() + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public void setOnSpatializerOutputChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnSpatializerOutputChangedListener listener) { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + synchronized (mOutputListenerLock) { + if (mOutputListener != null) { + throw new IllegalStateException("Trying to overwrite existing listener"); + } + mOutputListener = + new ListenerInfo<OnSpatializerOutputChangedListener>(listener, executor); + mOutputDispatcher = new SpatializerOutputDispatcherStub(); + try { + mAm.getService().registerSpatializerOutputCallback(mOutputDispatcher); + } catch (RemoteException e) { + mOutputListener = null; + mOutputDispatcher = null; + } + } + } + + /** + * @hide + * Clears the listener for spatializer effect output updates + * @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener) + */ + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public void clearOnSpatializerOutputChangedListener() { + synchronized (mOutputListenerLock) { + if (mOutputDispatcher == null) { + throw (new IllegalStateException("No listener to clear")); + } + try { + mAm.getService().unregisterSpatializerOutputCallback(mOutputDispatcher); + } catch (RemoteException e) { } + mOutputListener = null; + mOutputDispatcher = null; + } + } + //----------------------------------------------------------------------------- // callback helper definitions @@ -964,4 +1055,35 @@ public class Spatializer { } } } + + //----------------------------------------------------------------------------- + // output callback management and stub + private final Object mOutputListenerLock = new Object(); + /** + * Listener for output updates + */ + @GuardedBy("mOutputListenerLock") + private @Nullable ListenerInfo<OnSpatializerOutputChangedListener> mOutputListener; + @GuardedBy("mOutputListenerLock") + private @Nullable SpatializerOutputDispatcherStub mOutputDispatcher; + + private final class SpatializerOutputDispatcherStub + extends ISpatializerOutputCallback.Stub { + + @Override + public void dispatchSpatializerOutputChanged(int output) { + // make a copy of ref to listener so callback is not executed under lock + final ListenerInfo<OnSpatializerOutputChangedListener> listener; + synchronized (mOutputListenerLock) { + listener = mOutputListener; + } + if (listener == null) { + return; + } + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + listener.mExecutor.execute(() -> listener.mListener + .onSpatializerOutputChanged(Spatializer.this, output)); + } + } + } } diff --git a/packages/PrintSpooler/res/values-pa/strings.xml b/packages/PrintSpooler/res/values-pa/strings.xml index 601fa836902d..ddcec40bd643 100644 --- a/packages/PrintSpooler/res/values-pa/strings.xml +++ b/packages/PrintSpooler/res/values-pa/strings.xml @@ -50,8 +50,8 @@ <string name="search" msgid="5421724265322228497">"ਖੋਜੋ"</string> <string name="all_printers_label" msgid="3178848870161526399">"ਸਾਰੇ ਪ੍ਰਿੰਟਰ"</string> <string name="add_print_service_label" msgid="5356702546188981940">"ਸੇਵਾ ਸ਼ਾਮਲ ਕਰੋ"</string> - <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ਖੋਜ ਬਾਕਸ ਦਿਖਾਇਆ"</string> - <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"ਖੋਜ ਬਾਕਸ ਲੁਕਾਇਆ"</string> + <string name="print_search_box_shown_utterance" msgid="7967404953901376090">"ਖੋਜ ਬਾਕਸ ਦਿਖਾਇਆ ਗਿਆ"</string> + <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"ਖੋਜ ਬਾਕਸ ਲੁਕਾਇਆ ਗਿਆ"</string> <string name="print_add_printer" msgid="1088656468360653455">"ਪ੍ਰਿੰਟਰ ਸ਼ਾਮਲ ਕਰੋ"</string> <string name="print_select_printer" msgid="7388760939873368698">"ਪ੍ਰਿੰਟਰ ਚੁਣੋ"</string> <string name="print_forget_printer" msgid="5035287497291910766">"ਪ੍ਰਿੰਟਰ ਭੁੱਲੋ"</string> diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index 127f571a5529..940e5d3cac1b 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -164,11 +164,11 @@ <string name="tts_default_lang_title" msgid="4698933575028098940">"ਭਾਸ਼ਾ"</string> <string name="tts_lang_use_system" msgid="6312945299804012406">"ਸਿਸਟਮ ਭਾਸ਼ਾ ਵਰਤੋ"</string> <string name="tts_lang_not_selected" msgid="7927823081096056147">"ਭਾਸ਼ਾ ਨਹੀਂ ਚੁਣੀ"</string> - <string name="tts_default_lang_summary" msgid="9042620014800063470">"ਬੋਲੇ ਗਏ ਟੈਕਸਟ ਲਈ ਭਾਸ਼ਾ-ਵਿਸ਼ੇਸ਼ ਵੌਇਸ ਸੈਟ ਕਰਦਾ ਹੈ"</string> + <string name="tts_default_lang_summary" msgid="9042620014800063470">"ਬੋਲੀ ਗਈ ਲਿਖਤ ਲਈ ਭਾਸ਼ਾ-ਵਿਸ਼ੇਸ਼ ਅਵਾਜ਼ ਸੈੱਟ ਕਰਦਾ ਹੈ"</string> <string name="tts_play_example_title" msgid="1599468547216481684">"ਇੱਕ ਉਦਾਹਰਨ ਲਈ ਸੁਣੋ"</string> <string name="tts_play_example_summary" msgid="634044730710636383">"ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਦਾ ਇੱਕ ਛੋਟਾ ਪ੍ਰਦਰਸ਼ਨ ਪਲੇ ਕਰੋ"</string> - <string name="tts_install_data_title" msgid="1829942496472751703">"ਵੌਇਸ ਡਾਟਾ ਸਥਾਪਤ ਕਰੋ"</string> - <string name="tts_install_data_summary" msgid="3608874324992243851">"ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਲਈ ਲੋੜੀਂਦਾ ਵੌਇਸ ਡਾਟਾ ਸਥਾਪਤ ਕਰੋ"</string> + <string name="tts_install_data_title" msgid="1829942496472751703">"ਅਵਾਜ਼ੀ ਡਾਟਾ ਸਥਾਪਤ ਕਰੋ"</string> + <string name="tts_install_data_summary" msgid="3608874324992243851">"ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਲਈ ਲੋੜੀਂਦਾ ਅਵਾਜ਼ੀ ਡਾਟਾ ਸਥਾਪਤ ਕਰੋ"</string> <string name="tts_engine_security_warning" msgid="3372432853837988146">"ਇਹ ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਇੰਜਣ ਉਹ ਸਭ ਲਿਖਤ ਇਕੱਤਰ ਕਰਨ ਵਿੱਚ ਸਮਰੱਥ ਹੋ ਸਕਦਾ ਹੈ, ਜੋ ਬੋਲਿਆ ਜਾਏਗਾ, ਨਿੱਜੀ ਡਾਟਾ ਸਮੇਤ ਜਿਵੇਂ ਪਾਸਵਰਡ ਅਤੇ ਕ੍ਰੈਡਿਟ ਕਾਰਡ ਨੰਬਰ। ਇਹ <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> ਇੰਜਣ ਤੋਂ ਆਉਂਦਾ ਹੈ। ਕੀ ਇਸ ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਇੰਜਣ ਦੀ ਵਰਤੋਂ ਨੂੰ ਚਾਲੂ ਕਰਨਾ ਹੈੈ?"</string> <string name="tts_engine_network_required" msgid="8722087649733906851">"ਇਸ ਭਾਸ਼ਾ ਲਈ ਲਿਖਤ ਤੋਂ ਬੋਲੀ ਆਊਟਪੁੱਟ ਲਈ ਇੱਕ ਚਾਲੂ ਨੈੱਟਵਰਕ ਕਨੈਕਸ਼ਨ ਦੀ ਲੋੜ ਹੈ।"</string> <string name="tts_default_sample_string" msgid="6388016028292967973">"ਇਹ ਸਪੀਚ ਸਿੰਥੈਸਿਸ ਦਾ ਇੱਕ ਉਦਾਹਰਨ ਹੈ"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index 22001c9c925a..c40b7b7d88af 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -479,9 +479,9 @@ public class LocalMediaManager implements BluetoothCallback { @Override public void onDeviceListAdded(List<MediaDevice> devices) { synchronized (mMediaDevicesLock) { + Collections.sort(devices, COMPARATOR); mMediaDevices.clear(); mMediaDevices.addAll(devices); - Collections.sort(devices, COMPARATOR); // Add disconnected bluetooth devices only when phone output device is available. for (MediaDevice device : devices) { final int type = device.getDeviceType(); diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 6671308ef66a..c6cca5adaeb0 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -50,6 +50,16 @@ java_library { srcs: ["src/com/android/systemui/EventLogTags.logtags"], } +filegroup { + name: "ReleaseJavaFiles", + srcs: ["src/com/android/systemui/flags/FeatureFlagManager.java"], +} + +filegroup { + name: "DebugJavaFiles", + srcs: ["src-debug/com/android/systemui/flags/FeatureFlagManager.java"], +} + android_library { name: "SystemUI-core", srcs: [ @@ -57,6 +67,12 @@ android_library { "src/**/*.java", "src/**/I*.aidl", ], + product_variables: { + debuggable: { + srcs: [":DebugJavaFiles"], + exclude_srcs: [":ReleaseJavaFiles"], + }, + }, resource_dirs: [ "res-product", "res-keyguard", diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 9de1c5ea1a3d..58e3d398553c 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -183,6 +183,9 @@ <permission android:name="com.android.systemui.permission.PLUGIN" android:protectionLevel="signature" /> + <permission android:name="com.android.systemui.permission.FLAGS" + android:protectionLevel="signature" /> + <!-- Adding Quick Settings tiles --> <uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" /> diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index f7e0d588407f..3ccf5e4fbdd0 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -209,8 +209,13 @@ open class GhostedViewLaunchAnimatorController( val heightRatio = state.height.toFloat() / ghostedViewState.height val scale = min(widthRatio, heightRatio) + if (ghostedView.parent is ViewGroup) { + // Recalculate the matrix in case the ghosted view moved. We ensure that the ghosted + // view is still attached to a ViewGroup, otherwise calculateMatrix will throw. + GhostView.calculateMatrix(ghostedView, launchContainer, ghostViewMatrix) + } + launchContainer.getLocationOnScreen(launchContainerLocation) - GhostView.calculateMatrix(ghostedView, launchContainer, ghostViewMatrix) ghostViewMatrix.postScale( scale, scale, ghostedViewState.centerX - launchContainerLocation[0], diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java index 27658824933a..80634832acd9 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java @@ -197,26 +197,6 @@ public class Interpolators { return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress))); } - /** - * Interpolate alpha for notifications background scrim during shade expansion. - * @param fraction Shade expansion fraction - * @param forNotification If we want the alpha of the notification shade or the scrim. - */ - public static float getNotificationScrimAlpha(float fraction, boolean forNotification) { - if (forNotification) { - fraction = MathUtils.constrainedMap(0f, 1f, 0.3f, 1f, fraction); - } else { - fraction = MathUtils.constrainedMap(0f, 1f, 0f, 0.5f, fraction); - } - fraction = fraction * 1.2f - 0.2f; - if (fraction <= 0) { - return 0; - } else { - final float oneMinusFrac = 1f - fraction; - return (float) (1f - 0.5f * (1f - Math.cos(3.14159f * oneMinusFrac * oneMinusFrac))); - } - } - // Create the default emphasized interpolator private static PathInterpolator createEmphasizedInterpolator() { Path path = new Path(); diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt new file mode 100644 index 000000000000..0ee2bfea55c5 --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt @@ -0,0 +1,37 @@ +package com.android.systemui.animation + +import android.util.MathUtils + +object ShadeInterpolation { + + /** + * Interpolate alpha for notification background scrim during shade expansion. + * @param fraction Shade expansion fraction + */ + @JvmStatic + fun getNotificationScrimAlpha(fraction: Float): Float { + val mappedFraction = MathUtils.constrainedMap(0f, 1f, 0f, 0.5f, fraction) + return interpolateEaseInOut(mappedFraction) + } + + /** + * Interpolate alpha for shade content during shade expansion. + * @param fraction Shade expansion fraction + */ + @JvmStatic + fun getContentAlpha(fraction: Float): Float { + val mappedFraction = MathUtils.constrainedMap(0f, 1f, 0.3f, 1f, fraction) + return interpolateEaseInOut(mappedFraction) + } + + private fun interpolateEaseInOut(fraction: Float): Float { + val mappedFraction = fraction * 1.2f - 0.2f + return if (mappedFraction <= 0) { + 0f + } else { + val oneMinusFrac = 1f - mappedFraction + (1f - 0.5f * (1f - Math.cos((3.14159f * oneMinusFrac * oneMinusFrac).toDouble()))) + .toFloat() + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/docs/keyguard.md b/packages/SystemUI/docs/keyguard.md index e3d48aee96e4..5e7bc1c8cad2 100644 --- a/packages/SystemUI/docs/keyguard.md +++ b/packages/SystemUI/docs/keyguard.md @@ -8,5 +8,39 @@ Keyguard is responsible for: Keyguard is the first screen available when turning on the device, as long as the user has not specified a security method of NONE. +## Critical User Journeys + +The journeys below generally refer to Keyguard's portion of the overall flow, especially regarding use of the power button. Power button key interpretation (short press, long press, very long press, multi press) is done in [PhoneWindowManager][4], with calls to [PowerManagerService][2] to sleep or wake up, if needed. + +### Power On - AOD enabled or disabled + +Begins with the device in low power mode, with the display active for [AOD][3] or inactive. [PowerManagerService][2] can be directed to wake up on various user-configurable signals, such as lift to wake, screen taps, among others. [AOD][2], whether visibly enabled or not, handles these signals to transition AOD to full Lockscreen content. See more in [AOD][3]. + +### Power Off + +An indication to power off the device most likely comes from one of two signals: the user presses the power button or the screen timeout has passed. This may [lock the device](#How-the-device-locks) + +#### On Lockscreen + +#### On Lockscreen, occluded by an activity + +#### Device unlocked, Keyguard has gone away + +### Pulsing (Incoming notifications while dozing) + +### How the device locks + +More coming +* Screen timeout +* Smart lock +* Device policy +* Power button instantly locks setting +* Lock timeout after screen timeout setting + + [1]: /frameworks/base/packages/SystemUI/docs/keyguard/bouncer.md +[2]: /frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java +[3]: /frameworks/base/packages/SystemUI/docs/keyguard/aod.md +[4]: /frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java + diff --git a/packages/SystemUI/docs/keyguard/aod.md b/packages/SystemUI/docs/keyguard/aod.md new file mode 100644 index 000000000000..6d76ed55174a --- /dev/null +++ b/packages/SystemUI/docs/keyguard/aod.md @@ -0,0 +1 @@ +# Always-on Display (AOD) diff --git a/packages/SystemUI/docs/keyguard/bouncer.md b/packages/SystemUI/docs/keyguard/bouncer.md index a724966a639a..51f851608a34 100644 --- a/packages/SystemUI/docs/keyguard/bouncer.md +++ b/packages/SystemUI/docs/keyguard/bouncer.md @@ -7,9 +7,9 @@ The bouncer contains a hierarchy of controllers/views to render the user's security method and to manage the authentication attempts. 1. [KeyguardBouncer][1] - Entrypoint for managing the bouncer visibility. - 1. [KeyguardHostViewController][2] - Intercepts media keys. Can most likely be merged with the next item. - 1. [KeyguardSecurityContainerController][3] - Manages unlock attempt responses, one-handed use - 1. [KeyguardSecurityViewFlipperController][4] - Based upon the [KeyguardSecurityModel#SecurityMode][5], will instantiate the required view and controller. PIN, Pattern, etc. + 1. [KeyguardHostViewController][2] - Intercepts media keys. Can most likely be merged with the next item. + 1. [KeyguardSecurityContainerController][3] - Manages unlock attempt responses, one-handed use + 1. [KeyguardSecurityViewFlipperController][4] - Based upon the [KeyguardSecurityModel#SecurityMode][5], will instantiate the required view and controller. PIN, Pattern, etc. [1]: /frameworks/base/packages/SystemUI/com/android/systemui/statusbar/phone/KeyguardBouncer [2]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardHostViewController diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt index e026012f9de8..1844288796cc 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -249,6 +249,9 @@ public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) { val population = populationByColor[entry.key]!! val cam = camByColor[entry.key]!! val hue = cam.hue.roundToInt() % 360 + if (cam.chroma <= MIN_CHROMA) { + continue + } huePopulation[hue] = huePopulation[hue] + population } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java index d5b9243ebe86..3761d42ae98c 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/flags/Flags.java @@ -16,8 +16,114 @@ package com.android.systemui.flags; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + /** * List of {@link Flag} objects for use in SystemUI. + * + * Flag Ids are integers. + * Ids must be unique. This is enforced in a unit test. + * Ids need not be sequential. Flags can "claim" a chunk of ids for flags in related featurs with + * a comment. This is purely for organizational purposes. + * + * On public release builds, flags will always return their default value. There is no way to + * change their value on release builds. + * + * See {@link FeatureFlagManager} for instructions on flipping the flags via adb. */ public class Flags { + public static final BooleanFlag TEAMFOOD = new BooleanFlag(1, false); + + /***************************************/ + // 100 - notification + public static final BooleanFlag NEW_NOTIFICATION_PIPELINE = + new BooleanFlag(100, true); + + public static final BooleanFlag NEW_NOTIFICATION_PIPELINE_RENDERING = + new BooleanFlag(101, false); + + public static final BooleanFlag NOTIFICATION_UPDATES = + new BooleanFlag(102, true); + + + /***************************************/ + // 200 - keyguard/lockscreen + public static final BooleanFlag KEYGUARD_LAYOUT = + new BooleanFlag(200, true); + + public static final BooleanFlag LOCKSCREEN_ANIMATIONS = + new BooleanFlag(201, true); + + public static final BooleanFlag NEW_UNLOCK_SWIPE_ANIMATION = + new BooleanFlag(202, true); + + /***************************************/ + // 300 - power menu + public static final BooleanFlag POWER_MENU_LITE = + new BooleanFlag(300, true); + + /***************************************/ + // 400 - smartspace + public static final BooleanFlag SMARTSPACE_DEDUPING = + new BooleanFlag(400, true); + + public static final BooleanFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED = + new BooleanFlag(401, false); + + /***************************************/ + // 500 - quick settings + public static final BooleanFlag NEW_USER_SWITCHER = + new BooleanFlag(500, true); + + /***************************************/ + // 600- status bar + public static final BooleanFlag COMBINED_STATUS_BAR_SIGNAL_ICONS = + new BooleanFlag(501, false); + + /***************************************/ + // 700 - dialer/calls + public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP = + new BooleanFlag(600, true); + + public static final BooleanFlag ONGOING_CALL_IN_IMMERSIVE = + new BooleanFlag(601, true); + + public static final BooleanFlag ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = + new BooleanFlag(602, true); + + // Pay no attention to the reflection behind the curtain. + // ========================== Curtain ========================== + // | | + // | . . . . . . . . . . . . . . . . . . . | + private static Map<Integer, Flag<?>> sFlagMap; + static Map<Integer, Flag<?>> collectFlags() { + if (sFlagMap != null) { + return sFlagMap; + } + Map<Integer, Flag<?>> flags = new HashMap<>(); + + Field[] fields = Flags.class.getFields(); + + for (Field field : fields) { + Class<?> t = field.getType(); + if (Flag.class.isAssignableFrom(t)) { + try { + Flag<?> flag = (Flag<?>) field.get(null); + flags.put(flag.getId(), flag); + } catch (IllegalAccessException e) { + // no-op + } + } + } + + sFlagMap = flags; + + return sFlagMap; + } + // | . . . . . . . . . . . . . . . . . . . | + // | | + // \_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/\_/ + } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java index a03d84956ddc..757dc2ec807d 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java @@ -52,7 +52,16 @@ public interface QS extends FragmentBase { boolean isShowingDetail(); void closeDetail(); void animateHeaderSlidingOut(); - void setQsExpansion(float qsExpansionFraction, float headerTranslation); + + /** + * Asks QS to update its presentation, according to {@code NotificationPanelViewController}. + * + * @param qsExpansionFraction How much each UI element in QS should be expanded (QQS to QS.) + * @param panelExpansionFraction Whats the expansion of the whole shade. + * @param headerTranslation How much we should vertically translate QS. + */ + void setQsExpansion(float qsExpansionFraction, float panelExpansionFraction, + float headerTranslation); void setHeaderListening(boolean listening); void notifyCustomizeChanged(); void setContainerController(QSContainerController controller); @@ -75,13 +84,13 @@ public interface QS extends FragmentBase { /** * If QS should translate as we pull it down, or if it should be static. */ - void setTranslateWhileExpanding(boolean shouldTranslate); + void setInSplitShade(boolean shouldTranslate); /** * Set the amount of pixels we have currently dragged down if we're transitioning to the full * shade. 0.0f means we're not transitioning yet. */ - default void setTransitionToFullShadeAmount(float pxAmount, boolean animated) {} + default void setTransitionToFullShadeAmount(float pxAmount, float progress) {} /** * A rounded corner clipping that makes QS feel as if it were behind everything. diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java index 6c5c4ef94921..9829918a0302 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java @@ -109,9 +109,8 @@ public interface StatusBarStateController { * Callback to be notified when the fullscreen or immersive state changes. * * @param isFullscreen if any of the system bar is hidden by the focused window. - * @param isImmersive if the navigation bar can stay hidden when the display gets tapped. */ - default void onFullscreenStateChanged(boolean isFullscreen, boolean isImmersive) {} + default void onFullscreenStateChanged(boolean isFullscreen) {} /** * Callback to be notified when the pulsing state changes diff --git a/packages/SystemUI/res/anim/fp_to_unlock.xml b/packages/SystemUI/res-keyguard/drawable/fp_to_unlock.xml index a5f75b6726c8..b93ccc6ac106 100644 --- a/packages/SystemUI/res/anim/fp_to_unlock.xml +++ b/packages/SystemUI/res-keyguard/drawable/fp_to_unlock.xml @@ -19,15 +19,15 @@ <group android:name="_R_G"> <group android:name="_R_G_L_1_G_N_7_T_0" android:translateX="-27" android:translateY="-17.5"> <group android:name="_R_G_L_1_G" android:translateX="30.75" android:translateY="25.75"> - <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " /> - <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " /> - <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " /> - <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " /> + <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " /> + <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " /> + <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " /> + <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " /> </group> </group> <group android:name="_R_G_L_0_G_N_7_T_0" android:translateX="-27" android:translateY="-17.5"> <group android:name="_R_G_L_0_G" android:translateX="47.357" android:translateY="53.25" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866"> - <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#b7f29f" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c " /> + <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c " /> </group> </group> </group> diff --git a/packages/SystemUI/res-keyguard/drawable/ic_fingerprint.xml b/packages/SystemUI/res-keyguard/drawable/ic_fingerprint.xml new file mode 100644 index 000000000000..2063d21bb5d6 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/ic_fingerprint.xml @@ -0,0 +1,48 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="65dp" + android:width="46dp" + android:viewportHeight="65" + android:viewportWidth="46"> + <group android:name="_R_G_L_0_G" android:translateX="3.75" android:translateY="8.25"> + <path + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2.5" + android:pathData="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " /> + <path + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2.5" + android:pathData="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " /> + <path + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2.5" + android:pathData="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " /> + <path + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2.5" + android:pathData="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " /> + </group> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/drawable/ic_lock.xml b/packages/SystemUI/res-keyguard/drawable/ic_lock.xml new file mode 100644 index 000000000000..14a8d0bdf8e8 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/ic_lock.xml @@ -0,0 +1,95 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="65dp" + android:width="46dp" + android:viewportHeight="65" + android:viewportWidth="46"> + <group android:name="_R_G"> + <group android:name="_R_G_L_2_G_N_10_N_11_T_0" + android:translateX="-27.5" + android:translateY="-17.5"> + <group android:name="_R_G_L_2_G_N_10_T_1" + android:translateX="50.25" + android:translateY="61"> + <group android:name="_R_G_L_2_G_N_10_T_0" + android:translateX="-13.75" + android:translateY="-7.5"> + <group android:name="_R_G_L_2_G" + android:translateX="-0.375" + android:translateY="-22.375"> + <path android:name="_R_G_L_2_G_D_0_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:strokeAlpha="1" + android:pathData=" M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c "/> + </group> + </group> + </group> + </group> + <group android:name="_R_G_L_1_G_N_10_N_11_T_0" + android:translateX="-27.5" + android:translateY="-17.5"> + <group android:name="_R_G_L_1_G_N_10_T_1" + android:translateX="50.25" + android:translateY="61"> + <group android:name="_R_G_L_1_G_N_10_T_0" + android:translateX="-13.75" + android:translateY="-7.5"> + <group android:name="_R_G_L_1_G" + android:translateX="5" + android:translateY="-22.5"> + <path android:name="_R_G_L_1_G_D_0_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:strokeAlpha="1" + android:pathData=" M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 "/> + </group> + </group> + </group> + </group> + <group android:name="_R_G_L_0_G_N_10_N_11_T_0" + android:translateX="-27.5" + android:translateY="-17.5"> + <group android:name="_R_G_L_0_G_N_10_T_1" + android:translateX="50.25" + android:translateY="61"> + <group android:name="_R_G_L_0_G_N_10_T_0" + android:translateX="-13.75" + android:translateY="-7.5"> + <group android:name="_R_G_L_0_G" + android:translateX="11" + android:translateY="-0.25" + android:pivotX="2.75" + android:pivotY="2.75" + android:scaleX="1" + android:scaleY="1"> + <path android:name="_R_G_L_0_G_D_0_P_0" + android:fillColor="#FF000000" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/> + </group> + </group> + </group> + </group> + </group> +</vector> diff --git a/packages/SystemUI/res-keyguard/drawable/ic_lock_aod.xml b/packages/SystemUI/res-keyguard/drawable/ic_lock_aod.xml new file mode 100644 index 000000000000..cdae306cfc26 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/ic_lock_aod.xml @@ -0,0 +1,41 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="65dp" + android:width="46dp" + android:viewportHeight="65" + android:viewportWidth="46"> + <group android:name="_R_G_L_2_G" android:translateX="23" android:translateY="32.125"> + <path + android:fillColor="#FF000000" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " /> + <path + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:pathData=" M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " /> + <path + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:pathData=" M4.38 -2.62 C4.38,-2.62 4.38,-7.1 4.38,-7.1 C4.38,-9.46 2.42,-11.37 0,-11.37 C-2.42,-11.37 -4.37,-9.46 -4.37,-7.1 C-4.37,-7.1 -4.37,-2.62 -4.37,-2.62 " /> + </group> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/drawable/ic_unlocked.xml b/packages/SystemUI/res-keyguard/drawable/ic_unlocked.xml new file mode 100644 index 000000000000..54242781cd73 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/ic_unlocked.xml @@ -0,0 +1,48 @@ +<?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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:height="65dp" + android:width="46dp" + android:viewportHeight="65" + android:viewportWidth="46"> + <group android:translateX="8.625" + android:translateY="13.625"> + <path + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2.5" + android:pathData="M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c "/> + </group> + <group android:translateX="14" + android:translateY="13.5"> + <path + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2.5" + android:pathData="M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 "/> + </group> + <group android:translateX="20" + android:translateY="35.75"> + <path + android:fillColor="#FF000000" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/> + </group> +</vector> diff --git a/packages/SystemUI/res-keyguard/drawable/lock_aod_to_ls.xml b/packages/SystemUI/res-keyguard/drawable/lock_aod_to_ls.xml new file mode 100644 index 000000000000..d35f69589c39 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/lock_aod_to_ls.xml @@ -0,0 +1,151 @@ +<?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. +--> +<animated-vector xmlns:aapt="http://schemas.android.com/aapt" + xmlns:android="http://schemas.android.com/apk/res/android"> + <aapt:attr name="android:drawable"> + <vector android:height="65dp" + android:width="46dp" + android:viewportHeight="65" + android:viewportWidth="46"> + <group android:name="_R_G"> + <group android:name="_R_G_L_2_G" + android:translateX="23" + android:translateY="32.125"> + <path android:name="_R_G_L_2_G_D_0_P_0" + android:fillColor="#FF000000" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c "/> + </group> + <group android:name="_R_G_L_1_G" + android:translateX="23" + android:translateY="32.125"> + <path android:name="_R_G_L_1_G_D_0_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:strokeAlpha="1" + android:pathData=" M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c "/> + </group> + <group android:name="_R_G_L_0_G" + android:translateX="23" + android:translateY="32.125"> + <path android:name="_R_G_L_0_G_D_0_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="1.5" + android:strokeAlpha="1" + android:pathData=" M4.38 -2.62 C4.38,-2.62 4.38,-7.1 4.38,-7.1 C4.38,-9.46 2.42,-11.37 0,-11.37 C-2.42,-11.37 -4.37,-9.46 -4.37,-7.1 C-4.37,-7.1 -4.37,-2.62 -4.37,-2.62 "/> + </group> + </group> + <group android:name="time_group"/> + </vector> + </aapt:attr> + <target android:name="_R_G_L_2_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="333" + android:startOffset="0" + android:valueFrom="M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " + android:valueTo="M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.372,0 0.203,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeWidth" + android:duration="333" + android:startOffset="0" + android:valueFrom="1.5" + android:valueTo="2" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.386,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="333" + android:startOffset="0" + android:valueFrom="M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " + android:valueTo="M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.372,0 0.203,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeWidth" + android:duration="333" + android:startOffset="0" + android:valueFrom="1.5" + android:valueTo="2" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.386,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="333" + android:startOffset="0" + android:valueFrom="M4.38 -2.62 C4.38,-2.62 4.38,-7.1 4.38,-7.1 C4.38,-9.46 2.42,-11.37 0,-11.37 C-2.42,-11.37 -4.37,-9.46 -4.37,-7.1 C-4.37,-7.1 -4.37,-2.62 -4.37,-2.62 " + android:valueTo="M5.88 -3.87 C5.88,-3.87 5.88,-10.2 5.88,-10.2 C5.88,-13.54 3.08,-16.25 -0.37,-16.25 C-3.83,-16.25 -6.62,-13.54 -6.62,-10.2 C-6.62,-10.2 -6.62,-3.87 -6.62,-3.87 " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.372,0 0.203,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateX" + android:duration="517" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"/> + </set> + </aapt:attr> + </target> +</animated-vector> diff --git a/packages/SystemUI/res-keyguard/drawable/lock_ls_to_aod.xml b/packages/SystemUI/res-keyguard/drawable/lock_ls_to_aod.xml new file mode 100644 index 000000000000..8a728ee7b46a --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/lock_ls_to_aod.xml @@ -0,0 +1,151 @@ +<?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. +--> +<animated-vector xmlns:aapt="http://schemas.android.com/aapt" + xmlns:android="http://schemas.android.com/apk/res/android"> + <aapt:attr name="android:drawable"> + <vector android:height="65dp" + android:width="46dp" + android:viewportHeight="65" + android:viewportWidth="46"> + <group android:name="_R_G"> + <group android:name="_R_G_L_2_G" + android:translateX="23" + android:translateY="32.125"> + <path android:name="_R_G_L_2_G_D_0_P_0" + android:fillColor="#FF000000" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c "/> + </group> + <group android:name="_R_G_L_1_G" + android:translateX="23" + android:translateY="32.125"> + <path android:name="_R_G_L_1_G_D_0_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:strokeAlpha="1" + android:pathData=" M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c "/> + </group> + <group android:name="_R_G_L_0_G" + android:translateX="23" + android:translateY="32.125"> + <path android:name="_R_G_L_0_G_D_0_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:strokeAlpha="1" + android:pathData=" M5.88 -3.87 C5.88,-3.87 5.88,-10.2 5.88,-10.2 C5.88,-13.54 3.08,-16.25 -0.37,-16.25 C-3.83,-16.25 -6.62,-13.54 -6.62,-10.2 C-6.62,-10.2 -6.62,-3.87 -6.62,-3.87 "/> + </group> + </group> + <group android:name="time_group"/> + </vector> + </aapt:attr> + <target android:name="_R_G_L_2_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="333" + android:startOffset="0" + android:valueFrom="M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " + android:valueTo="M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeWidth" + android:duration="333" + android:startOffset="0" + android:valueFrom="2" + android:valueTo="1.5" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.38,0 0.274,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="333" + android:startOffset="0" + android:valueFrom="M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " + android:valueTo="M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeWidth" + android:duration="333" + android:startOffset="0" + android:valueFrom="2" + android:valueTo="1.5" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.38,0 0.274,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="333" + android:startOffset="0" + android:valueFrom="M5.88 -3.87 C5.88,-3.87 5.88,-10.2 5.88,-10.2 C5.88,-13.54 3.08,-16.25 -0.37,-16.25 C-3.83,-16.25 -6.62,-13.54 -6.62,-10.2 C-6.62,-10.2 -6.62,-3.87 -6.62,-3.87 " + android:valueTo="M4.38 -2.62 C4.38,-2.62 4.38,-7.1 4.38,-7.1 C4.38,-9.46 2.42,-11.37 0,-11.37 C-2.42,-11.37 -4.37,-9.46 -4.37,-7.1 C-4.37,-7.1 -4.37,-2.62 -4.37,-2.62 " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateX" + android:duration="517" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"/> + </set> + </aapt:attr> + </target> +</animated-vector> diff --git a/packages/SystemUI/res/anim/lock_to_unlock.xml b/packages/SystemUI/res-keyguard/drawable/lock_to_unlock.xml index 76f7a05866d9..ab7e9d9e582b 100644 --- a/packages/SystemUI/res/anim/lock_to_unlock.xml +++ b/packages/SystemUI/res-keyguard/drawable/lock_to_unlock.xml @@ -21,7 +21,7 @@ <group android:name="_R_G_L_2_G_N_10_T_1" android:translateX="50.25" android:translateY="61"> <group android:name="_R_G_L_2_G_N_10_T_0" android:translateX="-13.75" android:translateY="-7.5"> <group android:name="_R_G_L_2_G" android:translateX="-0.375" android:translateY="-22.375"> - <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c " /> + <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M4.75 15 C4.75,15 23.25,15 23.25,15 C24.35,15 25.25,15.9 25.25,17 C25.25,17 25.25,33 25.25,33 C25.25,34.1 24.35,35 23.25,35 C23.25,35 4.75,35 4.75,35 C3.65,35 2.75,34.1 2.75,33 C2.75,33 2.75,17 2.75,17 C2.75,15.9 3.65,15 4.75,15c " /> </group> </group> </group> @@ -30,7 +30,7 @@ <group android:name="_R_G_L_1_G_N_10_T_1" android:translateX="50.25" android:translateY="61"> <group android:name="_R_G_L_1_G_N_10_T_0" android:translateX="-13.75" android:translateY="-7.5"> <group android:name="_R_G_L_1_G" android:translateX="5" android:translateY="-22.5"> - <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#b7f29f" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 " /> + <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 " /> </group> </group> </group> @@ -39,7 +39,7 @@ <group android:name="_R_G_L_0_G_N_10_T_1" android:translateX="50.25" android:translateY="61"> <group android:name="_R_G_L_0_G_N_10_T_0" android:translateX="-13.75" android:translateY="-7.5"> <group android:name="_R_G_L_0_G" android:translateX="11" android:translateY="-0.25" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1" android:scaleY="1"> - <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#b7f29f" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c " /> + <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c " /> </group> </group> </group> diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml new file mode 100644 index 000000000000..7f0f68f4fc06 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml @@ -0,0 +1,82 @@ +<?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 + --> +<animated-selector + xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- + State corresponds with the following icons: + state_first => lock icon + state_middle => fingerprint icon + state_last => unlocked icon + + state_single + = true => AOD + = false => LS + --> + + <item + android:id="@+id/locked" + android:drawable="@drawable/ic_lock" + android:state_first="true" + android:state_single="false"/> + + <item + android:id="@+id/locked_fp" + android:state_middle="true" + android:state_single="false" + android:drawable="@drawable/ic_fingerprint" /> + + <item + android:id="@+id/unlocked" + android:state_last="true" + android:state_single="false" + android:drawable="@drawable/ic_unlocked" /> + + <item + android:id="@+id/locked_aod" + android:state_first="true" + android:state_single="true" + android:drawable="@drawable/ic_lock_aod" /> + + <item + android:id="@+id/no_icon" + android:drawable="@color/transparent" /> + + <transition + android:fromId="@id/locked" + android:toId="@id/unlocked" + android:drawable="@drawable/lock_to_unlock" /> + + <transition + android:fromId="@id/locked_fp" + android:toId="@id/unlocked" + android:drawable="@drawable/fp_to_unlock" /> + + <transition + android:fromId="@id/unlocked" + android:toId="@id/locked_fp" + android:drawable="@drawable/unlock_to_fp" /> + + <transition + android:fromId="@id/locked_aod" + android:toId="@id/locked" + android:drawable="@drawable/lock_aod_to_ls" /> + + <transition + android:fromId="@id/locked" + android:toId="@id/locked_aod" + android:drawable="@drawable/lock_ls_to_aod" /> +</animated-selector> diff --git a/packages/SystemUI/res-keyguard/drawable/unlock_to_fp.xml b/packages/SystemUI/res-keyguard/drawable/unlock_to_fp.xml new file mode 100644 index 000000000000..620c71a73121 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/unlock_to_fp.xml @@ -0,0 +1,298 @@ +<?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 + --> +<animated-vector xmlns:aapt="http://schemas.android.com/aapt" + xmlns:android="http://schemas.android.com/apk/res/android"> + <aapt:attr name="android:drawable"> + <vector android:height="65dp" + android:width="46dp" + android:viewportHeight="65" + android:viewportWidth="46"> + <group android:name="_R_G"> + <group android:name="_R_G_L_1_G" + android:translateX="3.75" + android:translateY="8.25"> + <path android:name="_R_G_L_1_G_D_0_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:strokeAlpha="1" + android:pathData=" M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "/> + <path android:name="_R_G_L_1_G_D_1_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:strokeAlpha="0" + android:pathData=" M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "/> + <path android:name="_R_G_L_1_G_D_2_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:strokeAlpha="1" + android:pathData=" M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "/> + <path android:name="_R_G_L_1_G_D_3_P_0" + android:strokeColor="#FF000000" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="2" + android:strokeAlpha="1" + android:pathData=" M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "/> + </group> + <group android:name="_R_G_L_0_G" + android:translateX="20.357" + android:translateY="35.75" + android:pivotX="2.75" + android:pivotY="2.75" + android:scaleX="1" + android:scaleY="1"> + <path android:name="_R_G_L_0_G_D_0_P_0" + android:fillColor="#FF000000" + android:fillAlpha="1" + android:fillType="nonZero" + android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/> + </group> + </group> + <group android:name="time_group"/> + </vector> + </aapt:attr> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="183" + android:startOffset="0" + android:valueFrom="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " + android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" + android:duration="133" + android:startOffset="183" + android:valueFrom="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " + android:valueTo="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeAlpha" + android:duration="183" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="0" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="strokeAlpha" + android:duration="33" + android:startOffset="183" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="183" + android:startOffset="0" + android:valueFrom="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " + android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" + android:duration="133" + android:startOffset="183" + android:valueFrom="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " + android:valueTo="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="183" + android:startOffset="0" + android:valueFrom="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " + android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" + android:duration="133" + android:startOffset="183" + android:valueFrom="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " + android:valueTo="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.15,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_3_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" + android:duration="150" + android:startOffset="0" + android:valueFrom="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " + android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.261,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" + android:duration="33" + android:startOffset="150" + android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " + android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.123,0 0.261,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" + android:duration="133" + android:startOffset="183" + android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " + android:valueTo="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " + android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.123,0 0.15,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="fillAlpha" + android:duration="200" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="fillAlpha" + android:duration="17" + android:startOffset="200" + android:valueFrom="1" + android:valueTo="0" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="scaleX" + android:duration="183" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.596,0 0.018,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="scaleY" + android:duration="183" + android:startOffset="0" + android:valueFrom="1" + android:valueTo="1" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.596,0 0.018,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="scaleX" + android:duration="67" + android:startOffset="183" + android:valueFrom="1" + android:valueTo="1.4186600000000003" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.596,0 0.018,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="scaleY" + android:duration="67" + android:startOffset="183" + android:valueFrom="1" + android:valueTo="1.4186600000000003" + android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.596,0 0.018,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateX" + android:duration="433" + android:startOffset="0" + android:valueFrom="0" + android:valueTo="1" + android:valueType="floatType"/> + </set> + </aapt:attr> + </target> +</animated-vector> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml index 28c61663bd4d..87a9825af1cb 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml @@ -51,7 +51,7 @@ android:id="@+id/lockscreen_clock_view_large" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_below="@id/keyguard_status_area" + android:layout_below="@id/keyguard_slice_view" android:visibility="gone"> <com.android.keyguard.AnimatableClockView android:id="@+id/animatable_clock_view_large" @@ -68,19 +68,28 @@ lockScreenWeight="400" /> </FrameLayout> - <include layout="@layout/keyguard_status_area" + + <!-- Not quite optimal but needed to translate these items as a group. The + NotificationIconContainer has its own logic for translation. --> + <LinearLayout android:id="@+id/keyguard_status_area" + android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentStart="true" - android:layout_below="@id/lockscreen_clock_view" /> + android:layout_below="@id/lockscreen_clock_view"> - <com.android.systemui.statusbar.phone.NotificationIconContainer - android:id="@+id/left_aligned_notification_icon_container" - android:layout_width="match_parent" - android:layout_height="@dimen/notification_shelf_height" - android:layout_below="@id/keyguard_status_area" - android:paddingStart="@dimen/below_clock_padding_start_icons" - android:visibility="invisible" - /> + <include layout="@layout/keyguard_slice_view" + android:id="@+id/keyguard_slice_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <com.android.systemui.statusbar.phone.NotificationIconContainer + android:id="@+id/left_aligned_notification_icon_container" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_shelf_height" + android:paddingStart="@dimen/below_clock_padding_start_icons" + android:visibility="invisible" + /> + </LinearLayout> </com.android.keyguard.KeyguardClockSwitch> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml index 95eb5c1d5f1a..1863d1112947 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml @@ -22,11 +22,10 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" + android:layout_gravity="start" android:clipToPadding="false" android:orientation="vertical" - android:paddingStart="@dimen/below_clock_padding_start" - android:layout_centerHorizontal="true"> + android:paddingStart="@dimen/below_clock_padding_start"> <TextView android:id="@+id/title" android:layout_width="match_parent" @@ -42,6 +41,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:gravity="center" + android:gravity="start" /> </com.android.keyguard.KeyguardSliceView> diff --git a/packages/SystemUI/res/color/prv_color_surface.xml b/packages/SystemUI/res/color/prv_color_surface.xml new file mode 100644 index 000000000000..b9d016c46ba0 --- /dev/null +++ b/packages/SystemUI/res/color/prv_color_surface.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. + --> +<selector xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="?androidprv:attr/colorSurface" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/color/prv_text_color_on_accent.xml b/packages/SystemUI/res/color/prv_text_color_on_accent.xml new file mode 100644 index 000000000000..9f44acadb420 --- /dev/null +++ b/packages/SystemUI/res/color/prv_text_color_on_accent.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. + --> +<selector xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="?androidprv:attr/textColorOnAccent" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml new file mode 100644 index 000000000000..363a022efdac --- /dev/null +++ b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml @@ -0,0 +1,29 @@ +<!-- + ~ 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. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <stroke + android:color="?androidprv:attr/colorAccentPrimaryVariant" + android:width="1dp"/> + <corners android:radius="20dp"/> + <padding + android:left="16dp" + android:right="16dp" + android:top="8dp" + android:bottom="8dp" /> + <solid android:color="@android:color/transparent" /> +</shape> diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml new file mode 100644 index 000000000000..1a128dfe8b10 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml @@ -0,0 +1,39 @@ +<?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. + --> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:insetTop="@dimen/qs_dialog_button_vertical_inset" + android:insetBottom="@dimen/qs_dialog_button_vertical_inset"> + <ripple android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="@android:color/white"/> + <corners android:radius="?android:attr/buttonCornerRadius"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <corners android:radius="?android:attr/buttonCornerRadius"/> + <solid android:color="?androidprv:attr/colorAccentPrimary"/> + <padding android:left="@dimen/qs_dialog_button_horizontal_padding" + android:top="@dimen/qs_dialog_button_vertical_padding" + android:right="@dimen/qs_dialog_button_horizontal_padding" + android:bottom="@dimen/qs_dialog_button_vertical_padding"/> + </shape> + </item> + </ripple> +</inset> diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml new file mode 100644 index 000000000000..467c20f3ffcd --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml @@ -0,0 +1,42 @@ +<?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. + --> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:insetTop="@dimen/qs_dialog_button_vertical_inset" + android:insetBottom="@dimen/qs_dialog_button_vertical_inset"> + <ripple android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="@android:color/white"/> + <corners android:radius="?android:attr/buttonCornerRadius"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <corners android:radius="?android:attr/buttonCornerRadius"/> + <solid android:color="@android:color/transparent"/> + <stroke android:color="?androidprv:attr/colorAccentPrimary" + android:width="1dp" + /> + <padding android:left="@dimen/qs_dialog_button_horizontal_padding" + android:top="@dimen/qs_dialog_button_vertical_padding" + android:right="@dimen/qs_dialog_button_horizontal_padding" + android:bottom="@dimen/qs_dialog_button_vertical_padding"/> + </shape> + </item> + </ripple> +</inset> diff --git a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml index 3761a40ddbaa..c415ecd4f0a8 100644 --- a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml +++ b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml @@ -21,7 +21,4 @@ <path android:fillColor="@android:color/white" android:pathData="M20,6h-4V4c0-1.1-0.9-2-2-2h-4C8.9,2,8,2.9,8,4v2H4C2.9,6,2,6.9,2,8l0,11c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8 C22,6.9,21.1,6,20,6z M10,4h4v2h-4V4z M20,19H4V8h16V19z" /> - <path - android:fillColor="@android:color/white" - android:pathData="M 12 12 C 12.8284271247 12 13.5 12.6715728753 13.5 13.5 C 13.5 14.3284271247 12.8284271247 15 12 15 C 11.1715728753 15 10.5 14.3284271247 10.5 13.5 C 10.5 12.6715728753 11.1715728753 12 12 12 Z" /> </vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index c1d7308be5a8..79ac737ba304 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -323,6 +323,46 @@ </FrameLayout> </LinearLayout> + <LinearLayout + android:id="@+id/wifi_scan_notify_layout" + style="@style/InternetDialog.Network" + android:orientation="vertical" + android:layout_height="wrap_content" + android:paddingBottom="4dp" + android:clickable="false" + android:focusable="false"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="56dp" + android:gravity="start|top" + android:orientation="horizontal" + android:paddingEnd="12dp" + android:paddingTop="16dp" + android:paddingBottom="4dp"> + <ImageView + android:src="@drawable/ic_info_outline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:tint="?android:attr/textColorTertiary"/> + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + <TextView + android:id="@+id/wifi_scan_notify_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingTop="16dp" + android:paddingBottom="8dp" + android:textColor="?android:attr/textColorSecondary" + android:clickable="true"/> + </LinearLayout> + </LinearLayout> + <FrameLayout android:id="@+id/done_layout" android:layout_width="67dp" diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml index b33889469f48..cd6bc11bacf3 100644 --- a/packages/SystemUI/res/layout/media_output_dialog.xml +++ b/packages/SystemUI/res/layout/media_output_dialog.xml @@ -24,41 +24,44 @@ <LinearLayout android:layout_width="match_parent" - android:layout_height="94dp" + android:layout_height="96dp" android:gravity="start|center_vertical" android:paddingStart="16dp" android:orientation="horizontal"> <ImageView android:id="@+id/header_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingEnd="@dimen/media_output_dialog_header_icon_padding" + android:layout_width="48dp" + android:layout_height="48dp" android:importantForAccessibility="no"/> <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginEnd="16dp" + android:layout_height="match_parent" + android:paddingStart="16dp" + android:paddingTop="20dp" + android:paddingBottom="24dp" + android:paddingEnd="24dp" android:orientation="vertical"> <TextView android:id="@+id/header_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="end" + android:gravity="center_vertical" android:maxLines="1" android:textColor="?android:attr/textColorPrimary" android:fontFamily="@*android:string/config_headlineFontFamilyMedium" android:textSize="20sp"/> - <TextView android:id="@+id/header_subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:gravity="center_vertical" android:ellipsize="end" android:maxLines="1" + android:textColor="?android:attr/textColorTertiary" android:fontFamily="roboto-regular" - android:textSize="14sp"/> - + android:textSize="16sp"/> </LinearLayout> </LinearLayout> @@ -81,21 +84,21 @@ android:overScrollMode="never"/> </LinearLayout> - <View - android:layout_width="match_parent" - android:layout_height="1dp" - android:background="?android:attr/listDivider"/> - <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginStart="24dp" + android:layout_marginBottom="18dp" + android:layout_marginEnd="24dp" android:orientation="horizontal"> <Button android:id="@+id/stop" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + style="@style/MediaOutputRoundedOutlinedButton" android:layout_width="wrap_content" - android:layout_height="64dp" + android:layout_height="36dp" + android:minWidth="0dp" android:text="@string/keyboard_key_media_stop" android:visibility="gone"/> @@ -106,10 +109,10 @@ <Button android:id="@+id/done" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + style="@style/MediaOutputRoundedOutlinedButton" android:layout_width="wrap_content" - android:layout_height="64dp" - android:layout_marginEnd="0dp" + android:layout_height="36dp" + android:minWidth="0dp" android:text="@string/inline_done_button"/> </LinearLayout> </LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml index 16c03e124794..a5a7efa24f47 100644 --- a/packages/SystemUI/res/layout/media_output_list_item.xml +++ b/packages/SystemUI/res/layout/media_output_list_item.xml @@ -23,17 +23,20 @@ android:orientation="vertical"> <FrameLayout android:layout_width="match_parent" - android:layout_height="64dp"> + android:layout_height="88dp" + android:paddingTop="24dp" + android:paddingBottom="16dp" + android:paddingStart="24dp" + android:paddingEnd="8dp"> <FrameLayout - android:layout_width="36dp" - android:layout_height="36dp" - android:layout_gravity="center_vertical|start" - android:layout_marginStart="16dp"> + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_gravity="center_vertical|start"> <ImageView android:id="@+id/title_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="48dp" + android:layout_height="48dp" android:layout_gravity="center"/> </FrameLayout> @@ -42,49 +45,69 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|start" - android:layout_marginStart="68dp" + android:layout_marginStart="64dp" android:ellipsize="end" android:maxLines="1" android:textColor="?android:attr/textColorPrimary" - android:textSize="14sp"/> + android:textSize="16sp"/> <RelativeLayout android:id="@+id/two_line_layout" android:layout_width="wrap_content" android:layout_height="48dp" - android:layout_marginStart="52dp" - android:layout_marginEnd="69dp" - android:layout_marginTop="10dp"> + android:layout_marginStart="48dp"> <TextView android:id="@+id/two_line_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:layout_marginEnd="15dp" + android:layout_marginEnd="48dp" android:ellipsize="end" android:maxLines="1" android:textColor="?android:attr/textColorPrimary" - android:textSize="14sp"/> + android:textSize="16sp"/> <TextView android:id="@+id/subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginEnd="15dp" - android:layout_marginBottom="7dp" + android:layout_marginTop="4dp" android:layout_alignParentBottom="true" android:ellipsize="end" android:maxLines="1" android:textColor="?android:attr/textColorSecondary" - android:textSize="12sp" + android:textSize="14sp" android:fontFamily="roboto-regular" android:visibility="gone"/> <SeekBar android:id="@+id/volume_seekbar" + android:layout_marginTop="16dp" + android:layout_marginEnd="8dp" style="@*android:style/Widget.DeviceDefault.SeekBar" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true"/> + <ImageView + android:id="@+id/add_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="right" + android:layout_marginEnd="24dp" + android:layout_alignParentRight="true" + android:src="@drawable/ic_add" + android:tint="?android:attr/colorAccent" + /> + <CheckBox + android:id="@+id/check_box" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="right" + android:layout_marginEnd="24dp" + android:layout_alignParentRight="true" + android:button="@drawable/ic_check_box" + android:visibility="gone" + /> </RelativeLayout> <ProgressBar @@ -92,47 +115,17 @@ style="@*android:style/Widget.Material.ProgressBar.Horizontal" android:layout_width="258dp" android:layout_height="18dp" - android:layout_marginStart="68dp" - android:layout_marginTop="40dp" + android:layout_marginStart="64dp" + android:layout_marginTop="28dp" android:indeterminate="true" android:indeterminateOnly="true" android:visibility="gone"/> - - <View - android:id="@+id/end_divider" - android:layout_width="1dp" - android:layout_height="36dp" - android:layout_marginEnd="68dp" - android:layout_gravity="right|center_vertical" - android:background="?android:attr/listDivider" - android:visibility="gone"/> - - <ImageView - android:id="@+id/add_icon" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_gravity="right|center_vertical" - android:layout_marginEnd="24dp" - android:src="@drawable/ic_add" - android:tint="?android:attr/colorAccent" - android:visibility="gone"/> - - <CheckBox - android:id="@+id/check_box" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_gravity="right|center_vertical" - android:layout_marginEnd="24dp" - android:button="@drawable/ic_check_box" - android:visibility="gone"/> </FrameLayout> <View android:id="@+id/bottom_divider" android:layout_width="match_parent" android:layout_height="1dp" - android:layout_marginTop="12dp" - android:layout_marginBottom="12dp" android:layout_gravity="bottom" android:background="?android:attr/listDivider" android:visibility="gone"/> diff --git a/packages/SystemUI/res/layout/qs_user_dialog_content.xml b/packages/SystemUI/res/layout/qs_user_dialog_content.xml new file mode 100644 index 000000000000..543b7d77243b --- /dev/null +++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml @@ -0,0 +1,93 @@ +<?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. + --> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:sysui="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="24dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + > + <TextView + android:id="@+id/title" + android:layout_height="wrap_content" + android:layout_width="0dp" + android:textAlignment="center" + android:text="@string/qs_user_switch_dialog_title" + android:textAppearance="@style/TextAppearance.QSDialog.Title" + android:layout_marginBottom="32dp" + sysui:layout_constraintTop_toTopOf="parent" + sysui:layout_constraintStart_toStartOf="parent" + sysui:layout_constraintEnd_toEndOf="parent" + sysui:layout_constraintBottom_toTopOf="@id/grid" + /> + + <com.android.systemui.qs.PseudoGridView + android:id="@+id/grid" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="28dp" + sysui:verticalSpacing="4dp" + sysui:horizontalSpacing="4dp" + sysui:fixedChildWidth="80dp" + sysui:layout_constraintTop_toBottomOf="@id/title" + sysui:layout_constraintStart_toStartOf="parent" + sysui:layout_constraintEnd_toEndOf="parent" + sysui:layout_constraintBottom_toTopOf="@id/barrier" + /> + + <androidx.constraintlayout.widget.Barrier + android:id="@+id/barrier" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + sysui:barrierDirection="top" + sysui:constraint_referenced_ids="settings,done" + /> + + <Button + android:id="@+id/settings" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:text="@string/quick_settings_more_user_settings" + sysui:layout_constraintTop_toBottomOf="@id/barrier" + sysui:layout_constraintBottom_toBottomOf="parent" + sysui:layout_constraintStart_toStartOf="parent" + sysui:layout_constraintEnd_toStartOf="@id/done" + sysui:layout_constraintHorizontal_chainStyle="spread_inside" + style="@style/Widget.QSDialog.Button.BorderButton" + /> + + <Button + android:id="@+id/done" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:text="@string/quick_settings_done" + sysui:layout_constraintTop_toBottomOf="@id/barrier" + sysui:layout_constraintBottom_toBottomOf="parent" + sysui:layout_constraintStart_toEndOf="@id/settings" + sysui:layout_constraintEnd_toEndOf="parent" + style="@style/Widget.QSDialog.Button" + /> + + </androidx.constraintlayout.widget.ConstraintLayout> +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml index 720e47b1908c..f91ab6f4980a 100644 --- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml +++ b/packages/SystemUI/res/layout/rounded_corners_bottom.xml @@ -31,8 +31,7 @@ android:id="@+id/privacy_dot_left_container" android:layout_height="@dimen/status_bar_height" android:layout_width="wrap_content" - android:layout_marginTop="@dimen/status_bar_padding_top" - android:layout_marginLeft="0dp" + android:paddingTop="@dimen/status_bar_padding_top" android:layout_gravity="left|bottom" android:visibility="invisible" > <ImageView @@ -51,12 +50,12 @@ android:tint="#ff000000" android:layout_gravity="right|bottom" android:src="@drawable/rounded_corner_bottom"/> + <FrameLayout android:id="@+id/privacy_dot_right_container" android:layout_height="@dimen/status_bar_height" android:layout_width="wrap_content" - android:layout_marginTop="@dimen/status_bar_padding_top" - android:layout_marginRight="0dp" + android:paddingTop="@dimen/status_bar_padding_top" android:layout_gravity="right|bottom" android:visibility="invisible" > <ImageView diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml index 6abe406e0ea6..819a9a4e9b02 100644 --- a/packages/SystemUI/res/layout/rounded_corners_top.xml +++ b/packages/SystemUI/res/layout/rounded_corners_top.xml @@ -29,10 +29,9 @@ <FrameLayout android:id="@+id/privacy_dot_left_container" - android:layout_height="@*android:dimen/status_bar_height_portrait" + android:layout_height="@dimen/status_bar_height" android:layout_width="wrap_content" - android:layout_marginTop="@dimen/status_bar_padding_top" - android:layout_marginLeft="0dp" + android:paddingTop="@dimen/status_bar_padding_top" android:layout_gravity="left|top" android:visibility="invisible" > <ImageView @@ -54,10 +53,9 @@ <FrameLayout android:id="@+id/privacy_dot_right_container" - android:layout_height="@*android:dimen/status_bar_height_portrait" + android:layout_height="@dimen/status_bar_height" android:layout_width="wrap_content" - android:layout_marginTop="@dimen/status_bar_padding_top" - android:layout_marginRight="0dp" + android:paddingTop="@dimen/status_bar_padding_top" android:layout_gravity="right|top" android:visibility="invisible" > <ImageView @@ -67,8 +65,6 @@ android:layout_gravity="center_vertical|left" android:src="@drawable/system_animation_ongoing_dot" android:visibility="visible" /> - </FrameLayout> - </com.android.systemui.RegionInterceptingFrameLayout> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 18315f1dff42..cc1af873ce2b 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -65,18 +65,6 @@ 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/udfps_aod_lock_icon.xml b/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml new file mode 100644 index 000000000000..f5bfa49e4b32 --- /dev/null +++ b/packages/SystemUI/res/layout/udfps_aod_lock_icon.xml @@ -0,0 +1,27 @@ +<!-- + ~ 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. + --> +<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/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"/>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index f589498c377a..ddb6b8f5c82b 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Hervat"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Instellings"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> deur <xliff:g id="ARTIST_NAME">%2$s</xliff:g> speel tans vanaf <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> van <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Speel"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Maak <xliff:g id="APP_LABEL">%1$s</xliff:g> oop"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Speel <xliff:g id="SONG_NAME">%1$s</xliff:g> deur <xliff:g id="ARTIST_NAME">%2$s</xliff:g> vanaf <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-fi sal vir nou nie outomaties koppel nie"</string> <string name="see_all_networks" msgid="3773666844913168122">"Sien alles"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ontkoppel Ethernet om netwerke te wissel"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kies gebruiker"</string> </resources> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index e94dc688ac7c..d56c3fd912ee 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"ከቆመበት ቀጥል"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"ቅንብሮች"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> በ<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ከ<xliff:g id="APP_LABEL">%3$s</xliff:g> እየተጫወተ ነው"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> ከ<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"አጫውት"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ክፈት"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> በ<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ከ<xliff:g id="APP_LABEL">%3$s</xliff:g> ያጫውቱ"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wifi ለአሁን በራስ-ሰር አይገናኝም"</string> <string name="see_all_networks" msgid="3773666844913168122">"ሁሉንም ይመልከቱ"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"አውታረ መረቦችን ለመቀየር፣ የኢተርኔት ግንኙነት ያቋርጡ"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ተጠቃሚን ይምረጡ"</string> </resources> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 14753dabb464..4f663562328d 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -1114,6 +1114,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"استئناف التشغيل"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"الإعدادات"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"يتم تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> للفنان <xliff:g id="ARTIST_NAME">%2$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> من إجمالي <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"تشغيل"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"فتح <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> للفنان <xliff:g id="ARTIST_NAME">%2$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1202,4 +1203,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"لن يتم الاتصال بشبكة Wi-Fi تلقائيًا في الوقت الحالي."</string> <string name="see_all_networks" msgid="3773666844913168122">"عرض الكل"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"للتبديل بين الشبكات، يجب فصل إيثرنت."</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"اختيار المستخدم"</string> </resources> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index a50d8c10dd3f..3015ccd39949 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"পুনৰ আৰম্ভ কৰক"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"ছেটিং"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ত <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ৰ <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ হৈ আছে"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>ৰ <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"প্লে’ কৰক"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> খোলক"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ত <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ৰ <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ কৰক"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"এতিয়া ৱাই-ফাই স্বয়ংক্ৰিয়ভাৱে সংযুক্ত নহ’ব"</string> <string name="see_all_networks" msgid="3773666844913168122">"আটাইবোৰ চাওক"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"নেটৱৰ্ক সলনি কৰিবলৈ ইথাৰনেটৰ পৰা সংযোগ বিচ্ছিন্ন কৰক"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ব্যৱহাৰকাৰী বাছনি কৰক"</string> </resources> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index e0ef1eca3282..dad7adde8b27 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Davam edin"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Ayarlar"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> tərəfindən <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> tətbiqindən oxudulur"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Oxudun"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> tətbiqini açın"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> tərəfindən <xliff:g id="SONG_NAME">%1$s</xliff:g> mahnısını <xliff:g id="APP_LABEL">%3$s</xliff:g> tətbiqindən oxudun"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi hələlik avtomatik qoşulmayacaq"</string> <string name="see_all_networks" msgid="3773666844913168122">"Hamısına baxın"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Şəbəkəni dəyişmək üçün etherneti ayırın"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"İstifadəçi seçin"</string> </resources> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index 31f8014e8b6c..1761205ba4b3 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -1096,6 +1096,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Podešavanja"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se pušta iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Pusti"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvorite <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1184,4 +1185,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"WiFi trenutno ne može da se automatski poveže"</string> <string name="see_all_networks" msgid="3773666844913168122">"Pogledajte sve"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Da biste promenili mrežu, prekinite eternet vezu"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Izaberite korisnika"</string> </resources> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index b296aa63eedb..8bfc32fbfa61 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -1102,6 +1102,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Узнавіць"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Налады"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"У праграме \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\" прайграецца кампазіцыя \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", выканаўца – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> з <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Прайграць"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Адкрыйце праграму \"<xliff:g id="APP_LABEL">%1$s</xliff:g>\""</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Прайграйце кампазіцыю \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (выканаўца – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) з дапамогай праграмы \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string> @@ -1190,4 +1191,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Аўтаматычнае падключэнне да Wi-Fi адсутнічае"</string> <string name="see_all_networks" msgid="3773666844913168122">"Паказаць усе"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Каб падключыцца да сетак, выключыце Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Выбар карыстальніка"</string> </resources> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 84c22912a322..d9d06dc1f1e4 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Възобновяване"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Настройки"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="ARTIST_NAME">%2$s</xliff:g> се възпроизвежда от <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> от <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Google Play"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отваряне на <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пускане на <xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="ARTIST_NAME">%2$s</xliff:g> от <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Засега Wi-Fi няма да се свързва автоматично"</string> <string name="see_all_networks" msgid="3773666844913168122">"Вижте всички"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"За да превключите мрежите, прекъснете връзката с Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Избор на потребител"</string> </resources> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 57e70a6772a0..4ba14369746d 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -1090,6 +1090,8 @@ <string name="controls_media_resume" msgid="1933520684481586053">"আবার চালু করুন"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"সেটিংস"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-এর <xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%3$s</xliff:g> অ্যাপে চলছে"</string> + <!-- no translation found for controls_media_seekbar_description (4389621713616214611) --> + <skip /> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"চালান"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> অ্যাপ খুলুন"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-এর <xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%3$s</xliff:g> অ্যাপে চালান"</string> @@ -1178,4 +1180,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"এখন ওয়াই-ফাই নিজে থেকে কানেক্ট হবে না"</string> <string name="see_all_networks" msgid="3773666844913168122">"সবকটি দেখুন"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"নেটওয়ার্ক বদলাতে ইথারনেট ডিসকানেক্ট করুন"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ব্যবহারকারী বেছে নিন"</string> </resources> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index c8588ecd4cdd..95bca335e7b0 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -1096,6 +1096,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Postavke"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Pjesma <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se reproducira pomoću aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Pokrenite"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvorite aplikaciju <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1184,4 +1185,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"WiFi se trenutno ne može automatski povezati"</string> <string name="see_all_networks" msgid="3773666844913168122">"Prikaži sve"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Da promijenite mrežu, isključite ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Odaberite korisnika"</string> </resources> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index cfe70c2a22c9..2ca9897d325e 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Reprèn"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Configuració"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) s\'està reproduint des de l\'aplicació <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reprodueix"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Obre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reprodueix <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) des de l\'aplicació <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Per ara la Wi‑Fi no es connectarà automàticament"</string> <string name="see_all_networks" msgid="3773666844913168122">"Mostra-ho tot"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Per canviar de xarxa, desconnecta la connexió Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecciona un usuari"</string> </resources> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 3d648cb03309..bb205bfaaa50 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -716,7 +716,7 @@ <string name="tuner_full_importance_settings" msgid="1388025816553459059">"Rozšířené ovládací prvky oznámení"</string> <string name="tuner_full_importance_settings_on" msgid="917981436602311547">"Zapnuto"</string> <string name="tuner_full_importance_settings_off" msgid="5580102038749680829">"Vypnuto"</string> - <string name="power_notification_controls_description" msgid="1334963837572708952">"Rozšířené ovládací prvky oznámení umožňují nastavit úroveň důležitosti oznámení aplikace od 0 do 5. \n\n"<b>"Úroveň 5"</b>" \n– Zobrazit na začátku seznamu oznámení \n– Povolit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 4"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 3"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n\n"<b>"Úroveň 2"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat žádný zvukový signál ani nevibrovat \n\n"<b>"Úroveň 1"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat zvukový signál ani nevibrovat \n– Skrýt z obrazovky uzamčení a stavového řádku \n– Zobrazovat na konci seznamu oznámení \n\n"<b>";Úroveň 0"</b>" \n– Blokovat všechna oznámení z aplikace"</string> + <string name="power_notification_controls_description" msgid="1334963837572708952">"Rozšířené ovládací prvky oznámení umožňují nastavit úroveň důležitosti oznámení aplikace od 0 do 5. \n\n"<b>"Úroveň 5"</b>" \n– Zobrazit na začátku seznamu oznámení \n– Povolit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 4"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Vždy zobrazit náhled \n\n"<b>"Úroveň 3"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n\n"<b>"Úroveň 2"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat žádný zvukový signál ani nevibrovat \n\n"<b>"Úroveň 1"</b>" \n– Zabránit vyrušení na celou obrazovku \n– Nikdy nezobrazovat náhled \n– Nikdy nevydávat zvukový signál ani nevibrovat \n– Skrýt na obrazovce uzamčení a stavového řádku \n– Zobrazovat na konci seznamu oznámení \n\n"<b>";Úroveň 0"</b>" \n– Blokovat všechna oznámení z aplikace"</string> <string name="notification_header_default_channel" msgid="225454696914642444">"Oznámení"</string> <string name="notification_channel_disabled" msgid="928065923928416337">"Tato oznámení již nebudete dostávat"</string> <string name="notification_channel_minimized" msgid="6892672757877552959">"Tato oznámení budou minimalizována"</string> @@ -1102,6 +1102,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Pokračovat"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavení"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Skladba <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> hrajte z aplikace <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> z <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Přehrát"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otevřít aplikaci <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Přehrát skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikace <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1190,4 +1191,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi se prozatím nebude připojovat automaticky"</string> <string name="see_all_networks" msgid="3773666844913168122">"Zobrazit vše"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Pokud chcete přepnout sítě, odpojte ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Zvolte uživatele"</string> </resources> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index eb715aa8e7ca..1a251d26b4ce 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Genoptag"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Indstillinger"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> af <xliff:g id="ARTIST_NAME">%2$s</xliff:g> afspilles via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> af <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Afspil"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Åbn <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> af <xliff:g id="ARTIST_NAME">%2$s</xliff:g> via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Ingen automatisk forbindelse til Wi-Fi i øjeblikket"</string> <string name="see_all_networks" msgid="3773666844913168122">"Se alle"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Afbryd ethernetforbindelsen for at skifte netværk"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Vælg bruger"</string> </resources> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index b8154923bd63..ce469d57aa43 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -1090,6 +1090,8 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Fortsetzen"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Einstellungen"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> von <xliff:g id="ARTIST_NAME">%2$s</xliff:g> wird gerade über <xliff:g id="APP_LABEL">%3$s</xliff:g> wiedergegeben"</string> + <!-- no translation found for controls_media_seekbar_description (4389621713616214611) --> + <skip /> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Wiedergeben"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> öffnen"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> von <xliff:g id="ARTIST_NAME">%2$s</xliff:g> über <xliff:g id="APP_LABEL">%3$s</xliff:g> wiedergeben"</string> @@ -1178,4 +1180,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Zurzeit wird keine automatische WLAN-Verbindung hergestellt"</string> <string name="see_all_networks" msgid="3773666844913168122">"Alle ansehen"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Trenne das Ethernetkabel, um das Netzwerk zu wechseln"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Nutzer auswählen"</string> </resources> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index c6cc7f0d4212..2fa648cbb1c7 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Συνέχιση"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Ρυθμίσεις"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Γίνεται αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> από <xliff:g id="ARTIST_NAME">%2$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> από <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Άνοιγμα της εφαρμογής <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> από <xliff:g id="ARTIST_NAME">%2$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Δεν θα γίνεται προς το παρόν αυτόματη σύνδεση Wi-Fi."</string> <string name="see_all_networks" msgid="3773666844913168122">"Εμφάνιση όλων"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Για εναλλαγή δικτύων, αποσυνδέστε το ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Επιλογή χρήστη"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index e1006fc83780..84b24d68fa08 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi‑Fi won’t auto-connect for now"</string> <string name="see_all_networks" msgid="3773666844913168122">"See all"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 66b44d5ecf8b..c435a8367e68 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi‑Fi won’t auto-connect for now"</string> <string name="see_all_networks" msgid="3773666844913168122">"See all"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index e1006fc83780..84b24d68fa08 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi‑Fi won’t auto-connect for now"</string> <string name="see_all_networks" msgid="3773666844913168122">"See all"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index e1006fc83780..84b24d68fa08 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi‑Fi won’t auto-connect for now"</string> <string name="see_all_networks" msgid="3773666844913168122">"See all"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> </resources> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index 9af7322e8507..1ed76dc1ddb5 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Resume"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Settings"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> is playing from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> of <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Play"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi‑Fi won’t auto-connect for now"</string> <string name="see_all_networks" msgid="3773666844913168122">"See all"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Select user"</string> </resources> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 8d53bb99f3e0..4875e63614b8 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Reanudar"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Configuración"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Se está reproduciendo <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproducir"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Por ahora, el Wi-Fi no se conectará automáticamente"</string> <string name="see_all_networks" msgid="3773666844913168122">"Ver todo"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para cambiar de red, desconéctate de Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string> </resources> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 8db8770f82bd..af328945b1b4 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -663,8 +663,8 @@ <string name="status_bar" msgid="4357390266055077437">"Barra de estado"</string> <string name="overview" msgid="3522318590458536816">"Aplicaciones recientes"</string> <string name="demo_mode" msgid="263484519766901593">"Modo de demostración de UI del sistema"</string> - <string name="enable_demo_mode" msgid="3180345364745966431">"Habilitar modo de demostración"</string> - <string name="show_demo_mode" msgid="3677956462273059726">"Mostrar modo de demostración"</string> + <string name="enable_demo_mode" msgid="3180345364745966431">"Habilitar modo demo"</string> + <string name="show_demo_mode" msgid="3677956462273059726">"Mostrar modo demo"</string> <string name="status_bar_ethernet" msgid="5690979758988647484">"Ethernet"</string> <string name="status_bar_alarm" msgid="87160847643623352">"Alarma"</string> <string name="wallet_title" msgid="5369767670735827105">"Wallet"</string> @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Reanudar"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Ajustes"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Se está reproduciendo <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproducir"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Por ahora no se conectará automáticamente a redes Wi-Fi"</string> <string name="see_all_networks" msgid="3773666844913168122">"Ver todo"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para cambiar de red, desconecta el cable Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string> </resources> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index c3cb5520db83..ebab6f3825a7 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Jätka"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Seaded"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> esitajalt <xliff:g id="ARTIST_NAME">%2$s</xliff:g> esitatakse rakenduses <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Esitamine"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Rakenduse <xliff:g id="APP_LABEL">%1$s</xliff:g> avamine"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> esitajalt <xliff:g id="ARTIST_NAME">%2$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"WiFi-ühendust ei looda praegu automaatselt"</string> <string name="see_all_networks" msgid="3773666844913168122">"Kuva kõik"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Võrkude vahetamiseks katkestage Etherneti-ühendus"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kasutaja valimine"</string> </resources> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 8df329a899e3..0d5898653826 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Berrekin"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Ezarpenak"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) ari da erreproduzitzen <xliff:g id="APP_LABEL">%3$s</xliff:g> bidez"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Erreproduzitu"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ireki <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Erreproduzitu <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> bidez"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Oraingoz ez da automatikoki konektatuko wifira"</string> <string name="see_all_networks" msgid="3773666844913168122">"Ikusi guztiak"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Sarea aldatzeko, deskonektatu Ethernet-a"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Hautatu erabiltzaile bat"</string> </resources> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index ae308b7ed434..dfc88f4b4258 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -1090,6 +1090,8 @@ <string name="controls_media_resume" msgid="1933520684481586053">"ازسرگیری"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"تنظیمات"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> از <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ازطریق <xliff:g id="APP_LABEL">%3$s</xliff:g> پخش میشود"</string> + <!-- no translation found for controls_media_seekbar_description (4389621713616214611) --> + <skip /> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"پخش"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"باز کردن <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> از <xliff:g id="ARTIST_NAME">%2$s</xliff:g> را ازطریق <xliff:g id="APP_LABEL">%3$s</xliff:g> پخش کنید"</string> @@ -1178,4 +1180,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"فعلاً Wi-Fi بهطور خودکار متصل نمیشود"</string> <string name="see_all_networks" msgid="3773666844913168122">"مشاهده همه"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"برای تغییر شبکه، اترنت را قطع کنید"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"انتخاب کاربر"</string> </resources> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index ee0cdb65a83d..c62f66401345 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -1090,6 +1090,8 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Jatka"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Asetukset"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> soittaa nyt tätä: <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>)"</string> + <!-- no translation found for controls_media_seekbar_description (4389621713616214611) --> + <skip /> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Toista"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Avaa <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Soita <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) sovelluksessa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1180,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi ei toistaiseksi yhdistä automaattisesti"</string> <string name="see_all_networks" msgid="3773666844913168122">"Näytä kaikki"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Irrota Ethernet-johto, jos haluat vaihtaa verkkoa"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Valitse käyttäjä"</string> </resources> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 7fcbcfb6eb0b..0f60249c8871 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Reprendre"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Paramètres"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> est en cours de lecteur à partir de <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Faire jouer"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ouvrez <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Lecture de <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> à partir de <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Connexion automatique au Wi-Fi impossible pour le moment"</string> <string name="see_all_networks" msgid="3773666844913168122">"Tout afficher"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Pour changer de réseau, débranchez le câble Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Sélect. utilisateur"</string> </resources> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 74e55fc90687..c31ccafc51c8 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -895,7 +895,7 @@ <string name="right_icon" msgid="1103955040645237425">"Icône droite"</string> <string name="drag_to_add_tiles" msgid="8933270127508303672">"Faites glisser les blocs pour les ajouter"</string> <string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"Faites glisser les blocs pour les réorganiser"</string> - <string name="drag_to_remove_tiles" msgid="4682194717573850385">"Faites glisser les icônes ici pour les supprimer."</string> + <string name="drag_to_remove_tiles" msgid="4682194717573850385">"Faites glisser les blocs ici pour les supprimer"</string> <string name="drag_to_remove_disabled" msgid="933046987838658850">"Au minimum <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> tuiles sont nécessaires"</string> <string name="qs_edit" msgid="5583565172803472437">"Modifier"</string> <string name="tuner_time" msgid="2450785840990529997">"Heure"</string> @@ -1090,6 +1090,8 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Reprendre"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Paramètres"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> est en cours de lecture depuis <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <!-- no translation found for controls_media_seekbar_description (4389621713616214611) --> + <skip /> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Lire"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ouvre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> depuis <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1180,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Connexion automatique au Wi-Fi désactivée pour le moment"</string> <string name="see_all_networks" msgid="3773666844913168122">"Tout afficher"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Pour changer de réseau, déconnectez l\'Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Choisir utilisateur"</string> </resources> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 3bcfcc8e6a88..002f0a1a415e 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Configuración"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Estase reproducindo <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproducir"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"De momento, a wifi non se conectará automaticamente"</string> <string name="see_all_networks" msgid="3773666844913168122">"Ver todo"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para cambiar de rede, desconecta a Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleccionar usuario"</string> </resources> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index e713cb79f6dc..cf001fc70231 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -1090,6 +1090,8 @@ <string name="controls_media_resume" msgid="1933520684481586053">"ફરી શરૂ કરો"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"સેટિંગ"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> પર <xliff:g id="ARTIST_NAME">%2$s</xliff:g>નું <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચાલી રહ્યું છે"</string> + <!-- no translation found for controls_media_seekbar_description (4389621713616214611) --> + <skip /> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ચલાવો"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ખોલો"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> પર <xliff:g id="ARTIST_NAME">%2$s</xliff:g>નું <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચલાવો"</string> @@ -1178,4 +1180,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"હમણાં પૂરતું વાઇ-ફાઇ ઑટોમૅટિક રીતે કનેક્ટ થશે નહીં"</string> <string name="see_all_networks" msgid="3773666844913168122">"બધા જુઓ"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"બીજા નેટવર્ક પર જવા માટે, ઇથરનેટ ડિસ્કનેક્ટ કરો"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"વપરાશકર્તા પસંદ કરો"</string> </resources> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index edd7fc6799bf..143152e41444 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"फिर से शुरू करें"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिंग"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> पर, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> का <xliff:g id="SONG_NAME">%1$s</xliff:g> चल रहा है"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> में से <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"चलाएं"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> खोलें"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> पर, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> का <xliff:g id="SONG_NAME">%1$s</xliff:g> चलाएं"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"फ़िलहाल, वाई-फ़ाई अपने-आप कनेक्ट नहीं होगा"</string> <string name="see_all_networks" msgid="3773666844913168122">"सभी देखें"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"नेटवर्क बदलने के लिए, पहले ईथरनेट को डिसकनेक्ट करें"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"उपयोगकर्ता चुनें"</string> </resources> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index b066191b021a..206f297a31b2 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -1096,6 +1096,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Nastavi"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Postavke"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> reproducira se putem aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reprodukcija"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvori <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> putem aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1184,4 +1185,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi se zasad neće automatski povezivati"</string> <string name="see_all_networks" msgid="3773666844913168122">"Prikaži sve"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Da biste se prebacili na drugu mrežu, odspojite Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Odabir korisnika"</string> </resources> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 5880cf0abb02..ab91233d974f 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Folytatás"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Beállítások"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> <xliff:g id="SONG_NAME">%1$s</xliff:g> című száma hallható itt: <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>/<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Játék"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> megnyitása"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> <xliff:g id="SONG_NAME">%1$s</xliff:g> című számának lejátszása innen: <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"A Wi-Fi-re történő csatlakozás jelenleg nem automatikus"</string> <string name="see_all_networks" msgid="3773666844913168122">"Megtekintés"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Hálózatváltáshoz válassza le az ethernetet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Felhasználóválasztás"</string> </resources> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index ad3cf25467d9..7638843141ca 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Շարունակել"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Կարգավորումներ"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Այժմ նվագարկվում է <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-ի կատարմամբ <xliff:g id="APP_LABEL">%3$s</xliff:g> հավելվածից"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>՝ <xliff:g id="TOTAL_TIME">%2$s</xliff:g>-ից"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Նվագարկել"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Բացեք <xliff:g id="APP_LABEL">%1$s</xliff:g> հավելվածը"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Նվագարկել <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-ի կատարմամբ <xliff:g id="APP_LABEL">%3$s</xliff:g> հավելվածից"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi-ն ավտոմատ չի միանա"</string> <string name="see_all_networks" msgid="3773666844913168122">"Տեսնել բոլորը"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Մի ցանցից մյուսին անցնելու համար անջատեք Ethernet-ը"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Ընտրեք օգտատեր"</string> </resources> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index c42b143709c1..17456903d0b3 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Lanjutkan"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Setelan"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> sedang diputar dari <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> dari <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Putar"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buka <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Putar <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> dari <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi tidak akan otomatis terhubung untuk saat ini"</string> <string name="see_all_networks" msgid="3773666844913168122">"Lihat semua"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Untuk beralih jaringan, lepaskan kabel ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pilih pengguna"</string> </resources> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 469228065c8d..100b6f2f5bda 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Halda áfram"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Stillingar"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> með <xliff:g id="ARTIST_NAME">%2$s</xliff:g> er í spilun á <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> af <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Spila"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Opna <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spila <xliff:g id="SONG_NAME">%1$s</xliff:g> með <xliff:g id="ARTIST_NAME">%2$s</xliff:g> í <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi tengist ekki sjálfkrafa eins og er"</string> <string name="see_all_networks" msgid="3773666844913168122">"Sjá allt"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Aftengdu ethernet til að skipta um net"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Velja notanda"</string> </resources> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 1d96598be708..15598f000ecb 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Riprendi"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Impostazioni"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> di <xliff:g id="ARTIST_NAME">%2$s</xliff:g> è in riproduzione da <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> di <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Riproduci"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Apri <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Riproduci <xliff:g id="SONG_NAME">%1$s</xliff:g> di <xliff:g id="ARTIST_NAME">%2$s</xliff:g> da <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Connessione automatica rete Wi-Fi non attiva al momento"</string> <string name="see_all_networks" msgid="3773666844913168122">"Mostra tutte"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Per cambiare rete, scollega il cavo Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Seleziona utente"</string> </resources> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 8fd2bb9abf22..9f019b49345b 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -1102,6 +1102,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"המשך"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"הגדרות"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> של <xliff:g id="ARTIST_NAME">%2$s</xliff:g> מופעל מ-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> מתוך <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"הפעלה"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"פתיחה של <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> של <xliff:g id="ARTIST_NAME">%2$s</xliff:g> מ-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1190,4 +1191,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"ה-Wi-Fi לא יתחבר באופן אוטומטי בינתיים"</string> <string name="see_all_networks" msgid="3773666844913168122">"הצגת הכול"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"כדי לעבור בין רשתות, צריך לנתק את האתרנט"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"בחירת משתמש"</string> </resources> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 3134f73605c1..94dbd0e10354 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"再開"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g>(アーティスト名: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>)が <xliff:g id="APP_LABEL">%3$s</xliff:g> で再生中"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"再生"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> を開く"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g>(アーティスト名: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>)を <xliff:g id="APP_LABEL">%3$s</xliff:g> で再生"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi に自動接続しません"</string> <string name="see_all_networks" msgid="3773666844913168122">"すべて表示"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ネットワークを変更するにはイーサネット接続を解除してください"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ユーザーの選択"</string> </resources> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index c4c73c5cb912..889ce247fea2 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"გაგრძელება"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"პარამეტრები"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, უკრავს <xliff:g id="APP_LABEL">%3$s</xliff:g>-დან"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>-დან <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"დაკვრა"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"გახსენით <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="APP_LABEL">%3$s</xliff:g>-დან"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi ინტერნეტს დროებით ავტომატურად არ დაუკავშირდება"</string> <string name="see_all_networks" msgid="3773666844913168122">"ყველას ნახვა"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ქსელების გადასართავად, გაწყვიტეთ Ethernet-თან კავშირი"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"მომხმარებლის არჩევა"</string> </resources> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index bccc0ab7d937..b4f1d926587d 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Жалғастыру"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Параметрлер"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> қолданбасында <xliff:g id="ARTIST_NAME">%2$s</xliff:g> орындайтын \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әні ойнатылуда."</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>/<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Ойнату"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> қолданбасын ашу"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> қолданбасында <xliff:g id="ARTIST_NAME">%2$s</xliff:g> орындайтын \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әнін ойнату"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Әзірше Wi-Fi автоматты түрде қосылмайды."</string> <string name="see_all_networks" msgid="3773666844913168122">"Барлығын көру"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Желілерді ауыстыру үшін ethernet кабелін ажыратыңыз."</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Пайдаланушыны таңдау"</string> </resources> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 7d79023c1c61..3896eb063a36 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"បន្ត"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"ការកំណត់"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ច្រៀងដោយ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> កំពុងចាក់ពី <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> នៃ <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ចាក់"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"បើក <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ចាក់ <xliff:g id="SONG_NAME">%1$s</xliff:g> ច្រៀងដោយ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ពី <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi នឹងមិនភ្ជាប់ដោយស្វ័យប្រវត្តិក្នុងពេលនេះទេ"</string> <string name="see_all_networks" msgid="3773666844913168122">"មើលទាំងអស់"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ដើម្បីប្ដូរបណ្ដាញ សូមផ្ដាច់អ៊ីសឺរណិត"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ជ្រើសរើសអ្នកប្រើប្រាស់"</string> </resources> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index ca69a82a1295..9d908841c849 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -1090,6 +1090,8 @@ <string name="controls_media_resume" msgid="1933520684481586053">"ಪುನರಾರಂಭಿಸಿ"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"ಸೆಟ್ಟಿಂಗ್ಗಳು"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ಅವರ <xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%3$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲಾಗುತ್ತಿದೆ"</string> + <!-- no translation found for controls_media_seekbar_description (4389621713616214611) --> + <skip /> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ಪ್ಲೇ ಮಾಡಿ"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ಅನ್ನು ತೆರೆಯಿರಿ"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ಅವರ <xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%3$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string> @@ -1178,4 +1180,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"ಸದ್ಯದ ಮಟ್ಟಿಗೆ ವೈ-ಫೈ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಕನೆಕ್ಟ್ ಆಗುವುದಿಲ್ಲ"</string> <string name="see_all_networks" msgid="3773666844913168122">"ಎಲ್ಲವನ್ನೂ ನೋಡಿ"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ನೆಟ್ವರ್ಕ್ಗಳನ್ನು ಬದಲಿಸಲು, ಇಥರ್ನೆಟ್ ಅನ್ನು ಡಿಸ್ಕನೆಕ್ಟ್ ಮಾಡಿ"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ಬಳಕೆದಾರ ಆಯ್ಕೆಮಾಡಿ"</string> </resources> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 3a8657ba30a6..4ca855d1d77d 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"다시 시작"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"설정"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g>에서 <xliff:g id="ARTIST_NAME">%2$s</xliff:g>의 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생 중"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"재생"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> 열기"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>에서 <xliff:g id="ARTIST_NAME">%2$s</xliff:g>의 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"지금은 Wi-Fi가 자동으로 연결되지 않습니다."</string> <string name="see_all_networks" msgid="3773666844913168122">"모두 보기"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"네트워크를 전환하려면 이더넷을 연결 해제하세요."</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"사용자 선택"</string> </resources> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 9f242f34b300..77a933cc8c42 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Улантуу"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Жөндөөлөр"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ыры (аткаруучу: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> колдонмосунан ойнотулуп жатат"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> ичинен <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Ойнотуу"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> колдонмосун ачуу"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын (аткаруучу: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> колдонмосунан ойнотуу"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi азырынча автоматтык түрдө туташпайт"</string> <string name="see_all_networks" msgid="3773666844913168122">"Баарын көрүү"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Башка тармактарга которулуу үчүн Ethernet кабелин ажыратыңыз"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Колдонуучуну тандоо"</string> </resources> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index afd914d738ae..351b19828f5f 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"ສືບຕໍ່"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"ການຕັ້ງຄ່າ"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ໂດຍ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ກຳລັງຫຼິ້ນຈາກ <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> ຈາກ <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ຫຼິ້ນ"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"ເປີດ <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ຫຼິ້ນ <xliff:g id="SONG_NAME">%1$s</xliff:g> ໂດຍ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ຈາກ <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi ຈະບໍ່ເຊື່ອມຕໍ່ອັດຕະໂນມັດສຳລັບຕອນນີ້"</string> <string name="see_all_networks" msgid="3773666844913168122">"ເບິ່ງທັງໝົດ"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ເພື່ອສະຫຼັບເຄືອຂ່າຍ, ໃຫ້ຕັດການເຊື່ອມຕໍ່ອີເທີເນັດກ່ອນ"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ເລືອກຜູ້ໃຊ້"</string> </resources> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 233610f00cd0..3e4d3ffbe1ba 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -1102,6 +1102,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Tęsti"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Nustatymai"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> – „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ leidžiama iš „<xliff:g id="APP_LABEL">%3$s</xliff:g>“"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> iš <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Leisti"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Atidaryti „<xliff:g id="APP_LABEL">%1$s</xliff:g>“"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Leisti <xliff:g id="ARTIST_NAME">%2$s</xliff:g> – „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ iš „<xliff:g id="APP_LABEL">%3$s</xliff:g>“"</string> @@ -1190,4 +1191,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"„Wi-Fi“ šiuo metu nebus prijungtas automatiškai"</string> <string name="see_all_networks" msgid="3773666844913168122">"Žiūrėti viską"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Norėdami perjungti tinklus, atjunkite eternetą"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Naudotojo pasirinkimas"</string> </resources> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 83e6d6e514ea..f0d1bba3382d 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -1096,6 +1096,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Atsākt"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Iestatījumi"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Tiek atskaņots fails “<xliff:g id="SONG_NAME">%1$s</xliff:g>” (izpildītājs: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) no lietotnes <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> no <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Atskaņot"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Atveriet lietotni <xliff:g id="APP_LABEL">%1$s</xliff:g>."</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Atskaņojiet failu “<xliff:g id="SONG_NAME">%1$s</xliff:g>” (izpildītājs: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) no lietotnes <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string> @@ -1184,4 +1185,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi savienojums īslaicīgi netiks izveidots automātiski."</string> <string name="see_all_networks" msgid="3773666844913168122">"Visu tīklu skatīšana"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Lai pārslēgtu tīklus, atvienojiet tīkla Ethernet vadu."</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Lietotāja atlase"</string> </resources> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index e00108a7b9fb..29feaecc8bf6 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Продолжи"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Поставки"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> од <xliff:g id="ARTIST_NAME">%2$s</xliff:g> е пуштено на <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> од <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Пушти"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отворете <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пуштете <xliff:g id="SONG_NAME">%1$s</xliff:g> од <xliff:g id="ARTIST_NAME">%2$s</xliff:g> на <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi нема да се поврзува автоматски засега"</string> <string name="see_all_networks" msgid="3773666844913168122">"Прикажи ги сите"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"За промена на мрежата, прекинете ја врската со етернетот"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Изберете корисник"</string> </resources> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 49beea2dcb32..674ed8492704 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"പുനരാരംഭിക്കുക"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"ക്രമീകരണം"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> എന്ന ആർട്ടിസ്റ്റിന്റെ <xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%3$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുന്നു"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>-ൽ <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"പ്ലേ ചെയ്യുക"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> തുറക്കുക"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> എന്ന ആർട്ടിസ്റ്റിന്റെ <xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%3$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുക"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"വൈഫൈ ഇപ്പോൾ സ്വയമേവ കണക്റ്റ് ചെയ്യില്ല"</string> <string name="see_all_networks" msgid="3773666844913168122">"എല്ലാം കാണുക"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"മറ്റ് നെറ്റ്വർക്കുകളിലേക്ക് മാറാൻ, ഇതർനെറ്റ് വിച്ഛേദിക്കുക"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ഉപയോക്താവിനെ തിരഞ്ഞെടുക്കൂ"</string> </resources> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index f6dea18f0417..9cce14ab25e3 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Үргэлжлүүлэх"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Тохиргоо"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> дээр тоглуулж буй <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-н <xliff:g id="SONG_NAME">%1$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>-н <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Тоглуулах"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g>-г нээх"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-н <xliff:g id="SONG_NAME">%1$s</xliff:g>-г <xliff:g id="APP_LABEL">%3$s</xliff:g> дээр тоглуулах"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi-г одоогоор автоматаар холбохгүй"</string> <string name="see_all_networks" msgid="3773666844913168122">"Бүгдийг харах"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Сүлжээг сэлгэхийн тулд этернэтийг салгана уу"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Хэрэглэгч сонгох"</string> </resources> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 6156cf592def..9753209792cc 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -848,7 +848,7 @@ <string name="keyboard_shortcut_group_applications_sms" msgid="6912633831752843566">"SMS"</string> <string name="keyboard_shortcut_group_applications_music" msgid="9032078456666204025">"संगीत"</string> <string name="keyboard_shortcut_group_applications_youtube" msgid="5078136084632450333">"YouTube"</string> - <string name="keyboard_shortcut_group_applications_calendar" msgid="4229602992120154157">"Calendar"</string> + <string name="keyboard_shortcut_group_applications_calendar" msgid="4229602992120154157">"कॅलेंडर"</string> <string name="tuner_full_zen_title" msgid="5120366354224404511">"आवाज नियंत्रणांसह दर्शवा"</string> <string name="volume_and_do_not_disturb" msgid="502044092739382832">"व्यत्यय आणू नका"</string> <string name="volume_dnd_silent" msgid="4154597281458298093">"आवाजाच्या बटणांचा शार्टकट"</string> @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"पुन्हा सुरू करा"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिंग्ज"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> मध्ये <xliff:g id="ARTIST_NAME">%2$s</xliff:g> चे <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले होत आहे"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> पैकी <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"प्ले करणे"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> उघडा"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> मध्ये <xliff:g id="ARTIST_NAME">%2$s</xliff:g> चे <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले करा"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"सध्या वाय-फाय ऑटो-कनेक्ट होणार नाही"</string> <string name="see_all_networks" msgid="3773666844913168122">"सर्व पहा"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"नेटवर्क स्विच करण्यासाठी, इथरनेट केबल डिस्कनेक्ट करा"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"वापरकर्ता निवडा"</string> </resources> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 9dde6b667bc5..4418273f7399 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Sambung semula"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Tetapan"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> dimainkan daripada <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> daripada <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Main"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buka <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mainkan <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> daripada <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi tidak akan disambungkan secara automatik buat masa ini"</string> <string name="see_all_networks" msgid="3773666844913168122">"Lihat semua"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Untuk menukar rangkaian, putuskan sambungan ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pilih pengguna"</string> </resources> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 666f9edf8e59..0fed37b454db 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"ဆက်လုပ်ရန်"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"ဆက်တင်များ"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ၏ <xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%3$s</xliff:g> တွင် ဖွင့်ထားသည်"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> အနက် <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ဖွင့်ခြင်း"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ကို ဖွင့်ပါ"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ၏ <xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%3$s</xliff:g> တွင် ဖွင့်ပါ"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi က လောလောဆယ် အလိုအလျောက် ချိတ်ဆက်မည်မဟုတ်ပါ"</string> <string name="see_all_networks" msgid="3773666844913168122">"အားလုံးကြည့်ရန်"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ကွန်ရက်ပြောင်းရန် အီသာနက်ကို ချိတ်ဆက်မှုဖြုတ်ပါ"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"အသုံးပြုသူ ရွေးခြင်း"</string> </resources> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index c25bec73adf7..c37c81c2b48a 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -1090,6 +1090,8 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Gjenoppta"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Innstillinger"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> av <xliff:g id="ARTIST_NAME">%2$s</xliff:g> spilles av fra <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <!-- no translation found for controls_media_seekbar_description (4389621713616214611) --> + <skip /> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Spill av"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Åpne <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spill av <xliff:g id="SONG_NAME">%1$s</xliff:g> av <xliff:g id="ARTIST_NAME">%2$s</xliff:g> fra <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1180,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi kobles ikke til automatisk inntil videre"</string> <string name="see_all_networks" msgid="3773666844913168122">"Se alle"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"For å bytte nettverk, koble fra Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Velg bruker"</string> </resources> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index ce1787c26897..2405d5038ba7 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"सुचारु गर्नुहोस्"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"सेटिङ"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> को <xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%3$s</xliff:g> मा बज्दै छ"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> मध्ये <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"प्ले गर्नुहोस्"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> खोल्नुहोस्"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> को <xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%3$s</xliff:g> मा बजाउनुहोस्"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"केही समयका लागि Wi-Fi स्वतः कनेक्ट हुँदैन"</string> <string name="see_all_networks" msgid="3773666844913168122">"सबै नेटवर्क हेर्नुहोस्"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"नेटवर्क बदल्न इथरनेट डिस्कनेक्ट गर्नुहोस्"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"प्रयोगकर्ता चयन गर्नु…"</string> </resources> diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml index ffcc3a821d36..07e28b6d7f20 100644 --- a/packages/SystemUI/res/values-night/styles.xml +++ b/packages/SystemUI/res/values-night/styles.xml @@ -16,7 +16,9 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android"> - <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog" /> + <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog"> + <item name="android:buttonCornerRadius">28dp</item> + </style> <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert" /> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index ad1403e664df..b60a76dbab40 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Hervatten"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Instellingen"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> van <xliff:g id="ARTIST_NAME">%2$s</xliff:g> wordt afgespeeld via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> van <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Afspelen"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> openen"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> van <xliff:g id="ARTIST_NAME">%2$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wifi maakt momenteel niet automatisch verbinding"</string> <string name="see_all_networks" msgid="3773666844913168122">"Alles tonen"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Verbreek de ethernetverbinding om van netwerk te wisselen"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Gebruiker selecteren"</string> </resources> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index e5c2a2b61825..f276a227941f 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -1090,6 +1090,8 @@ <string name="controls_media_resume" msgid="1933520684481586053">"ପୁଣି ଆରମ୍ଭ କରନ୍ତୁ"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"ସେଟିଂସ୍"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ରୁ <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ଙ୍କ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚାଲୁଛି"</string> + <!-- no translation found for controls_media_seekbar_description (4389621713616214611) --> + <skip /> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ଚଲାନ୍ତୁ"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ଖୋଲନ୍ତୁ"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ରୁ <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ଙ୍କ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string> @@ -1178,4 +1180,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"ବର୍ତ୍ତମାନ ପାଇଁ ୱାଇ-ଫାଇ ସ୍ୱତଃ-ସଂଯୋଗ ହେବ ନାହିଁ"</string> <string name="see_all_networks" msgid="3773666844913168122">"ସବୁ ଦେଖନ୍ତୁ"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ନେଟୱାର୍କ ସ୍ୱିଚ୍ କରିବାକୁ, ଇଥରନେଟ୍ ବିଚ୍ଛିନ୍ନ କରନ୍ତୁ"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ଉପଯୋଗକର୍ତ୍ତା ଚୟନ କର"</string> </resources> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 48750cf95731..0ca4f7dd06e5 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"ਮੁੜ-ਚਾਲੂ ਕਰੋ"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"ਸੈਟਿੰਗਾਂ"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ਤੋਂ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ਦਾ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚੱਲ ਰਿਹਾ ਹੈ"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g> ਵਿੱਚੋਂ <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ਚਲਾਓ"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ਖੋਲ੍ਹੋ"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ਤੋਂ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ਦਾ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚਲਾਓ"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"ਫ਼ਿਲਹਾਲ ਵਾਈ-ਫਾਈ ਸਵੈ-ਕਨੈਕਟ ਨਹੀਂ ਹੋਵੇਗਾ"</string> <string name="see_all_networks" msgid="3773666844913168122">"ਸਭ ਦੇਖੋ"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ਨੈੱਟਵਰਕਾਂ ਨੂੰ ਬਦਲਣ ਲਈ, ਈਥਰਨੈੱਟ ਨੂੰ ਡਿਸਕਨੈਕਟ ਕਰੋ"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"ਵਰਤੋਂਕਾਰ ਚੁਣੋ"</string> </resources> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 3635876b04da..73b538f3f20b 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -1102,6 +1102,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Wznów"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Ustawienia"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Aplikacja <xliff:g id="APP_LABEL">%3$s</xliff:g> odtwarza utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>)"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> z <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Odtwórz"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otwórz aplikację <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Odtwórz utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) w aplikacji <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1190,4 +1191,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi nie będzie na razie włączać się automatycznie"</string> <string name="see_all_networks" msgid="3773666844913168122">"Pokaż wszystko"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Aby przełączać sieci, odłącz Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Wybierz użytkownika"</string> </resources> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 8f499f7fbb04..0205e8cbbc86 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Configurações"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Tocando <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Iniciar"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"A conexão automática ao Wi-Fi ficará indisponível"</string> <string name="see_all_networks" msgid="3773666844913168122">"Ver tudo"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para mudar de rede, desconecte o cabo Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecionar usuário"</string> </resources> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index b0c65d866b78..3be4e7238742 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Definições"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> em reprodução a partir da app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproduzir"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduzir <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> a partir da app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Por agora, o Wi-Fi não irá estabelecer lig. automaticamente"</string> <string name="see_all_networks" msgid="3773666844913168122">"Veja tudo"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para mudar de rede, desligue a Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecione utilizador"</string> </resources> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 8f499f7fbb04..0205e8cbbc86 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Retomar"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Configurações"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Tocando <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Iniciar"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"A conexão automática ao Wi-Fi ficará indisponível"</string> <string name="see_all_networks" msgid="3773666844913168122">"Ver tudo"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para mudar de rede, desconecte o cabo Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecionar usuário"</string> </resources> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index c143d5349ad4..9630356d6efc 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -1096,6 +1096,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Reia"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Setări"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se redă în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> din <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Redați"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Deschideți <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Redați <xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1184,4 +1185,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Deocamdată, Wi-Fi nu se poate conecta automat"</string> <string name="see_all_networks" msgid="3773666844913168122">"Afișează-le pe toate"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Pentru a schimba rețeaua, deconectați ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Alegeți utilizatorul"</string> </resources> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 752110ea59ec..a386d5ad8a8a 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -1102,6 +1102,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Возобновить"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Настройки"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Воспроизводится медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (исполнитель: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) из приложения \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\"."</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> из <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Воспроизведение"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Открыть приложение \"<xliff:g id="APP_LABEL">%1$s</xliff:g>\""</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Воспроизвести медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (исполнитель: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) из приложения \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string> @@ -1190,4 +1191,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Подключение по Wi-Fi не установится автоматически."</string> <string name="see_all_networks" msgid="3773666844913168122">"Показать все"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Чтобы переключиться между сетями, отключите кабель Ethernet."</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Выберите профиль"</string> </resources> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index b3a7a8e3cf7f..ec03c53afb50 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"නැවත පටන් ගන්න"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"සැකසීම්"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>ගේ <xliff:g id="SONG_NAME">%1$s</xliff:g> ගීතය <xliff:g id="APP_LABEL">%3$s</xliff:g> වෙතින් ධාවනය වෙමින් පවතී"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="TOTAL_TIME">%2$s</xliff:g>කින් <xliff:g id="ELAPSED_TIME">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"වාදනය කරන්න"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> විවෘත කරන්න"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>ගේ <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> වෙතින් වාදනය කරන්න"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi දැනට ස්වයං-සබැඳි නොවනු ඇත"</string> <string name="see_all_networks" msgid="3773666844913168122">"සියල්ල බලන්න"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ජාල මාරු කිරීමට, ඊතර්නෙට් විසන්ධි කරන්න"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"පරිශීලක තෝරන්න"</string> </resources> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index f45226b41ffc..dd4721925649 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -1102,6 +1102,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Pokračovať"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavenia"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> sa prehráva z aplikácie <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> z <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Prehrať"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvoriť <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1190,4 +1191,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi‑Fi sa teraz automaticky nepripojí"</string> <string name="see_all_networks" msgid="3773666844913168122">"Zobraziť všetko"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ak chcete prepnúť siete, odpojte ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Vyberte používateľa"</string> </resources> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index ed753906d8f1..b55c92371c27 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -1102,6 +1102,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Nadaljuj"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Nastavitve"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Skladba <xliff:g id="SONG_NAME">%1$s</xliff:g> izvajalca <xliff:g id="ARTIST_NAME">%2$s</xliff:g> se predvaja iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> od <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Predvajaj"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Odpri aplikacijo <xliff:g id="APP_LABEL">%1$s</xliff:g>."</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> izvajalca <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string> @@ -1190,4 +1191,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Vmesnik Wi-Fi trenutno ne bo samodejno vzpostavil povezave."</string> <string name="see_all_networks" msgid="3773666844913168122">"Prikaz vseh omrežij"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Če želite preklopiti omrežje, prekinite ethernetno povezavo."</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Izberite uporabnika"</string> </resources> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index dc7e82564df0..b728cf2ed144 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Vazhdo"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Cilësimet"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="ARTIST_NAME">%2$s</xliff:g> po luhet nga <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> nga <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Luaj"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Hap <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Luaj <xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="ARTIST_NAME">%2$s</xliff:g> nga <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi nuk do të lidhet automatikisht për momentin"</string> <string name="see_all_networks" msgid="3773666844913168122">"Shiko të gjitha"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Për të ndërruar rrjetet, shkëput Ethernet-in"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Zgjidh përdoruesin"</string> </resources> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index b486589b3bd5..8658df620599 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -1096,6 +1096,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Настави"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Подешавања"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> извођача <xliff:g id="ARTIST_NAME">%2$s</xliff:g> се пушта из апликације <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> од <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Пусти"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отворите <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пустите <xliff:g id="SONG_NAME">%1$s</xliff:g> извођача <xliff:g id="ARTIST_NAME">%2$s</xliff:g> из апликације <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1184,4 +1185,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"WiFi тренутно не може да се аутоматски повеже"</string> <string name="see_all_networks" msgid="3773666844913168122">"Погледајте све"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Да бисте променили мрежу, прекините етернет везу"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Изаберите корисника"</string> </resources> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 5db089bccc58..e1ecff9680b8 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Återuppta"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Inställningar"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> med <xliff:g id="ARTIST_NAME">%2$s</xliff:g> spelas upp från <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> av <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Spela upp"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Öppna <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spela upp <xliff:g id="SONG_NAME">%1$s</xliff:g> med <xliff:g id="ARTIST_NAME">%2$s</xliff:g> från <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Du ansluts inte till wifi automatiskt för närvarande"</string> <string name="see_all_networks" msgid="3773666844913168122">"Visa alla"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Koppla bort Ethernet för att växla nätverk"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Välj användare"</string> </resources> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 60104f623848..7b2f28761703 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Endelea"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Mipangilio"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ulioimbwa na <xliff:g id="ARTIST_NAME">%2$s</xliff:g> unacheza katika <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> kati ya <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Cheza"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Fungua <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> ulioimbwa na <xliff:g id="ARTIST_NAME">%2$s</xliff:g> katika <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi haitaunganishwa kiotomatiki kwa sasa"</string> <string name="see_all_networks" msgid="3773666844913168122">"Angalia yote"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ili kubadili mitandao, tenganisha ethaneti"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Chagua mtumiaji"</string> </resources> diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml new file mode 100644 index 000000000000..e4573c651039 --- /dev/null +++ b/packages/SystemUI/res/values-sw720dp-land/config.xml @@ -0,0 +1,35 @@ +<?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 + --> +<resources> + <!-- Max number of columns for quick controls area --> + <integer name="controls_max_columns">2</integer> + + <!-- The maximum number of rows in the QuickQSPanel --> + <integer name="quick_qs_panel_max_rows">4</integer> + + <!-- The maximum number of tiles in the QuickQSPanel --> + <integer name="quick_qs_panel_max_tiles">8</integer> + + <!-- Whether to use the split 2-column notification shade --> + <bool name="config_use_split_notification_shade">true</bool> + + <!-- The number of columns in the QuickSettings --> + <integer name="quick_settings_num_columns">2</integer> + + <!-- Notifications are sized to match the width of two (of 4) qs tiles in landscape. --> + <bool name="config_skinnyNotifsInLandscape">false</bool> +</resources> diff --git a/packages/SystemUI/res/values-sw720dp-port/config.xml b/packages/SystemUI/res/values-sw720dp-port/config.xml new file mode 100644 index 000000000000..12250861560b --- /dev/null +++ b/packages/SystemUI/res/values-sw720dp-port/config.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources> + <!-- The maximum number of tiles in the QuickQSPanel --> + <integer name="quick_qs_panel_max_tiles">6</integer> + + <!-- The number of columns in the QuickSettings --> + <integer name="quick_settings_num_columns">3</integer> + + <!-- The maximum number of rows in the QuickSettings --> + <integer name="quick_settings_max_rows">3</integer> + +</resources> + diff --git a/packages/SystemUI/res/values-sw720dp/config.xml b/packages/SystemUI/res/values-sw720dp/config.xml index ac44251b2255..436f8d0998f5 100644 --- a/packages/SystemUI/res/values-sw720dp/config.xml +++ b/packages/SystemUI/res/values-sw720dp/config.xml @@ -22,14 +22,5 @@ <resources> <integer name="status_bar_config_maxNotificationIcons">5</integer> - <!-- The maximum number of tiles in the QuickQSPanel --> - <integer name="quick_qs_panel_max_tiles">6</integer> - - <!-- The number of columns in the QuickSettings --> - <integer name="quick_settings_num_columns">3</integer> - - <!-- The maximum number of rows in the QuickSettings --> - <integer name="quick_settings_max_rows">3</integer> - </resources> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 8cc4c8dd5336..815a6f02d4fe 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"தொடர்க"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"அமைப்புகள்"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> இன் <xliff:g id="SONG_NAME">%1$s</xliff:g> பாடல் <xliff:g id="APP_LABEL">%3$s</xliff:g> ஆப்ஸில் பிளேயாகிறது"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> / <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"இயக்குதல்"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ஆப்ஸைத் திறங்கள்"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> இன் <xliff:g id="SONG_NAME">%1$s</xliff:g> பாடலை <xliff:g id="APP_LABEL">%3$s</xliff:g> ஆப்ஸில் பிளேசெய்"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"தற்போதைக்கு வைஃபை தானாக இணைக்கப்படாது"</string> <string name="see_all_networks" msgid="3773666844913168122">"அனைத்தையும் காட்டு"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"நெட்வொர்க்குகளை மாற்ற ஈதர்நெட் இணைப்பைத் துண்டிக்கவும்"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"பயனரைத் தேர்வுசெய்க"</string> </resources> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 95b8a63fa581..b1639ff0c997 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -1090,6 +1090,8 @@ <string name="controls_media_resume" msgid="1933520684481586053">"కొనసాగించండి"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"సెట్టింగ్లు"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> పాడిన <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> నుండి ప్లే అవుతోంది"</string> + <!-- no translation found for controls_media_seekbar_description (4389621713616214611) --> + <skip /> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"ప్లే చేయండి"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g>ను తెరవండి"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> నుండి <xliff:g id="ARTIST_NAME">%2$s</xliff:g> పాడిన <xliff:g id="SONG_NAME">%1$s</xliff:g>ను ప్లే చేయండి"</string> @@ -1178,4 +1180,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"ప్రస్తుతానికి Wi-Fi ఆటోమేటిక్గా కనెక్ట్ అవ్వదు"</string> <string name="see_all_networks" msgid="3773666844913168122">"అన్నీ చూడండి"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"నెట్వర్క్లను మార్చడానికి, ఈథర్నెట్ను డిస్కనెక్ట్ చేయండి"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"యూజర్ను ఎంచుకోండి"</string> </resources> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 91a24403f4d0..a551b19a2b96 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"เล่นต่อ"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"การตั้งค่า"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"กำลังเปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> ของ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> จาก <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> จาก <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"เล่น"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"เปิด <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> ของ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> จาก <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi จะไม่เชื่อมต่ออัตโนมัติในตอนนี้"</string> <string name="see_all_networks" msgid="3773666844913168122">"ดูทั้งหมด"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"ตัดการเชื่อมต่ออีเทอร์เน็ตเพื่อสลับเครือข่าย"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"เลือกผู้ใช้"</string> </resources> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index dcde0fc67584..b8604abba62f 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Ituloy"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Mga Setting"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Nagpe-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> ni/ng <xliff:g id="ARTIST_NAME">%2$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> sa <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"I-play"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buksan ang <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"I-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> ni/ng <xliff:g id="ARTIST_NAME">%2$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Hindi awtomatikong kokonekta ang Wi-Fi sa ngayon"</string> <string name="see_all_networks" msgid="3773666844913168122">"Tingnan lahat"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Para lumipat ng network, idiskonekta ang ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Pumili ng user"</string> </resources> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 367d0bf3e372..393cbee2f864 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Devam ettir"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Ayarlar"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> uygulamasından <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısı çalıyor"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Oynat"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> uygulamasını aç"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> uygulamasından <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Şu anda kablosuz ağa otomatik olarak bağlanılamıyor"</string> <string name="see_all_networks" msgid="3773666844913168122">"Tümünü göster"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ağ değiştirmek için ethernet bağlantısını kesin"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Kullanıcı seçin"</string> </resources> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index e692a36985b1..0dd178aa5d7b 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -1102,6 +1102,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Відновити"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Налаштування"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Пісня \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", яку виконує <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, грає в додатку <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> з <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Відтворення"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Відкрити додаток <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Увімкнути пісню \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", яку виконує <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, у додатку <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1190,4 +1191,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Пристрій не підключатиметься до Wi-Fi автоматично"</string> <string name="see_all_networks" msgid="3773666844913168122">"Показати все"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Щоб вибрати іншу мережу, від’єднайте кабель Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Виберіть користувача"</string> </resources> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 44b91ad7419e..bb2409da9d29 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"دوبارہ شروع کریں"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"ترتیبات"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> سے <xliff:g id="ARTIST_NAME">%2$s</xliff:g> کا <xliff:g id="SONG_NAME">%1$s</xliff:g> چل رہا ہے"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> از <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"چلائیں"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> کھولیں"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> سے <xliff:g id="ARTIST_NAME">%2$s</xliff:g> کا <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"ابھی Wi-Fi خود کار طور پر منسلک نہیں ہوگا"</string> <string name="see_all_networks" msgid="3773666844913168122">"سبھی دیکھیں"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"نیٹ ورکس پر سوئچ کرنے کیلئے، ایتھرنیٹ غیر منسلک کریں"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"صارف منتخب کریں"</string> </resources> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index f0afb6cb704b..afea9f47cd8a 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Davom etish"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Sozlamalar"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ilovasida ijro etilmoqda: <xliff:g id="SONG_NAME">%1$s</xliff:g> – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> / <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Ijro"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ilovasini ochish"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ilovasida ijro etish: <xliff:g id="SONG_NAME">%1$s</xliff:g> – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi-Fi hozir avtomatik ulanmaydi"</string> <string name="see_all_networks" msgid="3773666844913168122">"Hammasi"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Boshqa tarmoqqa almashish uchun Ethernet tarmogʻini uzing"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Foydalanuvchini tanlang"</string> </resources> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index b0f1dcf31133..15abead8f034 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Tiếp tục"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Cài đặt"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"Đang phát <xliff:g id="SONG_NAME">%1$s</xliff:g> của <xliff:g id="ARTIST_NAME">%2$s</xliff:g> trên <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Phát"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Mở <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Phát <xliff:g id="SONG_NAME">%1$s</xliff:g> của <xliff:g id="ARTIST_NAME">%2$s</xliff:g> trên <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Tạm thời, Wi-Fi sẽ không tự động kết nối"</string> <string name="see_all_networks" msgid="3773666844913168122">"Xem tất cả"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Để chuyển mạng, hãy rút cáp Ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Chọn người dùng"</string> </resources> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 7caae5df46e5..74b1a3996620 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"继续播放"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"设置"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"正在通过<xliff:g id="APP_LABEL">%3$s</xliff:g>播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> / <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"播放"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"打开<xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"通过<xliff:g id="APP_LABEL">%3$s</xliff:g>播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"WLAN 暂时无法自动连接"</string> <string name="see_all_networks" msgid="3773666844913168122">"查看全部"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"如要切换网络,请断开以太网连接"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"选择用户"</string> </resources> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 4a8567d7d29b..28e0ae9d1780 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"繼續播放"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"正在透過 <xliff:g id="APP_LABEL">%3$s</xliff:g> 播放 <xliff:g id="ARTIST_NAME">%2$s</xliff:g> 的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>/<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"播放"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"開啟 <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"在 <xliff:g id="APP_LABEL">%3$s</xliff:g> 播放 <xliff:g id="ARTIST_NAME">%2$s</xliff:g> 的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"目前系統不會自動連線至 Wi-Fi"</string> <string name="see_all_networks" msgid="3773666844913168122">"顯示全部"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"如要切換網絡,請中斷以太網連線"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"選取使用者"</string> </resources> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index b444d4223b7f..1b07cfd2de11 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"繼續播放"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"設定"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"系統正透過「<xliff:g id="APP_LABEL">%3$s</xliff:g>」播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g>,共 <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"播放"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"開啟「<xliff:g id="APP_LABEL">%1$s</xliff:g>」"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"透過「<xliff:g id="APP_LABEL">%3$s</xliff:g>」播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"目前不會自動連上 Wi-Fi"</string> <string name="see_all_networks" msgid="3773666844913168122">"查看全部"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"如要切換網路,請中斷乙太網路連線"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"選取使用者"</string> </resources> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 79884764562a..19e35eff0434 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -1090,6 +1090,7 @@ <string name="controls_media_resume" msgid="1933520684481586053">"Qalisa kabusha"</string> <string name="controls_media_settings_button" msgid="5815790345117172504">"Izilungiselelo"</string> <string name="controls_media_playing_item_description" msgid="4531853311504359098">"I-<xliff:g id="SONG_NAME">%1$s</xliff:g> ka-<xliff:g id="ARTIST_NAME">%2$s</xliff:g> idlala kusuka ku-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> + <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> ku-<xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Dlala"</string> <string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Vula i-<xliff:g id="APP_LABEL">%1$s</xliff:g>"</string> <string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Dlala i-<xliff:g id="SONG_NAME">%1$s</xliff:g> ka-<xliff:g id="ARTIST_NAME">%2$s</xliff:g> kusuka ku-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string> @@ -1178,4 +1179,5 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"I-Wi-Fi ngeke ixhume ngokuzenzakalelayo okwamanje"</string> <string name="see_all_networks" msgid="3773666844913168122">"Bona konke"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Ukuze ushintshe amanethiwekhi, nqamula i-ethernet"</string> + <string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Khetha umsebenzisi"</string> </resources> diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 3121ce37490a..db699242a061 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -77,6 +77,7 @@ <attr name="numColumns" format="integer" /> <attr name="verticalSpacing" format="dimension" /> <attr name="horizontalSpacing" format="dimension" /> + <attr name="fixedChildWidth" format="dimension" /> </declare-styleable> <!-- Theme for icons in the status/nav bar (light/dark). background/fillColor is used for dual diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 9f257466ff28..7293f3148ae4 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1439,10 +1439,10 @@ <!-- Output switcher panel related dimensions --> <dimen name="media_output_dialog_list_margin">12dp</dimen> <dimen name="media_output_dialog_list_max_height">364dp</dimen> - <dimen name="media_output_dialog_header_album_icon_size">52dp</dimen> - <dimen name="media_output_dialog_header_back_icon_size">36dp</dimen> + <dimen name="media_output_dialog_header_album_icon_size">48dp</dimen> + <dimen name="media_output_dialog_header_back_icon_size">32dp</dimen> <dimen name="media_output_dialog_header_icon_padding">16dp</dimen> - <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen> + <dimen name="media_output_dialog_icon_corner_radius">8dp</dimen> <dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen> <!-- Distance that the full shade transition takes in order for qs to fully transition to the @@ -1602,7 +1602,7 @@ <!-- Internet panel related dimensions --> <dimen name="internet_dialog_list_margin">12dp</dimen> - <dimen name="internet_dialog_list_max_height">646dp</dimen> + <dimen name="internet_dialog_list_max_height">662dp</dimen> <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) --> <dimen name="large_dialog_width">@dimen/match_parent</dimen> @@ -1639,4 +1639,9 @@ <item name="communal_source_height_percentage" format="float" type="dimen">0.80</item> <dimen name="drag_and_drop_icon_size">70dp</dimen> + + <dimen name="qs_dialog_button_horizontal_padding">16dp</dimen> + <dimen name="qs_dialog_button_vertical_padding">8dp</dimen> + <!-- The button will be 48dp tall, but the background needs to be 36dp tall --> + <dimen name="qs_dialog_button_vertical_inset">6dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index 9a438df5b7fc..c598097cd5af 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -18,39 +18,12 @@ <resources> <bool name="are_flags_overrideable">false</bool> - <bool name="flag_notification_pipeline2">true</bool> - <bool name="flag_notification_pipeline2_rendering">false</bool> - <bool name="flag_notif_updates">true</bool> - <bool name="flag_monet">true</bool> - <!-- AOD/Lockscreen alternate layout --> - <bool name="flag_keyguard_layout">true</bool> - <!-- People Tile flag --> <bool name="flag_conversations">false</bool> - <!-- The new animations to/from lockscreen and AOD! --> - <bool name="flag_lockscreen_animations">true</bool> - - <!-- The new swipe to unlock animation, which shows the app/launcher behind the keyguard during - the swipe. --> - <bool name="flag_new_unlock_swipe_animation">true</bool> - - <!-- The shared-element transition between lockscreen smartspace and launcher smartspace. --> - <bool name="flag_smartspace_shared_element_transition">false</bool> - - <bool name="flag_pm_lite">true</bool> - <bool name="flag_charging_ripple">false</bool> - <bool name="flag_ongoing_call_status_bar_chip">true</bool> - - <bool name="flag_ongoing_call_in_immersive">false</bool> - <bool name="flag_smartspace">false</bool> - - <bool name="flag_smartspace_deduping">true</bool> - - <bool name="flag_combined_status_bar_signal_icons">false</bool> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c26de37517d4..62737e6b6e61 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2830,6 +2830,8 @@ <string name="controls_media_settings_button">Settings</string> <!-- Description for media control's playing media item, including information for the media's title, the artist, and source app [CHAR LIMIT=NONE]--> <string name="controls_media_playing_item_description"><xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> is playing from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string> + <!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] --> + <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string> <!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] --> <string name="controls_media_smartspace_rec_title">Play</string> @@ -3026,4 +3028,9 @@ <string name="see_all_networks">See all</string> <!-- Summary for warning to disconnect ethernet first then switch to other networks. [CHAR LIMIT=60] --> <string name="to_switch_networks_disconnect_ethernet">To switch networks, disconnect ethernet</string> + <!-- Message to describe "Wi-Fi scan always available feature" when Wi-Fi is off and Wi-Fi scanning is on. [CHAR LIMIT=NONE] --> + <string name="wifi_scan_notify_message">To improve device experience, apps and services can still scan for Wi\u2011Fi networks at any time, even when Wi\u2011Fi is off. You can change this in Wi\u2011Fi scanning settings. <annotation id="link">Change</annotation></string> + + <!-- Title for User Switch dialog. [CHAR LIMIT=20] --> + <string name="qs_user_switch_dialog_title">Select user</string> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 6594216e4290..ff299eae8cf2 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -417,7 +417,9 @@ <item name="android:windowIsFloating">true</item> </style> - <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog" /> + <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"> + <item name="android:buttonCornerRadius">28dp</item> + </style> <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" /> @@ -513,6 +515,10 @@ <item name="android:background">@drawable/btn_borderless_rect</item> </style> + <style name="MediaOutputRoundedOutlinedButton" parent="@android:style/Widget.Material.Button"> + <item name="android:background">@drawable/media_output_dialog_button_background</item> + </style> + <style name="TunerSettings" parent="@android:style/Theme.DeviceDefault.Settings"> <item name="android:windowActionBar">false</item> <item name="preferenceTheme">@style/TunerPreferenceTheme</item> @@ -929,6 +935,27 @@ <item name="actionDividerHeight">32dp</item> </style> + <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog"> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textSize">24sp</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:lineHeight">32sp</item> + </style> + + <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog"> + <item name="android:background">@drawable/qs_dialog_btn_filled</item> + <item name="android:textColor">@color/prv_text_color_on_accent</item> + <item name="android:textSize">14sp</item> + <item name="android:lineHeight">20sp</item> + <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:stateListAnimator">@null</item> + </style> + + <style name="Widget.QSDialog.Button.BorderButton"> + <item name="android:background">@drawable/qs_dialog_btn_outline</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + <style name="Theme.SystemUI.Dialog.Internet"> <item name="android:windowBackground">@drawable/internet_dialog_background</item> </style> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 11557ad87929..5b7e500c4630 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -143,5 +143,12 @@ interface ISystemUiProxy { /** Notifies when taskbar status updated */ oneway void notifyTaskbarStatus(boolean visible, boolean stashed) = 47; - // Next id = 48 + /** + * Notifies sysui when taskbar requests autoHide to stop auto-hiding + * If called to suspend, caller is also responsible for calling this method to un-suspend + * @param suspend should be true to stop auto-hide, false to resume normal behavior + */ + oneway void notifyTaskbarAutohideSuspend(boolean suspend) = 48; + + // Next id = 49 } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 196e6f335b14..d447b485f33b 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -118,6 +118,8 @@ public class QuickStepContract { public static final int SYSUI_STATE_DEVICE_DOZING = 1 << 21; // The home feature is disabled (either by SUW/SysUI/device policy) public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22; + // The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it. + public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23; @Retention(RetentionPolicy.SOURCE) @IntDef({SYSUI_STATE_SCREEN_PINNING, @@ -142,7 +144,8 @@ public class QuickStepContract { SYSUI_STATE_MAGNIFICATION_OVERLAP, SYSUI_STATE_IME_SWITCHER_SHOWING, SYSUI_STATE_DEVICE_DOZING, - SYSUI_STATE_BACK_DISABLED + SYSUI_STATE_BACK_DISABLED, + SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED }) public @interface SystemUiStateFlags {} @@ -174,6 +177,8 @@ public class QuickStepContract { str.add((flags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0 ? "ime_switcher_showing" : ""); str.add((flags & SYSUI_STATE_DEVICE_DOZING) != 0 ? "device_dozing" : ""); str.add((flags & SYSUI_STATE_BACK_DISABLED) != 0 ? "back_disabled" : ""); + str.add((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0 + ? "bubbles_mange_menu_expanded" : ""); return str.toString(); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt index 10e6c2bfea3a..90f5998053b8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt @@ -16,6 +16,7 @@ package com.android.systemui.unfold.progress import android.os.Handler +import android.util.Log import android.util.MathUtils.saturate import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.FloatPropertyCompat @@ -92,7 +93,14 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( } } FOLD_UPDATE_FINISH_FULL_OPEN -> { - cancelTransition(endValue = 1f, animate = true) + // Do not cancel if we haven't started the transition yet. + // This could happen when we fully unfolded the device before the screen + // became available. In this case we start and immediately cancel the animation + // in FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE event handler, so we don't need to + // cancel it here. + if (isTransitionRunning) { + cancelTransition(endValue = 1f, animate = true) + } } FOLD_UPDATE_FINISH_CLOSED -> { cancelTransition(endValue = 0f, animate = false) @@ -101,6 +109,10 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( startTransition(startValue = 1f) } } + + if (DEBUG) { + Log.d(TAG, "onFoldUpdate = $update") + } } private fun cancelTransition(endValue: Float, animate: Boolean) { @@ -118,6 +130,10 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( listeners.forEach { it.onTransitionFinished() } + + if (DEBUG) { + Log.d(TAG, "onTransitionFinished") + } } } @@ -137,6 +153,10 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( it.onTransitionStarted() } isTransitionRunning = true + + if (DEBUG) { + Log.d(TAG, "onTransitionStarted") + } } private fun startTransition(startValue: Float) { @@ -189,6 +209,9 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( } } +private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider" +private const val DEBUG = true + private const val TRANSITION_TIMEOUT_MILLIS = 2000L private const val SPRING_STIFFNESS = 200.0f private const val MINIMAL_VISIBLE_CHANGE = 0.001f diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index c37ab06abb60..35e2b30d0a39 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -25,7 +25,7 @@ import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES import com.android.systemui.unfold.updates.hinge.HingeAngleProvider import java.util.concurrent.Executor -internal class DeviceFoldStateProvider( +class DeviceFoldStateProvider( context: Context, private val hingeAngleProvider: HingeAngleProvider, private val screenStatusProvider: ScreenStatusProvider, @@ -43,6 +43,7 @@ internal class DeviceFoldStateProvider( private val foldStateListener = FoldStateListener(context) private var isFolded = false + private var isUnfoldHandled = true override fun start() { deviceStateManager.registerCallback( @@ -104,6 +105,7 @@ internal class DeviceFoldStateProvider( lastFoldUpdate = FOLD_UPDATE_FINISH_CLOSED outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) } hingeAngleProvider.stop() + isUnfoldHandled = false } else { lastFoldUpdate = FOLD_UPDATE_START_OPENING outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_OPENING) } @@ -115,8 +117,15 @@ internal class DeviceFoldStateProvider( ScreenStatusProvider.ScreenListener { override fun onScreenTurnedOn() { - if (!isFolded) { + // Trigger this event only if we are unfolded and this is the first screen + // turned on event since unfold started. This prevents running the animation when + // turning on the internal display using the power button. + // Initially isUnfoldHandled is true so it will be reset to false *only* when we + // receive 'folded' event. If SystemUI started when device is already folded it will + // still receive 'folded' event on startup. + if (!isFolded && !isUnfoldHandled) { outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) } + isUnfoldHandled = true } } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt index 11984b93638e..643ece353522 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt @@ -24,7 +24,7 @@ import com.android.systemui.statusbar.policy.CallbackController * Allows to subscribe to main events related to fold/unfold process such as hinge angle update, * start folding/unfolding, screen availability */ -internal interface FoldStateProvider : CallbackController<FoldUpdatesListener> { +interface FoldStateProvider : CallbackController<FoldUpdatesListener> { fun start() fun stop() diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt index 2520d356b721..6f524560de99 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt @@ -9,7 +9,7 @@ import com.android.systemui.statusbar.policy.CallbackController * For foldable devices usually 0 corresponds to fully closed (folded) state and * 180 degrees corresponds to fully open (flat) state */ -internal interface HingeAngleProvider : CallbackController<Consumer<Float>> { +interface HingeAngleProvider : CallbackController<Consumer<Float>> { fun start() fun stop() } diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java new file mode 100644 index 000000000000..5b6845fcdb4f --- /dev/null +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java @@ -0,0 +1,189 @@ +/* + * 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.flags; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.util.Log; + +import com.android.systemui.dagger.SysUISingleton; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +import javax.inject.Inject; + +/** + * Concrete implementation of the a Flag manager that returns default values for debug builds + * + * Flags can be set (or unset) via the following adb command: + * + * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>] + * + * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command. + */ +@SysUISingleton +public class FeatureFlagManager implements FlagReader, FlagWriter { + private static final String TAG = "SysUIFlags"; + + private static final String SYSPROP_PREFIX = "persist.systemui.flag_"; + private static final String FIELD_TYPE = "type"; + private static final String FIELD_ID = "id"; + private static final String FIELD_VALUE = "value"; + private static final String TYPE_BOOLEAN = "boolean"; + private static final String ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG"; + private static final String FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"; + private final SystemPropertiesHelper mSystemPropertiesHelper; + + private final Map<Integer, Boolean> mBooleanFlagCache = new HashMap<>(); + + @Inject + public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context) { + mSystemPropertiesHelper = systemPropertiesHelper; + + IntentFilter filter = new IntentFilter(ACTION_SET_FLAG); + context.registerReceiver(mReceiver, filter, FLAGS_PERMISSION, null); + } + + /** Return a {@link BooleanFlag}'s value. */ + public boolean isEnabled(int id, boolean defaultValue) { + if (!mBooleanFlagCache.containsKey(id)) { + Boolean result = isEnabledInternal(id); + mBooleanFlagCache.put(id, result == null ? defaultValue : result); + } + + return mBooleanFlagCache.get(id); + } + + /** Returns the stored value or null if not set. */ + private Boolean isEnabledInternal(int id) { + String data = mSystemPropertiesHelper.get(keyToSysPropKey(id)); + if (data.isEmpty()) { + return null; + } + JSONObject json; + try { + json = new JSONObject(data); + if (!assertType(json, TYPE_BOOLEAN)) { + return null; + } + + return json.getBoolean(FIELD_VALUE); + } catch (JSONException e) { + eraseInternal(id); // Don't restart SystemUI in this case. + } + return null; + } + + /** Set whether a given {@link BooleanFlag} is enabled or not. */ + public void setEnabled(int id, boolean value) { + Boolean currentValue = isEnabledInternal(id); + if (currentValue != null && currentValue == value) { + return; + } + + JSONObject json = new JSONObject(); + try { + json.put(FIELD_TYPE, TYPE_BOOLEAN); + json.put(FIELD_VALUE, value); + mSystemPropertiesHelper.set(keyToSysPropKey(id), json.toString()); + Log.i(TAG, "Set id " + id + " to " + value); + restartSystemUI(); + } catch (JSONException e) { + // no-op + } + } + + /** Erase a flag's overridden value if there is one. */ + public void eraseFlag(int id) { + eraseInternal(id); + restartSystemUI(); + } + + /** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */ + private void eraseInternal(int id) { + // We can't actually "erase" things from sysprops, but we can set them to empty! + mSystemPropertiesHelper.set(keyToSysPropKey(id), ""); + Log.i(TAG, "Erase id " + id); + } + + public void addListener(Listener run) {} + + public void removeListener(Listener run) {} + + private void restartSystemUI() { + Log.i(TAG, "Restarting SystemUI"); + // SysUI starts back when up exited. Is there a better way to do this? + System.exit(0); + } + + private static String keyToSysPropKey(int key) { + return SYSPROP_PREFIX + key; + } + + private static boolean assertType(JSONObject json, String type) { + try { + return json.getString(FIELD_TYPE).equals(TYPE_BOOLEAN); + } catch (JSONException e) { + return false; + } + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + return; + } + + if (ACTION_SET_FLAG.equals(action)) { + handleSetFlag(intent.getExtras()); + } + } + + private void handleSetFlag(Bundle extras) { + int id = extras.getInt(FIELD_ID); + if (id <= 0) { + Log.w(TAG, "ID not set or less than or equal to 0: " + id); + return; + } + + Map<Integer, Flag<?>> flagMap = Flags.collectFlags(); + if (!flagMap.containsKey(id)) { + Log.w(TAG, "Tried to set unknown id: " + id); + return; + } + Flag<?> flag = flagMap.get(id); + + if (!extras.containsKey(FIELD_VALUE)) { + eraseFlag(id); + return; + } + + if (flag instanceof BooleanFlag) { + setEnabled(id, extras.getBoolean(FIELD_VALUE)); + } + } + }; +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index f81f0b9ca2c3..6fd0c821bad1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -41,7 +41,7 @@ public class KeyguardClockSwitch extends RelativeLayout { private static final long CLOCK_OUT_MILLIS = 150; private static final long CLOCK_IN_MILLIS = 200; - private static final long SMARTSPACE_MOVE_MILLIS = 350; + private static final long STATUS_AREA_MOVE_MILLIS = 350; @IntDef({LARGE, SMALL}) @Retention(RetentionPolicy.SOURCE) @@ -63,13 +63,7 @@ public class KeyguardClockSwitch extends RelativeLayout { private AnimatableClockView mClockView; private AnimatableClockView mLargeClockView; - /** - * Status area (date and other stuff) shown below the clock. Plugin can decide whether or not to - * show it below the alternate clock. - */ - private View mKeyguardStatusArea; - /** Mutually exclusive with mKeyguardStatusArea */ - private View mSmartspaceView; + private View mStatusArea; private int mSmartspaceTopOffset; /** @@ -85,7 +79,7 @@ public class KeyguardClockSwitch extends RelativeLayout { @VisibleForTesting AnimatorSet mClockInAnim = null; @VisibleForTesting AnimatorSet mClockOutAnim = null; - private ObjectAnimator mSmartspaceAnim = null; + private ObjectAnimator mStatusAreaAnim = null; /** * If the Keyguard Slice has a header (big center-aligned text.) @@ -131,7 +125,7 @@ public class KeyguardClockSwitch extends RelativeLayout { mClockView = findViewById(R.id.animatable_clock_view); mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large); mLargeClockView = findViewById(R.id.animatable_clock_view_large); - mKeyguardStatusArea = findViewById(R.id.keyguard_status_area); + mStatusArea = findViewById(R.id.keyguard_status_area); onDensityOrFontScaleChanged(); } @@ -200,22 +194,22 @@ public class KeyguardClockSwitch extends RelativeLayout { private void animateClockChange(boolean useLargeClock) { if (mClockInAnim != null) mClockInAnim.cancel(); if (mClockOutAnim != null) mClockOutAnim.cancel(); - if (mSmartspaceAnim != null) mSmartspaceAnim.cancel(); + if (mStatusAreaAnim != null) mStatusAreaAnim.cancel(); View in, out; int direction = 1; - float smartspaceYTranslation; + float statusAreaYTranslation; if (useLargeClock) { out = mClockFrame; in = mLargeClockFrame; if (indexOfChild(in) == -1) addView(in); direction = -1; - smartspaceYTranslation = mSmartspaceView == null ? 0 - : mClockFrame.getTop() - mSmartspaceView.getTop() + mSmartspaceTopOffset; + statusAreaYTranslation = mClockFrame.getTop() - mStatusArea.getTop() + + mSmartspaceTopOffset; } else { in = mClockFrame; out = mLargeClockFrame; - smartspaceYTranslation = 0f; + statusAreaYTranslation = 0f; // Must remove in order for notifications to appear in the proper place removeView(out); @@ -251,18 +245,16 @@ public class KeyguardClockSwitch extends RelativeLayout { mClockInAnim.start(); mClockOutAnim.start(); - if (mSmartspaceView != null) { - mSmartspaceAnim = ObjectAnimator.ofFloat(mSmartspaceView, View.TRANSLATION_Y, - smartspaceYTranslation); - mSmartspaceAnim.setDuration(SMARTSPACE_MOVE_MILLIS); - mSmartspaceAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - mSmartspaceAnim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - mSmartspaceAnim = null; - } - }); - mSmartspaceAnim.start(); - } + mStatusAreaAnim = ObjectAnimator.ofFloat(mStatusArea, View.TRANSLATION_Y, + statusAreaYTranslation); + mStatusAreaAnim.setDuration(STATUS_AREA_MOVE_MILLIS); + mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + mStatusAreaAnim.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + mStatusAreaAnim = null; + } + }); + mStatusAreaAnim.start(); } /** @@ -352,10 +344,6 @@ public class KeyguardClockSwitch extends RelativeLayout { } } - void setSmartspaceView(View smartspaceView) { - mSmartspaceView = smartspaceView; - } - void updateColors(ColorExtractor.GradientColors colors) { mSupportsDarkText = colors.supportsDarkText(); mColorPalette = colors.getColorPalette(); @@ -369,8 +357,7 @@ public class KeyguardClockSwitch extends RelativeLayout { pw.println(" mClockPlugin: " + mClockPlugin); pw.println(" mClockFrame: " + mClockFrame); pw.println(" mLargeClockFrame: " + mLargeClockFrame); - pw.println(" mKeyguardStatusArea: " + mKeyguardStatusArea); - pw.println(" mSmartspaceView: " + mSmartspaceView); + pw.println(" mStatusArea: " + mStatusArea); pw.println(" mDarkAmount: " + mDarkAmount); pw.println(" mSupportsDarkText: " + mSupportsDarkText); pw.println(" mColorPalette: " + Arrays.toString(mColorPalette)); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 4111020afbf5..01976b41a4b2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -25,7 +25,9 @@ import android.app.WallpaperManager; import android.content.res.Resources; import android.text.TextUtils; import android.view.View; +import android.view.ViewGroup; import android.widget.FrameLayout; +import android.widget.LinearLayout; import android.widget.RelativeLayout; import com.android.internal.colorextraction.ColorExtractor; @@ -99,7 +101,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin; - // If set, will replace keyguard_status_area + private ViewGroup mStatusArea; + // If set will replace keyguard_slice_view private View mSmartspaceView; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @@ -191,8 +194,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); if (mOnlyClock) { - View ksa = mView.findViewById(R.id.keyguard_status_area); - ksa.setVisibility(View.GONE); + View ksv = mView.findViewById(R.id.keyguard_slice_view); + ksv.setVisibility(View.GONE); View nic = mView.findViewById( R.id.left_aligned_notification_icon_container); @@ -201,19 +204,18 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } updateAodIcons(); + mStatusArea = mView.findViewById(R.id.keyguard_status_area); + if (mSmartspaceController.isEnabled()) { mSmartspaceView = mSmartspaceController.buildAndConnectView(mView); + View ksv = mView.findViewById(R.id.keyguard_slice_view); + int ksvIndex = mStatusArea.indexOfChild(ksv); + ksv.setVisibility(View.GONE); - View ksa = mView.findViewById(R.id.keyguard_status_area); - int ksaIndex = mView.indexOfChild(ksa); - ksa.setVisibility(View.GONE); - - // Place smartspace view below normal clock... - RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( MATCH_PARENT, WRAP_CONTENT); - lp.addRule(RelativeLayout.BELOW, R.id.lockscreen_clock_view); - mView.addView(mSmartspaceView, ksaIndex, lp); + mStatusArea.addView(mSmartspaceView, ksvIndex, lp); int startPadding = getContext().getResources() .getDimensionPixelSize(R.dimen.below_clock_padding_start); int endPadding = getContext().getResources() @@ -221,14 +223,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0); updateClockLayout(); - - View nic = mView.findViewById( - R.id.left_aligned_notification_icon_container); - lp = (RelativeLayout.LayoutParams) nic.getLayoutParams(); - lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId()); - nic.setLayoutParams(lp); - - mView.setSmartspaceView(mSmartspaceView); mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceView); } } @@ -244,8 +238,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } mColorExtractor.removeOnColorsChangedListener(mColorsListener); mView.setClockPlugin(null, mStatusBarStateController.getState()); - - mSmartspaceController.disconnect(); } /** @@ -328,8 +320,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_Y, scale, props, animate); - if (mSmartspaceView != null) { - PropertyAnimator.setProperty(mSmartspaceView, AnimatableProperty.TRANSLATION_X, + if (mStatusArea != null) { + PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X, x, props, animate); // If we're unlocking with the SmartSpace shared element transition, let the controller @@ -340,7 +332,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } mKeyguardSliceViewController.updatePosition(x, props, animate); - mNotificationIconAreaController.updatePosition(x, props, animate); } /** Sets an alpha value on every child view except for the smartspace. */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 9d649e78c363..d4d3d5b3ea2d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -209,7 +209,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @Override - public void onOverlayChanged() { + public void onThemeChanged() { mSecurityViewFlipperController.reloadColors(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 428006ebf446..9b76bab5c2a7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -33,12 +33,10 @@ import android.os.Trace; import android.text.TextUtils; import android.text.TextUtils.TruncateAt; import android.util.AttributeSet; -import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.view.animation.Animation; import android.widget.LinearLayout; -import android.widget.RelativeLayout; import android.widget.TextView; import androidx.slice.SliceItem; @@ -85,8 +83,6 @@ public class KeyguardSliceView extends LinearLayout { private boolean mHasHeader; private View.OnClickListener mOnClickListener; - private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; - public KeyguardSliceView(Context context, AttributeSet attrs) { super(context, attrs); @@ -136,35 +132,6 @@ public class KeyguardSliceView extends LinearLayout { } } - /** - * Updates the lockscreen mode which may change the layout of the keyguard slice view. - */ - public void updateLockScreenMode(int mode) { - mLockScreenMode = mode; - if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) { - mTitle.setPaddingRelative(0, 0, 0, 0); - mTitle.setGravity(Gravity.START); - setGravity(Gravity.START); - RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams(); - lp.removeRule(RelativeLayout.CENTER_HORIZONTAL); - setLayoutParams(lp); - } else { - final int horizontalPaddingDpValue = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - 44, - getResources().getDisplayMetrics() - ); - mTitle.setPaddingRelative(horizontalPaddingDpValue, 0, horizontalPaddingDpValue, 0); - mTitle.setGravity(Gravity.CENTER_HORIZONTAL); - setGravity(Gravity.CENTER_HORIZONTAL); - RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams(); - lp.addRule(RelativeLayout.CENTER_HORIZONTAL); - setLayoutParams(lp); - } - mRow.setLockscreenMode(mode); - requestLayout(); - } - Map<View, PendingIntent> showSlice(RowContent header, List<SliceContent> subItems) { Trace.beginSection("KeyguardSliceView#showSlice"); mHasHeader = header != null; @@ -189,8 +156,7 @@ public class KeyguardSliceView extends LinearLayout { final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE); LinearLayout.LayoutParams layoutParams = (LayoutParams) mRow.getLayoutParams(); - layoutParams.gravity = mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL - ? Gravity.START : Gravity.CENTER; + layoutParams.gravity = Gravity.START; mRow.setLayoutParams(layoutParams); for (int i = startIndex; i < subItemsCount; i++) { @@ -224,8 +190,7 @@ public class KeyguardSliceView extends LinearLayout { final int iconSize = mHasHeader ? mIconSizeWithHeader : mIconSize; iconDrawable = icon.getIcon().loadDrawable(mContext); if (iconDrawable != null) { - if ((iconDrawable instanceof InsetDrawable) - && mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) { + if (iconDrawable instanceof InsetDrawable) { // System icons (DnD) use insets which are fine for centered slice content // but will cause a slight indent for left/right-aligned slice views iconDrawable = ((InsetDrawable) iconDrawable).getDrawable(); @@ -321,7 +286,6 @@ public class KeyguardSliceView extends LinearLayout { pw.println(" mTextColor: " + Integer.toHexString(mTextColor)); pw.println(" mDarkAmount: " + mDarkAmount); pw.println(" mHasHeader: " + mHasHeader); - pw.println(" mLockScreenMode: " + mLockScreenMode); } @Override @@ -332,7 +296,6 @@ public class KeyguardSliceView extends LinearLayout { public static class Row extends LinearLayout { private Set<KeyguardSliceTextView> mKeyguardSliceTextViewSet = new HashSet(); - private int mLockScreenModeRow = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; /** * This view is visible in AOD, which means that the device will sleep if we @@ -407,11 +370,7 @@ public class KeyguardSliceView extends LinearLayout { for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (child instanceof KeyguardSliceTextView) { - if (mLockScreenModeRow == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) { - ((KeyguardSliceTextView) child).setMaxWidth(Integer.MAX_VALUE); - } else { - ((KeyguardSliceTextView) child).setMaxWidth(width / 3); - } + ((KeyguardSliceTextView) child).setMaxWidth(Integer.MAX_VALUE); } } @@ -443,7 +402,6 @@ public class KeyguardSliceView extends LinearLayout { super.addView(view, index); if (view instanceof KeyguardSliceTextView) { - ((KeyguardSliceTextView) view).setLockScreenMode(mLockScreenModeRow); mKeyguardSliceTextViewSet.add((KeyguardSliceTextView) view); } } @@ -455,24 +413,6 @@ public class KeyguardSliceView extends LinearLayout { mKeyguardSliceTextViewSet.remove((KeyguardSliceTextView) view); } } - - /** - * Updates the lockscreen mode which may change the layout of this view. - */ - public void setLockscreenMode(int mode) { - mLockScreenModeRow = mode; - if (mLockScreenModeRow == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) { - setOrientation(LinearLayout.VERTICAL); - setGravity(Gravity.START); - } else { - setOrientation(LinearLayout.HORIZONTAL); - setGravity(Gravity.CENTER); - } - - for (KeyguardSliceTextView textView : mKeyguardSliceTextViewSet) { - textView.setLockScreenMode(mLockScreenModeRow); - } - } } /** @@ -480,7 +420,6 @@ public class KeyguardSliceView extends LinearLayout { */ @VisibleForTesting static class KeyguardSliceTextView extends TextView { - private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; @StyleRes private static int sStyleId = R.style.TextAppearance_Keyguard_Secondary; @@ -509,13 +448,8 @@ public class KeyguardSliceView extends LinearLayout { boolean hasText = !TextUtils.isEmpty(getText()); int padding = (int) getContext().getResources() .getDimension(R.dimen.widget_horizontal_padding) / 2; - if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) { - // orientation is vertical, so add padding to top & bottom - setPadding(0, padding, 0, hasText ? padding : 0); - } else { - // orientation is horizontal, so add padding to left & right - setPadding(padding, 0, padding * (hasText ? 1 : -1), 0); - } + // orientation is vertical, so add padding to top & bottom + setPadding(0, padding, 0, hasText ? padding : 0); setCompoundDrawablePadding((int) mContext.getResources() .getDimension(R.dimen.widget_icon_padding)); @@ -543,18 +477,5 @@ public class KeyguardSliceView extends LinearLayout { } } } - - /** - * Updates the lockscreen mode which may change the layout of this view. - */ - public void setLockScreenMode(int mode) { - mLockScreenMode = mode; - if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) { - setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START); - } else { - setTextAlignment(View.TEXT_ALIGNMENT_CENTER); - } - updatePadding(); - } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java index 8038ce4c7b69..d05cc4ea8101 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java @@ -73,7 +73,6 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie private Uri mKeyguardSliceUri; private Slice mSlice; private Map<View, PendingIntent> mClickActions; - private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL; TunerService.Tunable mTunable = (key, newValue) -> setupUri(newValue); @@ -84,7 +83,7 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie mView.onDensityOrFontScaleChanged(); } @Override - public void onOverlayChanged() { + public void onThemeChanged() { mView.onOverlayChanged(); } }; @@ -137,7 +136,6 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie TAG + "@" + Integer.toHexString( KeyguardSliceViewController.this.hashCode()), KeyguardSliceViewController.this); - mView.updateLockScreenMode(mLockScreenMode); } @Override @@ -160,14 +158,6 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie } /** - * Updates the lockscreen mode which may change the layout of the keyguard slice view. - */ - public void updateLockScreenMode(int mode) { - mLockScreenMode = mode; - mView.updateLockScreenMode(mLockScreenMode); - } - - /** * Sets the slice provider Uri. */ public void setupUri(String uriString) { @@ -249,6 +239,5 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.println(" mSlice: " + mSlice); pw.println(" mClickActions: " + mClickActions); - pw.println(" mLockScreenMode: " + mLockScreenMode); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index 2362a1ad74a0..a72a050ee023 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -88,7 +88,7 @@ public class KeyguardStatusView extends GridLayout { mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext)); } - mKeyguardSlice = findViewById(R.id.keyguard_status_area); + mKeyguardSlice = findViewById(R.id.keyguard_slice_view); mTextColor = mClockView.getCurrentTextColor(); mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index d23b1c88b345..c58710ceec65 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -249,11 +249,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { @Override - public void onLockScreenModeChanged(int mode) { - mKeyguardSliceViewController.updateLockScreenMode(mode); - } - - @Override public void onTimeChanged() { refreshTime(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 5969e9290c9c..a41a49799c2d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -71,7 +71,6 @@ import android.os.ServiceManager; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; -import android.os.Vibrator; import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; @@ -86,11 +85,11 @@ import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; -import androidx.annotation.Nullable; import androidx.lifecycle.Observer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.settingslib.WirelessUtils; import com.android.settingslib.fuelgauge.BatteryStatus; @@ -98,7 +97,6 @@ import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.biometrics.AuthController; -import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -191,9 +189,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_TIME_FORMAT_UPDATE = 344; private static final int MSG_REQUIRE_NFC_UNLOCK = 345; - public static final int LOCK_SCREEN_MODE_NORMAL = 0; - public static final int LOCK_SCREEN_MODE_LAYOUT_1 = 1; - /** Biometric authentication state: Not listening. */ private static final int BIOMETRIC_STATE_STOPPED = 0; @@ -277,7 +272,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mBouncer; // true if bouncerIsOrWillBeShowing private boolean mAuthInterruptActive; private boolean mNeedsSlowUnlockTransition; - private boolean mHasLockscreenWallpaper; private boolean mAssistantVisible; private boolean mKeyguardOccluded; private boolean mOccludingAppRequestingFp; @@ -286,9 +280,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting protected boolean mTelephonyCapable; - private final boolean mAcquiredHapticEnabled = false; - @Nullable private final Vibrator mVibrator; - // Device provisioning state private boolean mDeviceProvisioned; @@ -323,6 +314,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final DevicePolicyManager mDevicePolicyManager; private final BroadcastDispatcher mBroadcastDispatcher; private final InteractionJankMonitor mInteractionJankMonitor; + private final LatencyTracker mLatencyTracker; private boolean mLogoutEnabled; // cached value to avoid IPCs private boolean mIsUdfpsEnrolled; @@ -1407,7 +1399,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting final FingerprintManager.AuthenticationCallback mFingerprintAuthenticationCallback = new AuthenticationCallback() { - private boolean mPlayedAcquiredHaptic; @Override public void onAuthenticationFailed() { @@ -1419,11 +1410,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded"); handleFingerprintAuthenticated(result.getUserId(), result.isStrongBiometric()); Trace.endSection(); - - // on auth success, we sometimes never received an acquired haptic - if (!mPlayedAcquiredHaptic && isUdfpsEnrolled()) { - playAcquiredHaptic(); - } } @Override @@ -1439,17 +1425,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationAcquired(int acquireInfo) { handleFingerprintAcquired(acquireInfo); - if (acquireInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD - && isUdfpsEnrolled()) { - mPlayedAcquiredHaptic = true; - playAcquiredHaptic(); - } } @Override public void onUdfpsPointerDown(int sensorId) { Log.d(TAG, "onUdfpsPointerDown, sensorId: " + sensorId); - mPlayedAcquiredHaptic = false; } @Override @@ -1458,17 +1438,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } }; - /** - * Play haptic to signal udfps fingeprrint acquired. - */ - @VisibleForTesting - public void playAcquiredHaptic() { - if (mAcquiredHapticEnabled && mVibrator != null) { - mVibrator.vibrate(UdfpsController.EFFECT_CLICK, - UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES); - } - } - private final FaceManager.FaceDetectionCallback mFaceDetectionCallback = (sensorId, userId, isStrongBiometric) -> { // Trigger the face success path so the bouncer can be shown @@ -1773,7 +1742,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab TelephonyListenerManager telephonyListenerManager, FeatureFlags featureFlags, InteractionJankMonitor interactionJankMonitor, - @Nullable Vibrator vibrator) { + LatencyTracker latencyTracker) { mContext = context; mSubscriptionManager = SubscriptionManager.from(context); mTelephonyListenerManager = telephonyListenerManager; @@ -1782,6 +1751,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mBackgroundExecutor = backgroundExecutor; mBroadcastDispatcher = broadcastDispatcher; mInteractionJankMonitor = interactionJankMonitor; + mLatencyTracker = latencyTracker; mRingerModeTracker = ringerModeTracker; mStatusBarStateController = statusBarStateController; mStatusBarStateController.addCallback(mStatusBarStateControllerListener); @@ -1789,7 +1759,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLockPatternUtils = lockPatternUtils; mAuthController = authController; dumpManager.registerDumpable(getClass().getName(), this); - mVibrator = vibrator; mHandler = new Handler(mainLooper) { @Override @@ -1898,9 +1867,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab case MSG_KEYGUARD_GOING_AWAY: handleKeyguardGoingAway((boolean) msg.obj); break; - case MSG_LOCK_SCREEN_MODE: - handleLockScreenMode(); - break; case MSG_TIME_FORMAT_UPDATE: handleTimeFormatUpdate((String) msg.obj); break; @@ -2042,8 +2008,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - updateLockScreenMode(featureFlags.isKeyguardLayoutEnabled()); - mTimeFormatChangeObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { @@ -2059,14 +2023,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab false, mTimeFormatChangeObserver, UserHandle.USER_ALL); } - private void updateLockScreenMode(boolean isEnabled) { - final int newMode = isEnabled ? LOCK_SCREEN_MODE_LAYOUT_1 : LOCK_SCREEN_MODE_NORMAL; - if (newMode != mLockScreenMode) { - mLockScreenMode = newMode; - mHandler.sendEmptyMessage(MSG_LOCK_SCREEN_MODE); - } - } - private void updateUdfpsEnrolled(int userId) { mIsUdfpsEnrolled = mAuthController.isUdfpsEnrolled(userId); } @@ -2579,31 +2535,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** - * Update the state whether Keyguard currently has a lockscreen wallpaper. - * - * @param hasLockscreenWallpaper Whether Keyguard has a lockscreen wallpaper. - */ - public void setHasLockscreenWallpaper(boolean hasLockscreenWallpaper) { - Assert.isMainThread(); - if (hasLockscreenWallpaper != mHasLockscreenWallpaper) { - mHasLockscreenWallpaper = hasLockscreenWallpaper; - for (int i = 0; i < mCallbacks.size(); i++) { - KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); - if (cb != null) { - cb.onHasLockscreenWallpaperChanged(hasLockscreenWallpaper); - } - } - } - } - - /** - * @return Whether Keyguard has a lockscreen wallpaper. - */ - public boolean hasLockscreenWallpaper() { - return mHasLockscreenWallpaper; - } - - /** * Handle {@link #MSG_DPM_STATE_CHANGED} */ private void handleDevicePolicyManagerStateChanged(int userId) { @@ -2651,6 +2582,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } mInteractionJankMonitor.end(InteractionJankMonitor.CUJ_USER_SWITCH); + mLatencyTracker.onActionEnd(LatencyTracker.ACTION_USER_SWITCH); } /** @@ -2722,20 +2654,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** - * Handle {@link #MSG_LOCK_SCREEN_MODE} - */ - private void handleLockScreenMode() { - Assert.isMainThread(); - if (DEBUG) Log.d(TAG, "handleLockScreenMode(" + mLockScreenMode + ")"); - for (int i = 0; i < mCallbacks.size(); i++) { - KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); - if (cb != null) { - cb.onLockScreenModeChanged(mLockScreenMode); - } - } - } - - /** * Handle (@line #MSG_TIMEZONE_UPDATE} */ private void handleTimeZoneUpdate(String timeZone) { @@ -3113,7 +3031,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab callback.onKeyguardOccludedChanged(mKeyguardOccluded); callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible); callback.onTelephonyCapable(mTelephonyCapable); - callback.onLockScreenModeChanged(mLockScreenMode); for (Entry<Integer, SimData> data : mSimDatas.entrySet()) { final SimData state = data.getValue(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 6aa7aaa4d488..12431984c9b9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -292,11 +292,6 @@ public class KeyguardUpdateMonitorCallback { public void onStrongAuthStateChanged(int userId) { } /** - * Called when the state whether we have a lockscreen wallpaper has changed. - */ - public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { } - - /** * Called when the dream's window state is changed. * @param dreaming true if the dream's window has been created and is visible */ @@ -330,11 +325,6 @@ public class KeyguardUpdateMonitorCallback { public void onSecondaryLockscreenRequirementChanged(int userId) { } /** - * Called to switch lock screen layout/clock layouts - */ - public void onLockScreenModeChanged(int mode) { } - - /** * Called when notifying user to unlock in order to use NFC. */ public void onRequireUnlockForNfc() { } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index ef4353b93179..68132f4c598b 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -26,6 +26,7 @@ import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; +import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -40,6 +41,17 @@ import java.io.PrintWriter; * A view positioned under the notification shade. */ public class LockIconView extends FrameLayout implements Dumpable { + @IntDef({ICON_NONE, ICON_LOCK, ICON_FINGERPRINT, ICON_UNLOCK}) + public @interface IconType {} + + public static final int ICON_NONE = -1; + public static final int ICON_LOCK = 0; + public static final int ICON_FINGERPRINT = 1; + public static final int ICON_UNLOCK = 2; + + private @IconType int mIconType; + private boolean mAod; + @NonNull private final RectF mSensorRect; @NonNull private PointF mLockIconCenter = new PointF(0f, 0f); private int mRadius; @@ -49,6 +61,7 @@ public class LockIconView extends FrameLayout implements Dumpable { private int mLockIconColor; private boolean mUseBackground = false; + private float mDozeAmount = 0f; public LockIconView(Context context, AttributeSet attrs) { super(context, attrs); @@ -62,11 +75,17 @@ public class LockIconView extends FrameLayout implements Dumpable { mBgView = findViewById(R.id.lock_icon_bg); } + void setDozeAmount(float dozeAmount) { + mDozeAmount = dozeAmount; + updateColorAndBackgroundVisibility(); + } + void updateColorAndBackgroundVisibility() { if (mUseBackground && mLockIcon.getDrawable() != null) { mLockIconColor = Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorPrimary); mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg)); + mBgView.setAlpha(1f - mDozeAmount); mBgView.setVisibility(View.VISIBLE); } else { mLockIconColor = Utils.getColorAttrDefaultColor(getContext(), @@ -129,10 +148,75 @@ public class LockIconView extends FrameLayout implements Dumpable { return mLockIconCenter.y - mRadius; } + /** + * Updates the icon its default state where no visual is shown. + */ + public void clearIcon() { + updateIcon(ICON_NONE, false); + } + + /** + * Transition the current icon to a new state + * @param icon type (ie: lock icon, unlock icon, fingerprint icon) + * @param aod whether to use the aod icon variant (some icons don't have aod variants and will + * therefore show no icon) + */ + public void updateIcon(@IconType int icon, boolean aod) { + mIconType = icon; + mAod = aod; + + mLockIcon.setImageState(getLockIconState(mIconType, mAod), true); + } + + private static int[] getLockIconState(@IconType int icon, boolean aod) { + if (icon == ICON_NONE) { + return new int[0]; + } + + int[] lockIconState = new int[2]; + switch (icon) { + case ICON_LOCK: + lockIconState[0] = android.R.attr.state_first; + break; + case ICON_FINGERPRINT: + lockIconState[0] = android.R.attr.state_middle; + break; + case ICON_UNLOCK: + lockIconState[0] = android.R.attr.state_last; + break; + } + + if (aod) { + lockIconState[1] = android.R.attr.state_single; + } else { + lockIconState[1] = -android.R.attr.state_single; + } + + return lockIconState; + } + + private String typeToString(@IconType int type) { + switch (type) { + case ICON_NONE: + return "none"; + case ICON_LOCK: + return "lock"; + case ICON_FINGERPRINT: + return "fingerprint"; + case ICON_UNLOCK: + return "unlock"; + } + + return "invalid"; + } + @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.println("Center in px (x, y)= (" + mLockIconCenter.x + ", " + mLockIconCenter.y + ")"); pw.println("Radius in pixels: " + mRadius); pw.println("topLeft= (" + getX() + ", " + getY() + ")"); + pw.println("topLeft= (" + getX() + ", " + getY() + ")"); + pw.println("mIconType=" + typeToString(mIconType)); + pw.println("mAod=" + mAod); } } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 8cfd225fc4df..94b1728b34ae 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -18,16 +18,18 @@ package com.android.keyguard; import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; +import static com.android.keyguard.LockIconView.ICON_FINGERPRINT; +import static com.android.keyguard.LockIconView.ICON_LOCK; +import static com.android.keyguard.LockIconView.ICON_UNLOCK; 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; +import android.content.res.Resources; import android.graphics.PointF; import android.graphics.Rect; -import android.graphics.drawable.AnimatedVectorDrawable; -import android.graphics.drawable.Drawable; +import android.graphics.drawable.AnimatedStateListDrawable; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.SensorLocationInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -38,6 +40,7 @@ import android.util.DisplayMetrics; import android.util.MathUtils; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityManager; @@ -98,14 +101,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull private final AccessibilityManager mAccessibilityManager; @NonNull private final ConfigurationController mConfigurationController; @NonNull private final DelayableExecutor mExecutor; + @NonNull private final LayoutInflater mLayoutInflater; private boolean mUdfpsEnrolled; - @NonNull private LottieAnimationView mAodFp; + @Nullable private LottieAnimationView mAodFp; + @NonNull private final AnimatedStateListDrawable mIcon; - @NonNull private final AnimatedVectorDrawable mFpToUnlockIcon; - @NonNull private final AnimatedVectorDrawable mLockToUnlockIcon; - @NonNull private final Drawable mLockIcon; - @NonNull private final Drawable mUnlockIcon; @NonNull private CharSequence mUnlockedLabel; @NonNull private CharSequence mLockedLabel; @Nullable private final Vibrator mVibrator; @@ -131,13 +132,13 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private boolean mShowLockIcon; // for udfps when strong auth is required or unlocked on AOD + private boolean mShowAodLockIcon; 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(); @Inject @@ -154,7 +155,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull ConfigurationController configurationController, @NonNull @Main DelayableExecutor executor, @Nullable Vibrator vibrator, - @Nullable AuthRippleController authRippleController + @Nullable AuthRippleController authRippleController, + @NonNull @Main Resources resources, + @NonNull LayoutInflater inflater ) { super(view); mStatusBarStateController = statusBarStateController; @@ -168,27 +171,16 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mExecutor = executor; mVibrator = vibrator; mAuthRippleController = authRippleController; + mLayoutInflater = inflater; - 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()); - mLockIcon = mView.getContext().getResources().getDrawable( - R.anim.lock_to_unlock, - mView.getContext().getTheme()); - mFpToUnlockIcon = (AnimatedVectorDrawable) mView.getContext().getResources().getDrawable( - R.anim.fp_to_unlock, mView.getContext().getTheme()); - mLockToUnlockIcon = (AnimatedVectorDrawable) mView.getContext().getResources().getDrawable( - R.anim.lock_to_unlock, - mView.getContext().getTheme()); - mUnlockedLabel = context.getResources().getString(R.string.accessibility_unlock_button); - mLockedLabel = context.getResources().getString(R.string.accessibility_lock_icon); + mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); + mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); + + mIcon = (AnimatedStateListDrawable) + resources.getDrawable(R.drawable.super_lock_icon, mView.getContext().getTheme()); + mView.setImageDrawable(mIcon); + mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button); + mLockedLabel = resources.getString(R.string.accessibility_lock_icon); dumpManager.registerDumpable("LockIconViewController", this); } @@ -260,47 +252,52 @@ public class LockIconViewController extends ViewController<LockIconView> impleme return; } + boolean wasShowingUnlock = mShowUnlockIcon; boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon; - boolean wasShowingLockIcon = mShowLockIcon; - boolean wasShowingUnlockIcon = mShowUnlockIcon; mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen() - && (!mUdfpsEnrolled || !mRunningFPS); - mShowUnlockIcon = mCanDismissLockScreen && isLockScreen(); - mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS; + && (!mUdfpsEnrolled || !mRunningFPS); + mShowUnlockIcon = (mCanDismissLockScreen || mUserUnlockedWithBiometric) && isLockScreen(); + mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && mCanDismissLockScreen; + mShowAodLockIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && !mCanDismissLockScreen; final CharSequence prevContentDescription = mView.getContentDescription(); if (mShowLockIcon) { - mView.setImageDrawable(mLockIcon); - mView.setVisibility(View.VISIBLE); + mView.updateIcon(ICON_LOCK, false); mView.setContentDescription(mLockedLabel); + mView.setVisibility(View.VISIBLE); } else if (mShowUnlockIcon) { - if (!wasShowingUnlockIcon) { - if (wasShowingFpIcon) { - mView.setImageDrawable(mFpToUnlockIcon); - mFpToUnlockIcon.forceAnimationOnUI(); - mFpToUnlockIcon.start(); - } else if (wasShowingLockIcon) { - mView.setImageDrawable(mLockToUnlockIcon); - mLockToUnlockIcon.forceAnimationOnUI(); - mLockToUnlockIcon.start(); - } else { - mView.setImageDrawable(mUnlockIcon); - } + if (wasShowingFpIcon) { + // fp icon was shown by UdfpsView, and now we still want to animate the transition + // in this drawable + mView.updateIcon(ICON_FINGERPRINT, false); } - mView.setVisibility(View.VISIBLE); + mView.updateIcon(ICON_UNLOCK, false); mView.setContentDescription(mUnlockedLabel); + mView.setVisibility(View.VISIBLE); } else if (mShowAODFpIcon) { - mView.setImageDrawable(null); + // AOD fp icon is special cased as a lottie view (it updates for each burn-in offset), + // this state shows a transparent view mView.setContentDescription(null); mAodFp.setVisibility(View.VISIBLE); mAodFp.setContentDescription(mCanDismissLockScreen ? mUnlockedLabel : mLockedLabel); + + mView.updateIcon(ICON_FINGERPRINT, true); // this shows no icon + mView.setVisibility(View.VISIBLE); + } else if (mShowAodLockIcon) { + if (wasShowingUnlock) { + // transition to the unlock icon first + mView.updateIcon(ICON_LOCK, false); + } + mView.updateIcon(ICON_LOCK, true); + mView.setContentDescription(mLockedLabel); mView.setVisibility(View.VISIBLE); } else { + mView.clearIcon(); mView.setVisibility(View.INVISIBLE); mView.setContentDescription(null); } - if (!mShowAODFpIcon) { + if (!mShowAODFpIcon && mAodFp != null) { mAodFp.setVisibility(View.INVISIBLE); mAodFp.setContentDescription(null); } @@ -385,6 +382,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme pw.println("mUdfpsSupported: " + mUdfpsSupported); pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled); pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing); + pw.println(" mIcon: "); + for (int state : mIcon.getState()) { + pw.print(" " + state); + } + pw.println(); pw.println(" mShowUnlockIcon: " + mShowUnlockIcon); pw.println(" mShowLockIcon: " + mShowLockIcon); pw.println(" mShowAODFpIcon: " + mShowAODFpIcon); @@ -416,10 +418,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme - mMaxBurnInOffsetY, mInterpolatedDarkAmount); float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount); - mAodFp.setTranslationX(offsetX); - mAodFp.setTranslationY(offsetY); - mAodFp.setProgress(progress); - mAodFp.setAlpha(255 * mInterpolatedDarkAmount); + if (mAodFp != null) { + mAodFp.setTranslationX(offsetX); + mAodFp.setTranslationY(offsetY); + mAodFp.setProgress(progress); + mAodFp.setAlpha(255 * mInterpolatedDarkAmount); + } + + if (mShowAodLockIcon) { + mView.setTranslationX(offsetX); + mView.setTranslationY(offsetY); + } } private void updateIsUdfpsEnrolled() { @@ -430,6 +439,10 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mView.setUseBackground(mUdfpsSupported); mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); + if (!wasUdfpsEnrolled && mUdfpsEnrolled && mAodFp == null) { + mLayoutInflater.inflate(R.layout.udfps_aod_lock_icon, mView); + mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp); + } if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) { updateVisibility(); } @@ -440,6 +453,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Override public void onDozeAmountChanged(float linear, float eased) { mInterpolatedDarkAmount = eased; + mView.setDozeAmount(eased); updateBurnInOffsets(); } @@ -477,13 +491,15 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Override public void onBiometricRunningStateChanged(boolean running, BiometricSourceType biometricSourceType) { + final boolean wasRunningFps = mRunningFPS; + final boolean wasUserUnlockedWithBiometric = mUserUnlockedWithBiometric; mUserUnlockedWithBiometric = mKeyguardUpdateMonitor.getUserUnlockedWithBiometric( KeyguardUpdateMonitor.getCurrentUser()); if (biometricSourceType == FINGERPRINT) { mRunningFPS = running; - if (!mRunningFPS) { + if (wasRunningFps && !mRunningFPS) { if (mCancelDelayedUpdateVisibilityRunnable != null) { mCancelDelayedUpdateVisibilityRunnable.run(); } @@ -493,10 +509,14 @@ public class LockIconViewController extends ViewController<LockIconView> impleme // button in this case, so we delay updating the visibility by 50ms. mCancelDelayedUpdateVisibilityRunnable = mExecutor.executeDelayed(() -> updateVisibility(), 50); - } else { - updateVisibility(); + return; } } + + if (wasUserUnlockedWithBiometric != mUserUnlockedWithBiometric + || wasRunningFps != mRunningFPS) { + updateVisibility(); + } } }; @@ -545,11 +565,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } @Override - public void onOverlayChanged() { - updateColors(); - } - - @Override public void onConfigChanged(Configuration newConfig) { updateConfiguration(); updateColors(); @@ -559,7 +574,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private final GestureDetector mGestureDetector = new GestureDetector(new SimpleOnGestureListener() { public boolean onDown(MotionEvent e) { - mDetectedLongPress = false; if (!isClickable()) { mDownDetected = false; return false; @@ -584,7 +598,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme if (!wasClickableOnDownEvent()) { return; } - mDetectedLongPress = true; if (onAffordanceClick() && mVibrator != null) { // only vibrate if the click went through and wasn't intercepted by falsing @@ -650,7 +663,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) { if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY()) && (mView.getVisibility() == View.VISIBLE - || mAodFp.getVisibility() == View.VISIBLE)) { + || (mAodFp != null && mAodFp.getVisibility() == View.VISIBLE))) { mOnGestureDetectedRunnable = onGestureDetectedRunnable; mGestureDetector.onTouchEvent(event); } diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java index 1d51e5925de8..b8841eda1de4 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewModule.java @@ -34,6 +34,6 @@ public abstract class KeyguardStatusViewModule { @Provides static KeyguardSliceView getKeyguardSliceView(KeyguardClockSwitch keyguardClockSwitch) { - return keyguardClockSwitch.findViewById(R.id.keyguard_status_area); + return keyguardClockSwitch.findViewById(R.id.keyguard_slice_view); } } diff --git a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java index 5ed9eaad0a00..12dd8f06de17 100644 --- a/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java +++ b/packages/SystemUI/src/com/android/systemui/AutoReinflateContainer.java @@ -86,7 +86,7 @@ public class AutoReinflateContainer extends FrameLayout implements } @Override - public void onOverlayChanged() { + public void onThemeChanged() { inflateLayout(); } diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index cffc048f3fd4..d1739aaccac2 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -127,7 +127,12 @@ public class ImageWallpaper extends WallpaperService { setFixedSizeAllowed(true); updateSurfaceSize(); - mRenderer.setOnBitmapChanged(this::updateMiniBitmap); + mRenderer.setOnBitmapChanged(b -> { + mLocalColorsToAdd.addAll(mColorAreas); + if (mLocalColorsToAdd.size() > 0) { + updateMiniBitmapAndNotify(b); + } + }); getDisplayContext().getSystemService(DisplayManager.class) .registerDisplayListener(this, mWorker.getThreadHandler()); Trace.endSection(); @@ -171,7 +176,7 @@ public class ImageWallpaper extends WallpaperService { computeAndNotifyLocalColors(new ArrayList<>(mColorAreas), mMiniBitmap)); } - private void updateMiniBitmap(Bitmap b) { + private void updateMiniBitmapAndNotify(Bitmap b) { if (b == null) return; int size = Math.min(b.getWidth(), b.getHeight()); float scale = 1.0f; @@ -233,6 +238,7 @@ public class ImageWallpaper extends WallpaperService { Bitmap bitmap = mMiniBitmap; if (bitmap == null) { mLocalColorsToAdd.addAll(regions); + if (mRenderer != null) mRenderer.use(this::updateMiniBitmapAndNotify); } else { computeAndNotifyLocalColors(regions, bitmap); } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 23a3f8d58f71..80c3616f693e 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -161,8 +161,6 @@ public class ScreenDecorations extends SystemUI implements Tunable { private boolean mPendingRotationChange; private boolean mIsRoundedCornerMultipleRadius; private boolean mIsPrivacyDotEnabled; - private int mStatusBarHeightPortrait; - private int mStatusBarHeightLandscape; private Drawable mRoundedCornerDrawable; private Drawable mRoundedCornerDrawableTop; private Drawable mRoundedCornerDrawableBottom; @@ -315,7 +313,6 @@ public class ScreenDecorations extends SystemUI implements Tunable { private void setupDecorations() { if (hasRoundedCorners() || shouldDrawCutout() || mIsPrivacyDotEnabled) { - updateStatusBarHeight(); final DisplayCutout cutout = getCutout(); for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { if (shouldShowCutout(i, cutout) || shouldShowRoundedCorner(i, cutout) @@ -326,7 +323,8 @@ public class ScreenDecorations extends SystemUI implements Tunable { } } - if (mIsPrivacyDotEnabled) { + if (mTopLeftDot != null && mTopRightDot != null && mBottomLeftDot != null + && mBottomRightDot != null) { // Overlays have been created, send the dots to the controller //TODO: need a better way to do this mDotViewController.initialize( @@ -430,7 +428,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { if (mOverlays[pos] != null) { return; } - mOverlays[pos] = overlayForPosition(pos); + mOverlays[pos] = overlayForPosition(pos, cutout); mCutoutViews[pos] = new DisplayCutoutView(mContext, pos, this); ((ViewGroup) mOverlays[pos]).addView(mCutoutViews[pos]); @@ -462,10 +460,46 @@ public class ScreenDecorations extends SystemUI implements Tunable { /** * Allow overrides for top/bottom positions */ - private View overlayForPosition(@BoundsPosition int pos) { + private View overlayForPosition(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { final int layoutId = (pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_TOP) ? R.layout.rounded_corners_top : R.layout.rounded_corners_bottom; - return LayoutInflater.from(mContext).inflate(layoutId, null); + final ViewGroup vg = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutId, null); + initPrivacyDotView(vg, pos, cutout); + return vg; + } + + private void initPrivacyDotView(@NonNull ViewGroup viewGroup, @BoundsPosition int pos, + @Nullable DisplayCutout cutout) { + final View left = viewGroup.findViewById(R.id.privacy_dot_left_container); + final View right = viewGroup.findViewById(R.id.privacy_dot_right_container); + if (!shouldShowPrivacyDot(pos, cutout)) { + viewGroup.removeView(left); + viewGroup.removeView(right); + return; + } + + switch (pos) { + case BOUNDS_POSITION_LEFT: { + mTopLeftDot = left; + mBottomLeftDot = right; + break; + } + case BOUNDS_POSITION_TOP: { + mTopLeftDot = left; + mTopRightDot = right; + break; + } + case BOUNDS_POSITION_RIGHT: { + mTopRightDot = left; + mBottomRightDot = right; + break; + } + case BOUNDS_POSITION_BOTTOM: { + mBottomLeftDot = left; + mBottomRightDot = right; + break; + } + } } private void updateView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { @@ -483,8 +517,6 @@ public class ScreenDecorations extends SystemUI implements Tunable { if (mCutoutViews != null && mCutoutViews[pos] != null) { mCutoutViews[pos].setRotation(mRotation); } - - updatePrivacyDotView(pos, cutout); } @VisibleForTesting @@ -671,14 +703,6 @@ public class ScreenDecorations extends SystemUI implements Tunable { } } - private void updateStatusBarHeight() { - mStatusBarHeightLandscape = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height_landscape); - mStatusBarHeightPortrait = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height_portrait); - mDotViewController.setStatusBarHeights(mStatusBarHeightPortrait, mStatusBarHeightLandscape); - } - private void updateRoundedCornerRadii() { // We should eventually move to just using the intrinsic size of the drawables since // they should be sized to the exact pixels they want to cover. Therefore I'm purposely not @@ -812,26 +836,6 @@ public class ScreenDecorations extends SystemUI implements Tunable { } } - private void updatePrivacyDotView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { - final ViewGroup viewGroup = (ViewGroup) mOverlays[pos]; - - final View left = viewGroup.findViewById(R.id.privacy_dot_left_container); - final View right = viewGroup.findViewById(R.id.privacy_dot_right_container); - if (shouldShowPrivacyDot(pos, cutout)) { - // TODO (b/201481944) Privacy Dots pos and var are wrong with long side cutout enable - if (pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_TOP) { - mTopLeftDot = left; - mTopRightDot = right; - } else { - mBottomLeftDot = left; - mBottomRightDot = right; - } - } else { - viewGroup.removeView(left); - viewGroup.removeView(right); - } - } - private int getRoundedCornerGravity(@BoundsPosition int pos, boolean isStart) { final int rotatedPos = getBoundPositionFromRotation(pos, mRotation); switch (rotatedPos) { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index a28223de2bf1..c64f416f9672 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -118,6 +118,7 @@ public class SystemUIFactory { .setTaskViewFactory(mWMComponent.getTaskViewFactory()) .setTransitions(mWMComponent.getTransitions()) .setStartingSurface(mWMComponent.getStartingSurface()) + .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper()) .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper()); } else { // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option @@ -133,6 +134,7 @@ public class SystemUIFactory { .setAppPairs(Optional.ofNullable(null)) .setTaskViewFactory(Optional.ofNullable(null)) .setTransitions(Transitions.createEmptyForTesting()) + .setDisplayAreaHelper(Optional.ofNullable(null)) .setStartingSurface(Optional.ofNullable(null)) .setTaskSurfaceHelper(Optional.ofNullable(null)); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java index d8e80fe99331..0d7551ff66e9 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AnnotationLinkSpan.java @@ -30,7 +30,7 @@ import java.util.Optional; /** * A span that turns the text wrapped by annotation tag into the clickable link text. */ -class AnnotationLinkSpan extends ClickableSpan { +public class AnnotationLinkSpan extends ClickableSpan { private final Optional<View.OnClickListener> mClickListener; private AnnotationLinkSpan(View.OnClickListener listener) { @@ -50,7 +50,7 @@ class AnnotationLinkSpan extends ClickableSpan { * @param linkInfos used to attach the click action into the corresponding span * @return the text attached with the span */ - static CharSequence linkify(CharSequence text, LinkInfo... linkInfos) { + public static CharSequence linkify(CharSequence text, LinkInfo... linkInfos) { final SpannableString msg = new SpannableString(text); final Annotation[] spans = msg.getSpans(/* queryStart= */ 0, msg.length(), Annotation.class); @@ -78,12 +78,12 @@ class AnnotationLinkSpan extends ClickableSpan { /** * Data class to store the annotation and the click action. */ - static class LinkInfo { - static final String DEFAULT_ANNOTATION = "link"; + public static class LinkInfo { + public static final String DEFAULT_ANNOTATION = "link"; private final Optional<String> mAnnotation; private final Optional<View.OnClickListener> mListener; - LinkInfo(@NonNull String annotation, View.OnClickListener listener) { + public LinkInfo(@NonNull String annotation, View.OnClickListener listener) { mAnnotation = Optional.of(annotation); mListener = Optional.ofNullable(listener); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index da69f4556547..f11dc9313852 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -824,12 +824,8 @@ public abstract class AuthBiometricView extends LinearLayout { return new AuthDialog.LayoutParams(width, totalHeight); } - /** - * Simple heuristic which should return true displays that are larger than a normal phone. - * For example, tablet displays, or the unfolded display for foldables. - */ - private boolean isLargeDisplay(int width, int height) { - return width > 1200 && height > 1200; + private boolean isLargeDisplay() { + return com.android.systemui.util.Utils.shouldUseSplitNotificationShade(getResources()); } @Override @@ -837,12 +833,12 @@ public abstract class AuthBiometricView extends LinearLayout { final int width = MeasureSpec.getSize(widthMeasureSpec); final int height = MeasureSpec.getSize(heightMeasureSpec); - Log.d(TAG, "Width: " + width + ", height: " + height); + final boolean isLargeDisplay = isLargeDisplay(); final int newWidth; - if (isLargeDisplay(width, height)) { - // TODO: Unless we can come up with a one-size-fits-all equation, we may want to - // consider moving this to an overlay. + if (isLargeDisplay) { + // TODO(b/201811580): Unless we can come up with a one-size-fits-all equation, we may + // want to consider moving this to an overlay. newWidth = 2 * Math.min(width, height) / 3; } else { newWidth = Math.min(width, height); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 0932a8ce757e..8b04bf59658a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -272,9 +272,6 @@ class AuthRippleController @Inject constructor( override fun onThemeChanged() { updateRippleColor() } - override fun onOverlayChanged() { - updateRippleColor() - } } private val udfpsControllerCallback = diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 594a642f33bb..b2eaa2b8a821 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -256,6 +256,13 @@ public class UdfpsController implements DozeReceiver { @Override public void hideUdfpsOverlay(int sensorId) { mFgExecutor.execute(() -> { + if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { + // if we get here, we expect keyguardUpdateMonitor's fingerprintRunningState + // to be updated shortly afterwards + Log.d(TAG, "hiding udfps overlay when " + + "mKeyguardUpdateMonitor.isFingerprintDetectionRunning()=true"); + } + mServerRequest = null; updateOverlay(); }); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt index ea2bbfad1b74..e23131069eab 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt @@ -58,9 +58,6 @@ class UdfpsHapticsSimulator @Inject constructor( "start" -> { udfpsController?.playStartHaptic() } - "acquired" -> { - keyguardUpdateMonitor.playAcquiredHaptic() - } "success" -> { // needs to be kept up to date with AcquisitionClient#SUCCESS_VIBRATION_EFFECT vibrator?.vibrate( @@ -82,7 +79,6 @@ class UdfpsHapticsSimulator @Inject constructor( pw.println("Usage: adb shell cmd statusbar udfps-haptic <haptic>") pw.println("Available commands:") pw.println(" start") - pw.println(" acquired") pw.println(" success, always plays CLICK haptic") pw.println(" error, always plays DOUBLE_CLICK haptic") } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index db93b26d99e5..7a28c9d52260 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -398,11 +398,6 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud } @Override - public void onOverlayChanged() { - mView.updateColor(); - } - - @Override public void onConfigChanged(Configuration newConfig) { mView.updateColor(); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index 37a6cfaabb5e..0a9329845b23 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -330,7 +330,7 @@ public class BrightLineFalsingManager implements FalsingManager { || mTestHarness || mDataProvider.isJustUnlockedWithFace() || mDataProvider.isDocked() - || mAccessibilityManager.isEnabled(); + || mAccessibilityManager.isTouchExplorationEnabled(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index d74df37401d0..79723188348e 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -374,6 +374,12 @@ public class FrameworkServicesModule { @Provides @Singleton + static Optional<TelecomManager> provideOptionalTelecomManager(Context context) { + return Optional.ofNullable(context.getSystemService(TelecomManager.class)); + } + + @Provides + @Singleton static TelephonyManager provideTelephonyManager(Context context) { return context.getSystemService(TelephonyManager.class); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 0fdc4d8e86a7..aac03f8551fc 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -30,6 +30,7 @@ import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.bubbles.Bubbles; +import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; @@ -96,6 +97,9 @@ public interface SysUIComponent { Builder setStartingSurface(Optional<StartingSurface> s); @BindsInstance + Builder setDisplayAreaHelper(Optional<DisplayAreaHelper> h); + + @BindsInstance Builder setTaskSurfaceHelper(Optional<TaskSurfaceHelper> t); SysUIComponent build(); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 3b459d1427d5..f9331280ece3 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -38,7 +38,10 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.dagger.DemoModeModule; import com.android.systemui.doze.dagger.DozeComponent; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlagManager; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.FlagReader; +import com.android.systemui.flags.FlagWriter; import com.android.systemui.fragments.FragmentService; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.model.SysUiState; @@ -149,6 +152,12 @@ public abstract class SystemUIModule { return state; } + @Binds + abstract FlagReader provideFlagReader(FeatureFlagManager impl); + + @Binds + abstract FlagWriter provideFlagWriter(FeatureFlagManager impl); + @BindsOptionalOf abstract CommandQueue optionalCommandQueue(); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index 442d351729d7..618c26b89196 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -27,6 +27,7 @@ import com.android.wm.shell.ShellInit; import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.bubbles.Bubbles; +import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; @@ -105,5 +106,8 @@ public interface WMComponent { Optional<StartingSurface> getStartingSurface(); @WMSingleton + Optional<DisplayAreaHelper> getDisplayAreaHelper(); + + @WMSingleton Optional<TaskSurfaceHelper> getTaskSurfaceHelper(); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index 845d7dc26ee5..669965bcbea5 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -26,6 +26,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; +import com.android.systemui.statusbar.policy.DevicePostureController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -268,6 +269,16 @@ public class DozeLog implements Dumpable { } /** + * Appends doze updates due to a posture change. + */ + public void tracePostureChanged( + @DevicePostureController.DevicePostureInt int posture, + String partUpdated + ) { + mLogger.logPostureChanged(posture, partUpdated); + } + + /** * Appends pulse dropped event to logs */ public void tracePulseDropped(boolean pulsePending, DozeMachine.State state, boolean blocked) { @@ -391,8 +402,8 @@ public class DozeLog implements Dumpable { case REASON_SENSOR_DOUBLE_TAP: return "doubletap"; case PULSE_REASON_SENSOR_LONG_PRESS: return "longpress"; case PULSE_REASON_DOCKING: return "docking"; - case PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN: return "reach-wakelockscreen"; - case REASON_SENSOR_WAKE_UP: return "presence-wakeup"; + case PULSE_REASON_SENSOR_WAKE_REACH: return "reach-wakelockscreen"; + case REASON_SENSOR_WAKE_UP_PRESENCE: return "presence-wakeup"; case REASON_SENSOR_TAP: return "tap"; case REASON_SENSOR_UDFPS_LONG_PRESS: return "udfps"; case REASON_SENSOR_QUICK_PICKUP: return "quickPickup"; @@ -403,8 +414,8 @@ public class DozeLog implements Dumpable { @Retention(RetentionPolicy.SOURCE) @IntDef({PULSE_REASON_NONE, PULSE_REASON_INTENT, PULSE_REASON_NOTIFICATION, PULSE_REASON_SENSOR_SIGMOTION, REASON_SENSOR_PICKUP, REASON_SENSOR_DOUBLE_TAP, - PULSE_REASON_SENSOR_LONG_PRESS, PULSE_REASON_DOCKING, REASON_SENSOR_WAKE_UP, - PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, REASON_SENSOR_TAP, + PULSE_REASON_SENSOR_LONG_PRESS, PULSE_REASON_DOCKING, REASON_SENSOR_WAKE_UP_PRESENCE, + PULSE_REASON_SENSOR_WAKE_REACH, REASON_SENSOR_TAP, REASON_SENSOR_UDFPS_LONG_PRESS, REASON_SENSOR_QUICK_PICKUP}) public @interface Reason {} public static final int PULSE_REASON_NONE = -1; @@ -415,8 +426,8 @@ public class DozeLog implements Dumpable { public static final int REASON_SENSOR_DOUBLE_TAP = 4; public static final int PULSE_REASON_SENSOR_LONG_PRESS = 5; public static final int PULSE_REASON_DOCKING = 6; - public static final int REASON_SENSOR_WAKE_UP = 7; - public static final int PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN = 8; + public static final int REASON_SENSOR_WAKE_UP_PRESENCE = 7; + public static final int PULSE_REASON_SENSOR_WAKE_REACH = 8; public static final int REASON_SENSOR_TAP = 9; public static final int REASON_SENSOR_UDFPS_LONG_PRESS = 10; public static final int REASON_SENSOR_QUICK_PICKUP = 11; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index dc186182432f..d79bf22cced2 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -24,6 +24,7 @@ import com.android.systemui.log.LogLevel.DEBUG import com.android.systemui.log.LogLevel.ERROR import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.dagger.DozeLog +import com.android.systemui.statusbar.policy.DevicePostureController import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -195,6 +196,16 @@ class DozeLogger @Inject constructor( }) } + fun logPostureChanged(posture: Int, partUpdated: String) { + buffer.log(TAG, INFO, { + int1 = posture + str1 = partUpdated + }, { + "Posture changed, posture=${DevicePostureController.devicePostureToString(int1)}" + + " partUpdated=$str1" + }) + } + fun logPulseDropped(pulsePending: Boolean, state: DozeMachine.State, blocked: Boolean) { buffer.log(TAG, INFO, { bool1 = pulsePending diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index da7b389fbd36..765c24507662 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -29,16 +29,18 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; +import android.util.IndentingPrintWriter; -import com.android.systemui.dock.DockManager; import com.android.systemui.doze.dagger.BrightnessSensor; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.doze.dagger.WrappedService; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.util.sensors.AsyncSensorManager; import java.io.PrintWriter; +import java.util.Objects; import java.util.Optional; import javax.inject.Inject; @@ -60,14 +62,17 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi private final DozeHost mDozeHost; private final Handler mHandler; private final SensorManager mSensorManager; - private final Optional<Sensor> mLightSensorOptional; + private final Optional<Sensor>[] mLightSensorOptional; // light sensors to use per posture private final WakefulnessLifecycle mWakefulnessLifecycle; private final DozeParameters mDozeParameters; - private final DockManager mDockManager; + private final DevicePostureController mDevicePostureController; + private final DozeLog mDozeLog; private final int[] mSensorToBrightness; private final int[] mSensorToScrimOpacity; private final int mScreenBrightnessDim; + @DevicePostureController.DevicePostureInt + private int mDevicePosture; private boolean mRegistered; private int mDefaultDozeBrightness; private boolean mPaused = false; @@ -83,27 +88,36 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi private int mDebugBrightnessBucket = -1; @Inject - public DozeScreenBrightness(Context context, @WrappedService DozeMachine.Service service, + public DozeScreenBrightness( + Context context, + @WrappedService DozeMachine.Service service, AsyncSensorManager sensorManager, - @BrightnessSensor Optional<Sensor> lightSensorOptional, DozeHost host, Handler handler, + @BrightnessSensor Optional<Sensor>[] lightSensorOptional, + DozeHost host, Handler handler, AlwaysOnDisplayPolicy alwaysOnDisplayPolicy, WakefulnessLifecycle wakefulnessLifecycle, DozeParameters dozeParameters, - DockManager dockManager) { + DevicePostureController devicePostureController, + DozeLog dozeLog + ) { mContext = context; mDozeService = service; mSensorManager = sensorManager; mLightSensorOptional = lightSensorOptional; + mDevicePostureController = devicePostureController; + mDevicePosture = mDevicePostureController.getDevicePosture(); mWakefulnessLifecycle = wakefulnessLifecycle; mDozeParameters = dozeParameters; mDozeHost = host; mHandler = handler; - mDockManager = dockManager; + mDozeLog = dozeLog; mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness; mScreenBrightnessDim = alwaysOnDisplayPolicy.dimBrightness; mSensorToBrightness = alwaysOnDisplayPolicy.screenBrightnessArray; mSensorToScrimOpacity = alwaysOnDisplayPolicy.dimmingScrimArray; + + mDevicePostureController.addCallback(mDevicePostureCallback); } @Override @@ -133,6 +147,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi private void onDestroy() { setLightSensorEnabled(false); + mDevicePostureController.removeCallback(mDevicePostureCallback); } @Override @@ -159,7 +174,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi } int scrimOpacity = -1; - if (!mLightSensorOptional.isPresent()) { + if (!isLightSensorPresent()) { // No light sensor, scrims are always transparent. scrimOpacity = 0; } else if (brightnessReady) { @@ -172,6 +187,27 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi } } + private boolean lightSensorSupportsCurrentPosture() { + return mLightSensorOptional != null + && mDevicePosture < mLightSensorOptional.length; + } + + private boolean isLightSensorPresent() { + if (!lightSensorSupportsCurrentPosture()) { + return mLightSensorOptional != null && mLightSensorOptional[0].isPresent(); + } + + return mLightSensorOptional[mDevicePosture].isPresent(); + } + + private Sensor getLightSensor() { + if (!lightSensorSupportsCurrentPosture()) { + return null; + } + + return mLightSensorOptional[mDevicePosture].get(); + } + private int computeScrimOpacity(int sensorValue) { if (sensorValue < 0 || sensorValue >= mSensorToScrimOpacity.length) { return -1; @@ -220,9 +256,9 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi } private void setLightSensorEnabled(boolean enabled) { - if (enabled && !mRegistered && mLightSensorOptional.isPresent()) { + if (enabled && !mRegistered && isLightSensorPresent()) { // Wait until we get an event from the sensor until indicating ready. - mRegistered = mSensorManager.registerListener(this, mLightSensorOptional.get(), + mRegistered = mSensorManager.registerListener(this, getLightSensor(), SensorManager.SENSOR_DELAY_NORMAL, mHandler); mLastSensorValue = -1; } else if (!enabled && mRegistered) { @@ -255,6 +291,41 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi /** Dump current state */ public void dump(PrintWriter pw) { - pw.println("DozeScreenBrightnessSensorRegistered=" + mRegistered); + pw.println("DozeScreenBrightness:"); + IndentingPrintWriter idpw = new IndentingPrintWriter(pw); + idpw.increaseIndent(); + idpw.println("registered=" + mRegistered); + idpw.println("posture=" + DevicePostureController.devicePostureToString(mDevicePosture)); } + + private final DevicePostureController.Callback mDevicePostureCallback = + new DevicePostureController.Callback() { + @Override + public void onPostureChanged(int posture) { + if (mDevicePosture == posture + || mLightSensorOptional.length < 2 + || posture >= mLightSensorOptional.length) { + return; + } + + final Sensor oldSensor = mLightSensorOptional[mDevicePosture].get(); + final Sensor newSensor = mLightSensorOptional[posture].get(); + if (Objects.equals(oldSensor, newSensor)) { + mDevicePosture = posture; + // uses the same sensor for the new posture + return; + } + + // cancel the previous sensor: + if (mRegistered) { + setLightSensorEnabled(false); + mDevicePosture = posture; + setLightSensorEnabled(true); + } else { + mDevicePosture = posture; + } + mDozeLog.tracePostureChanged(mDevicePosture, "DozeScreenBrightness swap " + + "{" + oldSensor + "} => {" + newSensor + "}, mRegistered=" + mRegistered); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 3cefce83393a..9d0591e4bda2 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -38,6 +38,7 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.view.Display; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.UiEvent; @@ -54,10 +55,36 @@ import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.wakelock.WakeLock; import java.io.PrintWriter; +import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.function.Consumer; +/** + * Tracks and registers/unregisters sensors while the device is dozing based on the config + * provided by {@link AmbientDisplayConfiguration} and parameters provided by {@link DozeParameters} + * + * Sensors registration depends on: + * - sensor existence/availability + * - user configuration (some can be toggled on/off via settings) + * - use of the proximity sensor (sometimes prox cannot be registered in certain display states) + * - touch state + * - device posture + * + * Sensors will trigger the provided Callback's {@link Callback#onSensorPulse} method. + * These sensors include: + * - pickup gesture + * - single and double tap gestures + * - udfps long-press gesture + * - reach and presence gestures + * - quick pickup gesture (low-threshold pickup gesture) + * + * This class also registers a ProximitySensor that reports near/far events and will + * trigger callbacks on the provided {@link mProxCallback}. + */ public class DozeSensors { private static final boolean DEBUG = DozeService.DEBUG; @@ -68,15 +95,21 @@ public class DozeSensors { private final AsyncSensorManager mSensorManager; private final AmbientDisplayConfiguration mConfig; private final WakeLock mWakeLock; - private final Consumer<Boolean> mProxCallback; + private final DozeLog mDozeLog; private final SecureSettings mSecureSettings; - private final Callback mCallback; + private final DevicePostureController mDevicePostureController; private final boolean mScreenOffUdfpsEnabled; + + // Sensors @VisibleForTesting - protected TriggerSensor[] mSensors; + protected TriggerSensor[] mTriggerSensors; + private final ProximitySensor mProximitySensor; + + // Sensor callbacks + private final Callback mSensorCallback; // receives callbacks on registered sensor events + private final Consumer<Boolean> mProxCallback; // receives callbacks on near/far updates private final Handler mHandler = new Handler(); - private final ProximitySensor mProximitySensor; private long mDebounceFrom; private boolean mSettingRegistered; private boolean mListening; @@ -106,37 +139,47 @@ public class DozeSensors { } } - DozeSensors(Context context, AsyncSensorManager sensorManager, - DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock, - Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog, - ProximitySensor proximitySensor, SecureSettings secureSettings, + DozeSensors( + Context context, + AsyncSensorManager sensorManager, + DozeParameters dozeParameters, + AmbientDisplayConfiguration config, + WakeLock wakeLock, + Callback sensorCallback, + Consumer<Boolean> proxCallback, + DozeLog dozeLog, + ProximitySensor proximitySensor, + SecureSettings secureSettings, AuthController authController, - int devicePosture) { + DevicePostureController devicePostureController + ) { mContext = context; mSensorManager = sensorManager; mConfig = config; mWakeLock = wakeLock; mProxCallback = proxCallback; mSecureSettings = secureSettings; - mCallback = callback; + mSensorCallback = sensorCallback; + mDozeLog = dozeLog; mProximitySensor = proximitySensor; mProximitySensor.setTag(TAG); mSelectivelyRegisterProxSensors = dozeParameters.getSelectivelyRegisterSensorsUsingProx(); mListeningProxSensors = !mSelectivelyRegisterProxSensors; mScreenOffUdfpsEnabled = config.screenOffUdfpsEnabled(KeyguardUpdateMonitor.getCurrentUser()); - mDevicePosture = devicePosture; + mDevicePostureController = devicePostureController; + mDevicePosture = mDevicePostureController.getDevicePosture(); boolean udfpsEnrolled = authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()); boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT); - mSensors = new TriggerSensor[] { + mTriggerSensors = new TriggerSensor[] { new TriggerSensor( mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION), null /* setting */, dozeParameters.getPulseOnSigMotion(), DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */, - false /* touchscreen */, dozeLog), + false /* touchscreen */), new TriggerSensor( mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE), Settings.Secure.DOZE_PICK_UP_GESTURE, @@ -145,18 +188,16 @@ public class DozeSensors { DozeLog.REASON_SENSOR_PICKUP, false /* touchCoords */, false /* touchscreen */, false /* ignoresSetting */, - false /* requires prox */, - dozeLog), + false /* requires prox */), new TriggerSensor( findSensor(config.doubleTapSensorType()), Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, true /* configured */, DozeLog.REASON_SENSOR_DOUBLE_TAP, dozeParameters.doubleTapReportsTouchCoordinates(), - true /* touchscreen */, - dozeLog), + true /* touchscreen */), new TriggerSensor( - findSensor(config.tapSensorType(mDevicePosture)), + findSensors(config.tapSensorTypeMapping()), Settings.Secure.DOZE_TAP_SCREEN_GESTURE, true /* settingDef */, true /* configured */, @@ -165,7 +206,7 @@ public class DozeSensors { true /* touchscreen */, false /* ignoresSetting */, dozeParameters.singleTapUsesProx(mDevicePosture) /* requiresProx */, - dozeLog), + mDevicePosture), new TriggerSensor( findSensor(config.longPressSensorType()), Settings.Secure.DOZE_PULSE_ON_LONG_PRESS, @@ -175,8 +216,7 @@ public class DozeSensors { true /* reports touch coordinates */, true /* touchscreen */, false /* ignoresSetting */, - dozeParameters.longPressUsesProx() /* requiresProx */, - dozeLog), + dozeParameters.longPressUsesProx() /* requiresProx */), new TriggerSensor( findSensor(config.udfpsLongPressSensorType()), "doze_pulse_on_auth", @@ -186,25 +226,22 @@ public class DozeSensors { true /* reports touch coordinates */, true /* touchscreen */, false /* ignoresSetting */, - dozeParameters.longPressUsesProx(), - dozeLog), + dozeParameters.longPressUsesProx()), new PluginSensor( new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY), Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE, mConfig.wakeScreenGestureAvailable() && alwaysOn, - DozeLog.REASON_SENSOR_WAKE_UP, + DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE, false /* reports touch coordinates */, - false /* touchscreen */, - dozeLog), + false /* touchscreen */), new PluginSensor( new SensorManagerPlugin.Sensor(TYPE_WAKE_LOCK_SCREEN), Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE, mConfig.wakeScreenGestureAvailable(), - DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, + DozeLog.PULSE_REASON_SENSOR_WAKE_REACH, false /* reports touch coordinates */, false /* touchscreen */, - mConfig.getWakeLockScreenDebounce(), - dozeLog), + mConfig.getWakeLockScreenDebounce()), new TriggerSensor( findSensor(config.quickPickupSensorType()), Settings.Secure.DOZE_QUICK_PICKUP_GESTURE, @@ -212,10 +249,11 @@ public class DozeSensors { config.quickPickupSensorEnabled(KeyguardUpdateMonitor.getCurrentUser()) && udfpsEnrolled, DozeLog.REASON_SENSOR_QUICK_PICKUP, - false /* touchCoords */, - false /* touchscreen */, dozeLog), + false /* requiresTouchCoordinates */, + false /* requiresTouchscreen */, + false /* ignoresSetting */, + false /* requiresProx */), }; - setProxListening(false); // Don't immediately start listening when we register. mProximitySensor.register( proximityEvent -> { @@ -223,17 +261,21 @@ public class DozeSensors { mProxCallback.accept(!proximityEvent.getBelow()); } }); + + mDevicePostureController.addCallback(mDevicePostureCallback); } /** - * Unregister any sensors. + * Unregister all sensors and callbacks. */ public void destroy() { // Unregisters everything, which is enough to allow gc. - for (TriggerSensor triggerSensor : mSensors) { + for (TriggerSensor triggerSensor : mTriggerSensors) { triggerSensor.setListening(false); } mProximitySensor.pause(); + + mDevicePostureController.removeCallback(mDevicePostureCallback); } /** @@ -248,6 +290,24 @@ public class DozeSensors { return findSensor(mSensorManager, type, null); } + @NonNull + private Sensor[] findSensors(@NonNull String[] types) { + Sensor[] sensorMap = new Sensor[DevicePostureController.SUPPORTED_POSTURES_SIZE]; + + // Map of sensorType => Sensor, so we reuse the same sensor if it's the same between + // postures + Map<String, Sensor> typeToSensorMap = new HashMap<>(); + for (int i = 0; i < types.length; i++) { + String sensorType = types[i]; + if (!typeToSensorMap.containsKey(sensorType)) { + typeToSensorMap.put(sensorType, findSensor(sensorType)); + } + sensorMap[i] = typeToSensorMap.get(sensorType); + } + + return sensorMap; + } + /** * Utility method to find a {@link Sensor} for the supplied string type and string name. * @@ -306,7 +366,7 @@ public class DozeSensors { */ private void updateListening() { boolean anyListening = false; - for (TriggerSensor s : mSensors) { + for (TriggerSensor s : mTriggerSensors) { boolean listen = mListening && (!s.mRequiresTouchscreen || mListeningTouchScreenSensors) && (!s.mRequiresProx || mListeningProxSensors); @@ -319,7 +379,7 @@ public class DozeSensors { if (!anyListening) { mSecureSettings.unregisterContentObserver(mSettingsObserver); } else if (!mSettingRegistered) { - for (TriggerSensor s : mSensors) { + for (TriggerSensor s : mTriggerSensors) { s.registerSettingsObserver(mSettingsObserver); } } @@ -328,7 +388,7 @@ public class DozeSensors { /** Set the listening state of only the sensors that require the touchscreen. */ public void setTouchscreenSensorsListening(boolean listening) { - for (TriggerSensor sensor : mSensors) { + for (TriggerSensor sensor : mTriggerSensors) { if (sensor.mRequiresTouchscreen) { sensor.setListening(listening); } @@ -336,7 +396,7 @@ public class DozeSensors { } public void onUserSwitched() { - for (TriggerSensor s : mSensors) { + for (TriggerSensor s : mTriggerSensors) { s.updateListening(); } } @@ -366,7 +426,7 @@ public class DozeSensors { if (userId != ActivityManager.getCurrentUser()) { return; } - for (TriggerSensor s : mSensors) { + for (TriggerSensor s : mTriggerSensors) { s.updateListening(); } } @@ -374,7 +434,7 @@ public class DozeSensors { /** Ignore the setting value of only the sensors that require the touchscreen. */ public void ignoreTouchScreenSensorsSettingInterferingWithDocking(boolean ignore) { - for (TriggerSensor sensor : mSensors) { + for (TriggerSensor sensor : mTriggerSensors) { if (sensor.mRequiresTouchscreen) { sensor.ignoreSetting(ignore); } @@ -392,7 +452,7 @@ public class DozeSensors { pw.println("mScreenOffUdfpsEnabled=" + mScreenOffUdfpsEnabled); IndentingPrintWriter idpw = new IndentingPrintWriter(pw); idpw.increaseIndent(); - for (TriggerSensor s : mSensors) { + for (TriggerSensor s : mTriggerSensors) { idpw.println("Sensor: " + s.toString()); } idpw.println("ProxSensor: " + mProximitySensor.toString()); @@ -407,7 +467,7 @@ public class DozeSensors { @VisibleForTesting class TriggerSensor extends TriggerEventListener { - final Sensor mSensor; + @NonNull final Sensor[] mSensors; // index = posture, value = sensor final boolean mConfigured; final int mPulseReason; private final String mSetting; @@ -420,27 +480,67 @@ public class DozeSensors { protected boolean mRegistered; protected boolean mDisabled; protected boolean mIgnoresSetting; - protected final DozeLog mDozeLog; - - public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason, - boolean reportsTouchCoordinates, boolean requiresTouchscreen, DozeLog dozeLog) { - this(sensor, setting, true /* settingDef */, configured, pulseReason, - reportsTouchCoordinates, requiresTouchscreen, dozeLog); - } - - public TriggerSensor(Sensor sensor, String setting, boolean settingDef, - boolean configured, int pulseReason, boolean reportsTouchCoordinates, - boolean requiresTouchscreen, DozeLog dozeLog) { - this(sensor, setting, settingDef, configured, pulseReason, reportsTouchCoordinates, - requiresTouchscreen, false /* ignoresSetting */, - false /* requiresProx */, dozeLog); - } - - private TriggerSensor(Sensor sensor, String setting, boolean settingDef, - boolean configured, int pulseReason, boolean reportsTouchCoordinates, - boolean requiresTouchscreen, boolean ignoresSetting, boolean requiresProx, - DozeLog dozeLog) { - mSensor = sensor; + private @DevicePostureController.DevicePostureInt int mPosture; + + TriggerSensor( + Sensor sensor, + String setting, + boolean configured, + int pulseReason, + boolean reportsTouchCoordinates, + boolean requiresTouchscreen + ) { + this( + sensor, + setting, + true /* settingDef */, + configured, + pulseReason, + false /* ignoresSetting */, + false /* requiresProx */, + reportsTouchCoordinates, + requiresTouchscreen + ); + } + + TriggerSensor( + Sensor sensor, + String setting, + boolean settingDef, + boolean configured, + int pulseReason, + boolean reportsTouchCoordinates, + boolean requiresTouchscreen, + boolean ignoresSetting, + boolean requiresProx + ) { + this( + new Sensor[]{ sensor }, + setting, + settingDef, + configured, + pulseReason, + reportsTouchCoordinates, + requiresTouchscreen, + ignoresSetting, + requiresProx, + DevicePostureController.DEVICE_POSTURE_UNKNOWN + ); + } + + TriggerSensor( + @NonNull Sensor[] sensors, + String setting, + boolean settingDef, + boolean configured, + int pulseReason, + boolean reportsTouchCoordinates, + boolean requiresTouchscreen, + boolean ignoresSetting, + boolean requiresProx, + @DevicePostureController.DevicePostureInt int posture + ) { + mSensors = sensors; mSetting = setting; mSettingDefault = settingDef; mConfigured = configured; @@ -449,7 +549,43 @@ public class DozeSensors { mRequiresTouchscreen = requiresTouchscreen; mIgnoresSetting = ignoresSetting; mRequiresProx = requiresProx; - mDozeLog = dozeLog; + mPosture = posture; + } + + /** + * @return true if the sensor changed based for the new posture + */ + public boolean setPosture(@DevicePostureController.DevicePostureInt int posture) { + if (mPosture == posture + || mSensors.length < 2 + || posture >= mSensors.length) { + return false; + } + + Sensor oldSensor = mSensors[mPosture]; + Sensor newSensor = mSensors[posture]; + if (Objects.equals(oldSensor, newSensor)) { + mPosture = posture; + // uses the same sensor for the new posture + return false; + } + + // cancel the previous sensor: + if (mRegistered) { + final boolean rt = mSensorManager.cancelTriggerSensor(this, oldSensor); + if (DEBUG) { + Log.d(TAG, "posture changed, cancelTriggerSensor[" + oldSensor + "] " + + rt); + } + mRegistered = false; + } + + // update the new sensor: + mPosture = posture; + updateListening(); + mDozeLog.tracePostureChanged(mPosture, "DozeSensors swap " + + "{" + oldSensor + "} => {" + newSensor + "}, mRegistered=" + mRegistered); + return true; } public void setListening(boolean listen) { @@ -471,24 +607,23 @@ public class DozeSensors { } public void updateListening() { - if (!mConfigured || mSensor == null) return; + final Sensor sensor = mSensors[mPosture]; + if (!mConfigured || sensor == null) return; if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)) { if (!mRegistered) { - mRegistered = mSensorManager.requestTriggerSensor(this, mSensor); + mRegistered = mSensorManager.requestTriggerSensor(this, sensor); if (DEBUG) { - Log.d(TAG, "requestTriggerSensor[" + mSensor - + "] " + mRegistered); + Log.d(TAG, "requestTriggerSensor[" + sensor + "] " + mRegistered); } } else { if (DEBUG) { - Log.d(TAG, "requestTriggerSensor[" + mSensor - + "] already registered"); + Log.d(TAG, "requestTriggerSensor[" + sensor + "] already registered"); } } } else if (mRegistered) { - final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor); + final boolean rt = mSensorManager.cancelTriggerSensor(this, sensor); if (DEBUG) { - Log.d(TAG, "cancelTriggerSensor[" + mSensor + "] " + rt); + Log.d(TAG, "cancelTriggerSensor[" + sensor + "] " + rt); } mRegistered = false; } @@ -506,21 +641,29 @@ public class DozeSensors { @Override public String toString() { - return new StringBuilder("{mRegistered=").append(mRegistered) + StringBuilder sb = new StringBuilder(); + sb.append("{") + .append("mRegistered=").append(mRegistered) .append(", mRequested=").append(mRequested) .append(", mDisabled=").append(mDisabled) .append(", mConfigured=").append(mConfigured) .append(", mIgnoresSetting=").append(mIgnoresSetting) - .append(", mSensor=").append(mSensor).append("}").toString(); + .append(", mSensors=").append(Arrays.toString(mSensors)); + if (mSensors.length > 2) { + sb.append(", mPosture=") + .append(DevicePostureController.devicePostureToString(mDevicePosture)); + } + return sb.append("}").toString(); } @Override @AnyThread public void onTrigger(TriggerEvent event) { + final Sensor sensor = mSensors[mDevicePosture]; mDozeLog.traceSensor(mPulseReason); mHandler.post(mWakeLock.wrap(() -> { if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event)); - if (mSensor != null && mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) { + if (sensor != null && sensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) { UI_EVENT_LOGGER.log(DozeSensorsUiEvent.ACTION_AMBIENT_GESTURE_PICKUP); } @@ -531,7 +674,7 @@ public class DozeSensors { screenX = event.values[0]; screenY = event.values[1]; } - mCallback.onSensorPulse(mPulseReason, screenX, screenY, event.values); + mSensorCallback.onSensorPulse(mPulseReason, screenX, screenY, event.values); if (!mRegistered) { updateListening(); // reregister, this sensor only fires once } @@ -569,17 +712,16 @@ public class DozeSensors { private long mDebounce; PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured, - int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen, - DozeLog dozeLog) { + int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen) { this(sensor, setting, configured, pulseReason, reportsTouchCoordinates, - requiresTouchscreen, 0L /* debounce */, dozeLog); + requiresTouchscreen, 0L /* debounce */); } PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured, int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen, - long debounce, DozeLog dozeLog) { + long debounce) { super(null, setting, configured, pulseReason, reportsTouchCoordinates, - requiresTouchscreen, dozeLog); + requiresTouchscreen); mPluginSensor = sensor; mDebounce = debounce; } @@ -633,11 +775,22 @@ public class DozeSensors { return; } if (DEBUG) Log.d(TAG, "onSensorEvent: " + triggerEventToString(event)); - mCallback.onSensorPulse(mPulseReason, -1, -1, event.getValues()); + mSensorCallback.onSensorPulse(mPulseReason, -1, -1, event.getValues()); })); } } + private final DevicePostureController.Callback mDevicePostureCallback = posture -> { + if (mDevicePosture == posture) { + return; + } + mDevicePosture = posture; + + for (TriggerSensor triggerSensor : mTriggerSensors) { + triggerSensor.setPosture(mDevicePosture); + } + }; + public interface Callback { /** diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index b17f078e24ef..20cd5b987c1c 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -99,8 +99,6 @@ public class DozeTriggers implements DozeMachine.Part { private final UiEventLogger mUiEventLogger; private final DevicePostureController mDevicePostureController; - private @DevicePostureController.DevicePostureInt int mDevicePosture; - private long mNotificationPulseTime; private boolean mPulsePending; private Runnable mAodInterruptRunnable; @@ -197,11 +195,12 @@ public class DozeTriggers implements DozeMachine.Part { mSensorManager = sensorManager; mWakeLock = wakeLock; mAllowPulseTriggers = true; + mDevicePostureController = devicePostureController; - mDevicePosture = devicePostureController.getDevicePosture(); mDozeSensors = new DozeSensors(context, mSensorManager, dozeParameters, config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor, - secureSettings, authController, mDevicePosture); + secureSettings, authController, devicePostureController); + mUiModeManager = mContext.getSystemService(UiModeManager.class); mDockManager = dockManager; mProxCheck = proxCheck; @@ -212,6 +211,10 @@ public class DozeTriggers implements DozeMachine.Part { mUiEventLogger = uiEventLogger; mKeyguardStateController = keyguardStateController; } + private final DevicePostureController.Callback mDevicePostureCallback = + posture -> { + + }; @Override public void setDozeMachine(DozeMachine dozeMachine) { @@ -284,8 +287,8 @@ public class DozeTriggers implements DozeMachine.Part { boolean isTap = pulseReason == DozeLog.REASON_SENSOR_TAP; boolean isPickup = pulseReason == DozeLog.REASON_SENSOR_PICKUP; boolean isLongPress = pulseReason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS; - boolean isWakeOnPresence = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP; - boolean isWakeOnReach = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN; + boolean isWakeOnPresence = pulseReason == DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE; + boolean isWakeOnReach = pulseReason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH; boolean isUdfpsLongPress = pulseReason == DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS; boolean isQuickPickup = pulseReason == DozeLog.REASON_SENSOR_QUICK_PICKUP; boolean isWakeDisplayEvent = isQuickPickup || ((isWakeOnPresence || isWakeOnReach) @@ -455,7 +458,7 @@ public class DozeTriggers implements DozeMachine.Part { mWantSensors = true; mWantTouchScreenSensors = true; if (newState == DozeMachine.State.DOZE_AOD && !sWakeDisplaySensorState) { - onWakeScreen(false, newState, DozeLog.REASON_SENSOR_WAKE_UP); + onWakeScreen(false, newState, DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE); } break; case DOZE_AOD_PAUSED: @@ -524,7 +527,7 @@ public class DozeTriggers implements DozeMachine.Part { // When already pulsing we're allowed to show the wallpaper directly without // requesting a new pulse. if (dozeState == DozeMachine.State.DOZE_PULSING - && reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) { + && reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) { mMachine.requestState(DozeMachine.State.DOZE_PULSING_BRIGHT); return; } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index fbe06b02d955..374bed31eb7d 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -155,7 +155,7 @@ public class DozeUi implements DozeMachine.Part, TunerService.Tunable, public void onPulseStarted() { try { mMachine.requestState( - reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN + reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH ? DozeMachine.State.DOZE_PULSING_BRIGHT : DozeMachine.State.DOZE_PULSING); } catch (IllegalStateException e) { diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java index 571b666e4573..32b7658b6e09 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java +++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java @@ -43,6 +43,9 @@ import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.wakelock.DelayedWakeLock; import com.android.systemui.util.wakelock.WakeLock; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import dagger.Module; @@ -94,16 +97,43 @@ public abstract class DozeModule { @Provides @BrightnessSensor - static Optional<Sensor> providesBrightnessSensor( + static Optional<Sensor>[] providesBrightnessSensors( AsyncSensorManager sensorManager, Context context, - DozeParameters dozeParameters, - DevicePostureController devicePostureController) { - return Optional.ofNullable( - DozeSensors.findSensor( - sensorManager, - context.getString(R.string.doze_brightness_sensor_type), - dozeParameters.brightnessName(devicePostureController.getDevicePosture()) - )); + DozeParameters dozeParameters) { + String[] sensorNames = dozeParameters.brightnessNames(); + if (sensorNames.length == 0 || sensorNames == null) { + // if no brightness names are specified, just use the brightness sensor type + return new Optional[]{ + Optional.ofNullable(DozeSensors.findSensor( + sensorManager, + context.getString(R.string.doze_brightness_sensor_type), + null + )) + }; + } + + // length and index of brightnessMap correspond to DevicePostureController.DevicePostureInt: + final Optional<Sensor>[] brightnessSensorMap = + new Optional[DevicePostureController.SUPPORTED_POSTURES_SIZE]; + Arrays.fill(brightnessSensorMap, Optional.empty()); + + // Map of sensorName => Sensor, so we reuse the same sensor if it's the same between + // postures + Map<String, Optional<Sensor>> nameToSensorMap = new HashMap<>(); + for (int i = 0; i < sensorNames.length; i++) { + final String sensorName = sensorNames[i]; + if (!nameToSensorMap.containsKey(sensorName)) { + nameToSensorMap.put(sensorName, + Optional.ofNullable( + DozeSensors.findSensor( + sensorManager, + context.getString(R.string.doze_brightness_sensor_type), + sensorNames[i] + ))); + } + brightnessSensorMap[i] = nameToSensorMap.get(sensorName); + } + return brightnessSensorMap; } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java new file mode 100644 index 000000000000..85baed4a221c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagManager.java @@ -0,0 +1,38 @@ +/* + * 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.flags; + +import com.android.systemui.dagger.SysUISingleton; + +import javax.inject.Inject; + +/** + * Default implementation of the a Flag manager that returns default values for release builds + */ +@SysUISingleton +public class FeatureFlagManager implements FlagReader, FlagWriter { + @Inject + public FeatureFlagManager() {} + public boolean isEnabled(String key, boolean defaultValue) { + return defaultValue; + } + public boolean isEnabled(int key, boolean defaultValue) { + return defaultValue; + } + public void setEnabled(String key, boolean value) {} + public void setEnabled(int key, boolean value) {} +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java index d4d01c8d97b5..e78646a4839d 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagReader.java @@ -16,26 +16,29 @@ package com.android.systemui.flags; -import android.content.Context; import android.content.res.Resources; import android.util.SparseArray; import androidx.annotation.BoolRes; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.plugins.FlagReaderPlugin; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.dump.DumpManager; import com.android.systemui.util.wrapper.BuildInfo; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; + import javax.inject.Inject; /** * Reads and caches feature flags for quick access * - * Feature flags must be defined as boolean resources. For example: + * Feature flags must be defined as boolean resources. For example:t * * {@code * <bool name="flag_foo_bar_baz">false</bool> @@ -55,71 +58,39 @@ import javax.inject.Inject; * Calls to this class should probably be wrapped by a method in {@link FeatureFlags}. */ @SysUISingleton -public class FeatureFlagReader { +public class FeatureFlagReader implements Dumpable { private final Resources mResources; private final boolean mAreFlagsOverrideable; - private final PluginManager mPluginManager; private final SystemPropertiesHelper mSystemPropertiesHelper; private final SparseArray<CachedFlag> mCachedFlags = new SparseArray<>(); - private FlagReaderPlugin mPlugin = new FlagReaderPlugin(){}; + private final FlagReader mFlagReader; @Inject public FeatureFlagReader( @Main Resources resources, BuildInfo build, - PluginManager pluginManager, - SystemPropertiesHelper systemPropertiesHelper) { + DumpManager dumpManager, + SystemPropertiesHelper systemPropertiesHelper, + FlagReader reader) { mResources = resources; - mPluginManager = pluginManager; + mFlagReader = reader; mSystemPropertiesHelper = systemPropertiesHelper; mAreFlagsOverrideable = build.isDebuggable() && mResources.getBoolean(R.bool.are_flags_overrideable); - - mPluginManager.addPluginListener(mPluginListener, FlagReaderPlugin.class); + dumpManager.registerDumpable("FeatureFlags", this); } - private final PluginListener<FlagReaderPlugin> mPluginListener = - new PluginListener<FlagReaderPlugin>() { - public void onPluginConnected(FlagReaderPlugin plugin, Context context) { - mPlugin = plugin; - } - - public void onPluginDisconnected(FlagReaderPlugin plugin) { - mPlugin = new FlagReaderPlugin() {}; - } - }; - boolean isEnabled(BooleanFlag flag) { - return mPlugin.isEnabled(flag.getId(), flag.getDefault()); - } - - String getValue(StringFlag flag) { - return mPlugin.getValue(flag.getId(), flag.getDefault()); - } - - int getValue(IntFlag flag) { - return mPlugin.getValue(flag.getId(), flag.getDefault()); + return mFlagReader.isEnabled(flag.getId(), flag.getDefault()); } - long getValue(LongFlag flag) { - return mPlugin.getValue(flag.getId(), flag.getDefault()); + void addListener(FlagReader.Listener listener) { + mFlagReader.addListener(listener); } - float getValue(FloatFlag flag) { - return mPlugin.getValue(flag.getId(), flag.getDefault()); - } - - double getValue(DoubleFlag flag) { - return mPlugin.getValue(flag.getId(), flag.getDefault()); - } - - void addListener(FlagReaderPlugin.Listener listener) { - mPlugin.addListener(listener); - } - - void removeListener(FlagReaderPlugin.Listener listener) { - mPlugin.removeListener(listener); + void removeListener(FlagReader.Listener listener) { + mFlagReader.removeListener(listener); } /** @@ -172,6 +143,23 @@ public class FeatureFlagReader { } } + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + ArrayList<String> flagStrings = new ArrayList<>(mCachedFlags.size()); + for (int i = 0; i < mCachedFlags.size(); i++) { + int key = mCachedFlags.keyAt(i); + // get the object by the key. + CachedFlag flag = mCachedFlags.get(key); + flagStrings.add(" " + RESNAME_PREFIX + flag.name + ": " + flag.value + "\n"); + } + flagStrings.sort(String.CASE_INSENSITIVE_ORDER); + pw.println("AreFlagsOverrideable: " + mAreFlagsOverrideable); + pw.println("Cached FeatureFlags:"); + for (String flagString : flagStrings) { + pw.print(flagString); + } + } + private static class CachedFlag { public final String name; public final boolean value; diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java index b17041bc83f2..947a39a32365 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java @@ -22,7 +22,6 @@ import android.util.FeatureFlagUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.plugins.FlagReaderPlugin; import java.util.ArrayList; import java.util.HashMap; @@ -34,7 +33,7 @@ import javax.inject.Inject; /** * Class to manage simple DeviceConfig-based feature flags. * - * See {@link FeatureFlagReader} for instructions on defining and flipping flags. + * See {@link Flags} for instructions on defining new flags. */ @SysUISingleton public class FeatureFlags { @@ -51,7 +50,7 @@ public class FeatureFlags { flagReader.addListener(mListener); } - private final FlagReaderPlugin.Listener mListener = id -> { + private final FlagReader.Listener mListener = id -> { if (mListeners.containsKey(id) && mFlagMap.containsKey(id)) { mListeners.get(id).forEach(listener -> listener.onFlagChanged(mFlagMap.get(id))); } @@ -71,44 +70,7 @@ public class FeatureFlags { } /** - * @param flag The {@link StringFlag} of interest. - * @return The value of the flag. - */ - public String getValue(StringFlag flag) { - return mFlagReader.getValue(flag); - } - - /** * @param flag The {@link IntFlag} of interest. - * @return The value of the flag. - */ - public int getValue(IntFlag flag) { - return mFlagReader.getValue(flag); - } - - /** - * @param flag The {@link LongFlag} of interest. - * @return The value of the flag. - */ - public long getValue(LongFlag flag) { - return mFlagReader.getValue(flag); - } - - /** - * @param flag The {@link FloatFlag} of interest. - * @return The value of the flag. - */ - public float getValue(FloatFlag flag) { - return mFlagReader.getValue(flag); - } - - /** - * @param flag The {@link DoubleFlag} of interest. - * @return The value of the flag. - */ - public double getValue(DoubleFlag flag) { - return mFlagReader.getValue(flag); - } /** Add a listener for a specific flag. */ public void addFlagListener(Flag<?> flag, Listener listener) { @@ -125,66 +87,70 @@ public class FeatureFlags { } public boolean isNewNotifPipelineEnabled() { - return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2); + return isEnabled(Flags.NEW_NOTIFICATION_PIPELINE); } public boolean isNewNotifPipelineRenderingEnabled() { - return mFlagReader.isEnabled(R.bool.flag_notification_pipeline2_rendering); - } - - public boolean isKeyguardLayoutEnabled() { - return mFlagReader.isEnabled(R.bool.flag_keyguard_layout); + return isEnabled(Flags.NEW_NOTIFICATION_PIPELINE_RENDERING); } /** */ public boolean useNewLockscreenAnimations() { - return mFlagReader.isEnabled(R.bool.flag_lockscreen_animations); + return isEnabled(Flags.LOCKSCREEN_ANIMATIONS); } public boolean isPeopleTileEnabled() { + // TODO(b/202860494): different resource overlays have different values. return mFlagReader.isEnabled(R.bool.flag_conversations); } public boolean isMonetEnabled() { + // TODO(b/202860494): used in wallpaper picker. Always true, maybe delete. return mFlagReader.isEnabled(R.bool.flag_monet); } public boolean isPMLiteEnabled() { - return mFlagReader.isEnabled(R.bool.flag_pm_lite); + return isEnabled(Flags.POWER_MENU_LITE); } public boolean isChargingRippleEnabled() { + // TODO(b/202860494): different resource overlays have different values. return mFlagReader.isEnabled(R.bool.flag_charging_ripple); } public boolean isOngoingCallStatusBarChipEnabled() { - return mFlagReader.isEnabled(R.bool.flag_ongoing_call_status_bar_chip); + return isEnabled(Flags.ONGOING_CALL_STATUS_BAR_CHIP); } public boolean isOngoingCallInImmersiveEnabled() { - return isOngoingCallStatusBarChipEnabled() - && mFlagReader.isEnabled(R.bool.flag_ongoing_call_in_immersive); + return isOngoingCallStatusBarChipEnabled() && isEnabled(Flags.ONGOING_CALL_IN_IMMERSIVE); + } + + public boolean isOngoingCallInImmersiveChipTapEnabled() { + return isOngoingCallInImmersiveEnabled() + && isEnabled(Flags.ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP); } public boolean isSmartspaceEnabled() { + // TODO(b/202860494): different resource overlays have different values. return mFlagReader.isEnabled(R.bool.flag_smartspace); } public boolean isSmartspaceDedupingEnabled() { - return isSmartspaceEnabled() && mFlagReader.isEnabled(R.bool.flag_smartspace_deduping); + return isSmartspaceEnabled() && isEnabled(Flags.SMARTSPACE_DEDUPING); } public boolean isNewKeyguardSwipeAnimationEnabled() { - return mFlagReader.isEnabled(R.bool.flag_new_unlock_swipe_animation); + return isEnabled(Flags.NEW_UNLOCK_SWIPE_ANIMATION); } public boolean isSmartSpaceSharedElementTransitionEnabled() { - return mFlagReader.isEnabled(R.bool.flag_smartspace_shared_element_transition); + return isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED); } /** Whether or not to use the provider model behavior for the status bar icons */ public boolean isCombinedStatusBarSignalIconsEnabled() { - return mFlagReader.isEnabled(R.bool.flag_combined_status_bar_signal_icons); + return isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS); } /** System setting for provider model behavior */ @@ -192,6 +158,13 @@ public class FeatureFlags { return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL); } + /** + * Use the new version of the user switcher + */ + public boolean useNewUserSwitcher() { + return isEnabled(Flags.NEW_USER_SWITCHER); + } + /** static method for the system setting */ public static boolean isProviderModelSettingEnabled(Context context) { return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL); diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java b/packages/SystemUI/src/com/android/systemui/flags/FlagReader.java index ab1749978653..1ae8c1f2a712 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FlagReaderPlugin.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagReader.java @@ -14,49 +14,18 @@ * limitations under the License. */ -package com.android.systemui.plugins; - -import com.android.systemui.plugins.annotations.ProvidesInterface; +package com.android.systemui.flags; /** - * Plugin for loading flag values from an alternate source of truth. + * Plugin for loading flag values */ -@ProvidesInterface(action = FlagReaderPlugin.ACTION, version = FlagReaderPlugin.VERSION) -public interface FlagReaderPlugin extends Plugin { - int VERSION = 1; - String ACTION = "com.android.systemui.flags.FLAG_READER_PLUGIN"; - +public interface FlagReader { /** Returns a boolean value for the given flag. */ default boolean isEnabled(int id, boolean def) { return def; } - /** Returns a string value for the given flag id. */ - default String getValue(int id, String def) { - return def; - } - - /** Returns a int value for the given flag. */ - default int getValue(int id, int def) { - return def; - } - - /** Returns a long value for the given flag. */ - default long getValue(int id, long def) { - return def; - } - - /** Returns a float value for the given flag. */ - default float getValue(int id, float def) { - return def; - } - - /** Returns a double value for the given flag. */ - default double getValue(int id, double def) { - return def; - } - /** Add a listener to be alerted when any flag changes. */ default void addListener(Listener listener) {} diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagWriter.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagWriter.kt new file mode 100644 index 000000000000..bacc66b39c58 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagWriter.kt @@ -0,0 +1,21 @@ +/* + * 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.flags + +interface FlagWriter { + fun setEnabled(key: Int, value: Boolean) {} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt index 6561bd5a5323..1dc5a9f3adc5 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt @@ -26,11 +26,19 @@ import javax.inject.Inject */ @SysUISingleton open class SystemPropertiesHelper @Inject constructor() { + fun get(name: String): String { + return SystemProperties.get(name) + } + fun getBoolean(name: String, default: Boolean): Boolean { return SystemProperties.getBoolean(name, default) } + fun set(name: String, value: String) { + SystemProperties.set(name, value) + } + fun set(name: String, value: Int) { - SystemProperties.set(name, value.toString()) + set(name, value.toString()) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java deleted file mode 100644 index df6aa348ed84..000000000000 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ /dev/null @@ -1,599 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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.globalactions; - -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; - -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.annotation.Nullable; -import android.app.IActivityManager; -import android.app.PendingIntent; -import android.app.admin.DevicePolicyManager; -import android.app.trust.TrustManager; -import android.content.Context; -import android.content.DialogInterface; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.database.ContentObserver; -import android.graphics.drawable.Drawable; -import android.media.AudioManager; -import android.os.Handler; -import android.os.UserManager; -import android.os.Vibrator; -import android.provider.Settings; -import android.service.dreams.IDreamManager; -import android.telecom.TelecomManager; -import android.transition.AutoTransition; -import android.transition.TransitionManager; -import android.transition.TransitionSet; -import android.view.IWindowManager; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.widget.FrameLayout; -import android.widget.TextView; - -import androidx.lifecycle.LifecycleOwner; - -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.view.RotationPolicy; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.animation.Interpolators; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.model.SysUiState; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; -import com.android.systemui.plugins.GlobalActionsPanelPlugin; -import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.telephony.TelephonyListenerManager; -import com.android.systemui.util.RingerModeTracker; -import com.android.systemui.util.leak.RotationUtils; -import com.android.systemui.util.settings.GlobalSettings; -import com.android.systemui.util.settings.SecureSettings; - -import java.util.Optional; -import java.util.concurrent.Executor; - -import javax.inject.Inject; -import javax.inject.Provider; - -/** - * Helper to show the global actions dialog. Each item is an {@link Action} that may show depending - * on whether the keyguard is showing, and whether the device is provisioned. - * This version includes wallet. - */ -public class GlobalActionsDialog extends GlobalActionsDialogLite - implements DialogInterface.OnDismissListener, - DialogInterface.OnShowListener, - ConfigurationController.ConfigurationListener, - GlobalActionsPanelPlugin.Callbacks, - LifecycleOwner { - - private static final String TAG = "GlobalActionsDialog"; - - private final LockPatternUtils mLockPatternUtils; - private final KeyguardStateController mKeyguardStateController; - private final SysUiState mSysUiState; - private final ActivityStarter mActivityStarter; - private final SysuiColorExtractor mSysuiColorExtractor; - private final IStatusBarService mStatusBarService; - private final NotificationShadeWindowController mNotificationShadeWindowController; - private GlobalActionsPanelPlugin mWalletPlugin; - - @VisibleForTesting - boolean mShowLockScreenCards = false; - - private final KeyguardStateController.Callback mKeyguardStateControllerListener = - new KeyguardStateController.Callback() { - @Override - public void onUnlockedChanged() { - if (mDialog != null) { - ActionsDialog dialog = (ActionsDialog) mDialog; - boolean unlocked = mKeyguardStateController.isUnlocked(); - if (dialog.mWalletViewController != null) { - dialog.mWalletViewController.onDeviceLockStateChanged(!unlocked); - } - - if (unlocked) { - dialog.hideLockMessage(); - } - } - } - }; - - private final ContentObserver mSettingsObserver = new ContentObserver(mMainHandler) { - @Override - public void onChange(boolean selfChange) { - onPowerMenuLockScreenSettingsChanged(); - } - }; - - /** - * @param context everything needs a context :( - */ - @Inject - public GlobalActionsDialog( - Context context, - GlobalActionsManager windowManagerFuncs, - AudioManager audioManager, - IDreamManager iDreamManager, - DevicePolicyManager devicePolicyManager, - LockPatternUtils lockPatternUtils, - BroadcastDispatcher broadcastDispatcher, - TelephonyListenerManager telephonyListenerManager, - GlobalSettings globalSettings, - SecureSettings secureSettings, - @Nullable Vibrator vibrator, - @Main Resources resources, - ConfigurationController configurationController, - ActivityStarter activityStarter, - KeyguardStateController keyguardStateController, - UserManager userManager, - TrustManager trustManager, - IActivityManager iActivityManager, - @Nullable TelecomManager telecomManager, - MetricsLogger metricsLogger, - SysuiColorExtractor colorExtractor, - IStatusBarService statusBarService, - NotificationShadeWindowController notificationShadeWindowController, - IWindowManager iWindowManager, - @Background Executor backgroundExecutor, - UiEventLogger uiEventLogger, - RingerModeTracker ringerModeTracker, - SysUiState sysUiState, - @Main Handler handler, - PackageManager packageManager, - Optional<StatusBar> statusBarOptional, - KeyguardUpdateMonitor keyguardUpdateMonitor) { - - super(context, - windowManagerFuncs, - audioManager, - iDreamManager, - devicePolicyManager, - lockPatternUtils, - broadcastDispatcher, - telephonyListenerManager, - globalSettings, - secureSettings, - vibrator, - resources, - configurationController, - keyguardStateController, - userManager, - trustManager, - iActivityManager, - telecomManager, - metricsLogger, - colorExtractor, - statusBarService, - notificationShadeWindowController, - iWindowManager, - backgroundExecutor, - uiEventLogger, - ringerModeTracker, - sysUiState, - handler, - packageManager, - statusBarOptional, - keyguardUpdateMonitor); - - mLockPatternUtils = lockPatternUtils; - mKeyguardStateController = keyguardStateController; - mSysuiColorExtractor = colorExtractor; - mStatusBarService = statusBarService; - mNotificationShadeWindowController = notificationShadeWindowController; - mSysUiState = sysUiState; - mActivityStarter = activityStarter; - - mKeyguardStateController.addCallback(mKeyguardStateControllerListener); - - // Listen for changes to show pay on the power menu while locked - onPowerMenuLockScreenSettingsChanged(); - mGlobalSettings.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), - false /* notifyForDescendants */, - mSettingsObserver); - } - - @Override - public void destroy() { - super.destroy(); - mKeyguardStateController.removeCallback(mKeyguardStateControllerListener); - mGlobalSettings.unregisterContentObserver(mSettingsObserver); - } - - /** - * Show the global actions dialog (creating if necessary) - * - * @param keyguardShowing True if keyguard is showing - */ - public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned, - GlobalActionsPanelPlugin walletPlugin) { - mWalletPlugin = walletPlugin; - super.showOrHideDialog(keyguardShowing, isDeviceProvisioned); - } - - /** - * Returns the maximum number of power menu items to show based on which GlobalActions - * layout is being used. - */ - @VisibleForTesting - @Override - protected int getMaxShownPowerItems() { - return getContext().getResources().getInteger( - com.android.systemui.R.integer.power_menu_max_columns); - } - - /** - * Create the global actions dialog. - * - * @return A new dialog. - */ - @Override - protected ActionsDialogLite createDialog() { - initDialogItems(); - - ActionsDialog dialog = new ActionsDialog(getContext(), mAdapter, mOverflowAdapter, - this::getWalletViewController, mSysuiColorExtractor, - mStatusBarService, mNotificationShadeWindowController, - mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger(), - getStatusBar(), getKeyguardUpdateMonitor(), mLockPatternUtils); - - if (shouldShowLockMessage(dialog)) { - dialog.showLockMessage(); - } - dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. - dialog.setOnDismissListener(this); - dialog.setOnShowListener(this); - - return dialog; - } - - @Nullable - private GlobalActionsPanelPlugin.PanelViewController getWalletViewController() { - if (mWalletPlugin == null) { - return null; - } - return mWalletPlugin.onPanelShown(this, !mKeyguardStateController.isUnlocked()); - } - - /** - * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is - * called when the quick access wallet requests that an intent be started (with lock screen - * shown first if needed). - */ - @Override - public void startPendingIntentDismissingKeyguard(PendingIntent pendingIntent) { - mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent); - } - - @Override - protected int getEmergencyTextColor(Context context) { - return context.getResources().getColor( - com.android.systemui.R.color.global_actions_emergency_text); - } - - @Override - protected int getEmergencyIconColor(Context context) { - return getContext().getResources().getColor( - com.android.systemui.R.color.global_actions_emergency_text); - } - - @Override - protected int getEmergencyBackgroundColor(Context context) { - return getContext().getResources().getColor( - com.android.systemui.R.color.global_actions_emergency_background); - } - - @Override - protected int getGridItemLayoutResource() { - return com.android.systemui.R.layout.global_actions_grid_item_v2; - } - - @VisibleForTesting - static class ActionsDialog extends ActionsDialogLite { - - private final Provider<GlobalActionsPanelPlugin.PanelViewController> mWalletFactory; - @Nullable private GlobalActionsPanelPlugin.PanelViewController mWalletViewController; - private ResetOrientationData mResetOrientationData; - @VisibleForTesting ViewGroup mLockMessageContainer; - private TextView mLockMessage; - - ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter, - Provider<GlobalActionsPanelPlugin.PanelViewController> walletFactory, - SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, - NotificationShadeWindowController notificationShadeWindowController, - SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing, - MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger, - Optional<StatusBar> statusBarOptional, KeyguardUpdateMonitor keyguardUpdateMonitor, - LockPatternUtils lockPatternUtils) { - super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions, - adapter, overflowAdapter, sysuiColorExtractor, statusBarService, - notificationShadeWindowController, sysuiState, onRotateCallback, - keyguardShowing, powerAdapter, uiEventLogger, statusBarOptional, - keyguardUpdateMonitor, lockPatternUtils); - mWalletFactory = walletFactory; - - // Update window attributes - Window window = getWindow(); - window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - window.setLayout(MATCH_PARENT, MATCH_PARENT); - window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); - window.addFlags( - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR - | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED - | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); - setTitle(R.string.global_actions); - initializeLayout(); - } - - private boolean isWalletViewAvailable() { - return mWalletViewController != null && mWalletViewController.getPanelContent() != null; - } - - private void initializeWalletView() { - if (mWalletFactory == null) { - return; - } - mWalletViewController = mWalletFactory.get(); - if (!isWalletViewAvailable()) { - return; - } - - boolean isLandscapeWalletViewShown = mContext.getResources().getBoolean( - com.android.systemui.R.bool.global_actions_show_landscape_wallet_view); - - int rotation = RotationUtils.getRotation(mContext); - boolean rotationLocked = RotationPolicy.isRotationLocked(mContext); - if (rotation != RotationUtils.ROTATION_NONE) { - if (rotationLocked) { - if (mResetOrientationData == null) { - mResetOrientationData = new ResetOrientationData(); - mResetOrientationData.locked = true; - mResetOrientationData.rotation = rotation; - } - - // Unlock rotation, so user can choose to rotate to portrait to see the panel. - // This call is posted so that the rotation does not change until post-layout, - // otherwise onConfigurationChanged() may not get invoked. - mGlobalActionsLayout.post(() -> - RotationPolicy.setRotationLockAtAngle( - mContext, false, RotationUtils.ROTATION_NONE)); - - if (!isLandscapeWalletViewShown) { - return; - } - } - } else { - if (!rotationLocked) { - if (mResetOrientationData == null) { - mResetOrientationData = new ResetOrientationData(); - mResetOrientationData.locked = false; - } - } - - boolean shouldLockRotation = !isLandscapeWalletViewShown; - if (rotationLocked != shouldLockRotation) { - // Locks the screen to portrait if the landscape / seascape orientation does not - // show the wallet view, so the user doesn't accidentally hide the panel. - // This call is posted so that the rotation does not change until post-layout, - // otherwise onConfigurationChanged() may not get invoked. - mGlobalActionsLayout.post(() -> - RotationPolicy.setRotationLockAtAngle( - mContext, shouldLockRotation, RotationUtils.ROTATION_NONE)); - } - } - - // Disable rotation suggestions, if enabled - setRotationSuggestionsEnabled(false); - - FrameLayout panelContainer = - findViewById(com.android.systemui.R.id.global_actions_wallet); - FrameLayout.LayoutParams panelParams = - new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, - FrameLayout.LayoutParams.MATCH_PARENT); - panelParams.topMargin = mContext.getResources().getDimensionPixelSize( - com.android.systemui.R.dimen.global_actions_wallet_top_margin); - View walletView = mWalletViewController.getPanelContent(); - panelContainer.addView(walletView, panelParams); - // Smooth transitions when wallet is resized, which can happen when a card is added - ViewGroup root = findViewById(com.android.systemui.R.id.global_actions_grid_root); - if (root != null) { - walletView.addOnLayoutChangeListener((v, l, t, r, b, ol, ot, or, ob) -> { - int oldHeight = ob - ot; - int newHeight = b - t; - if (oldHeight > 0 && oldHeight != newHeight) { - TransitionSet transition = new AutoTransition() - .setDuration(250) - .setOrdering(TransitionSet.ORDERING_TOGETHER); - TransitionManager.beginDelayedTransition(root, transition); - } - }); - } - } - - @Override - protected int getLayoutResource() { - return com.android.systemui.R.layout.global_actions_grid_v2; - } - - @Override - protected void initializeLayout() { - super.initializeLayout(); - mLockMessageContainer = requireViewById( - com.android.systemui.R.id.global_actions_lock_message_container); - mLockMessage = requireViewById(com.android.systemui.R.id.global_actions_lock_message); - initializeWalletView(); - getWindow().setBackgroundDrawable(mBackgroundDrawable); - } - - @Override - protected void showDialog() { - mShowing = true; - mNotificationShadeWindowController.setRequestTopUi(true, TAG); - mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true) - .commitUpdate(mContext.getDisplayId()); - - ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView(); - root.setOnApplyWindowInsetsListener((v, windowInsets) -> { - root.setPadding(windowInsets.getStableInsetLeft(), - windowInsets.getStableInsetTop(), - windowInsets.getStableInsetRight(), - windowInsets.getStableInsetBottom()); - return WindowInsets.CONSUMED; - }); - - mBackgroundDrawable.setAlpha(0); - float xOffset = mGlobalActionsLayout.getAnimationOffsetX(); - ObjectAnimator alphaAnimator = - ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f); - alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - alphaAnimator.setDuration(183); - alphaAnimator.addUpdateListener((animation) -> { - float animatedValue = animation.getAnimatedFraction(); - int alpha = (int) (animatedValue * mScrimAlpha * 255); - mBackgroundDrawable.setAlpha(alpha); - }); - - ObjectAnimator xAnimator = - ObjectAnimator.ofFloat(mContainer, "translationX", xOffset, 0f); - xAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - xAnimator.setDuration(350); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(alphaAnimator, xAnimator); - animatorSet.start(); - } - - @Override - protected void dismissInternal() { - super.dismissInternal(); - } - - @Override - protected void completeDismiss() { - dismissWallet(); - resetOrientation(); - super.completeDismiss(); - } - - private void dismissWallet() { - if (mWalletViewController != null) { - mWalletViewController.onDismissed(); - // The wallet controller should not be re-used after being dismissed. - mWalletViewController = null; - } - } - - private void resetOrientation() { - if (mResetOrientationData != null) { - RotationPolicy.setRotationLockAtAngle(mContext, mResetOrientationData.locked, - mResetOrientationData.rotation); - } - setRotationSuggestionsEnabled(true); - } - - @Override - public void refreshDialog() { - // ensure dropdown menus are dismissed before re-initializing the dialog - dismissWallet(); - super.refreshDialog(); - } - - void hideLockMessage() { - if (mLockMessageContainer.getVisibility() == View.VISIBLE) { - mLockMessageContainer.animate().alpha(0).setDuration(150).setListener( - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mLockMessageContainer.setVisibility(View.GONE); - } - }).start(); - } - } - - void showLockMessage() { - Drawable lockIcon = mContext.getDrawable(com.android.internal.R.drawable.ic_lock); - lockIcon.setTint(mContext.getColor(com.android.systemui.R.color.control_primary_text)); - mLockMessage.setCompoundDrawablesWithIntrinsicBounds(null, lockIcon, null, null); - mLockMessageContainer.setVisibility(View.VISIBLE); - } - - private static class ResetOrientationData { - public boolean locked; - public int rotation; - } - } - - /** - * Determines whether or not debug mode has been activated for the Global Actions Panel. - */ - private static boolean isPanelDebugModeEnabled(Context context) { - return Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.GLOBAL_ACTIONS_PANEL_DEBUG_ENABLED, 0) == 1; - } - - /** - * Determines whether or not the Global Actions menu should be forced to use the newer - * grid-style layout. - */ - private static boolean isForceGridEnabled(Context context) { - return isPanelDebugModeEnabled(context); - } - - private boolean shouldShowLockMessage(ActionsDialog dialog) { - return isWalletAvailableAfterUnlock(dialog); - } - - // Temporary while we move items out of the power menu - private boolean isWalletAvailableAfterUnlock(ActionsDialog dialog) { - boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id) - == STRONG_AUTH_REQUIRED_AFTER_BOOT; - return !mKeyguardStateController.isUnlocked() - && (!mShowLockScreenCards || isLockedAfterBoot) - && dialog.isWalletViewAvailable(); - } - - private void onPowerMenuLockScreenSettingsChanged() { - mShowLockScreenCards = mSecureSettings.getInt( - Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java index a51ec54ebcce..729730cdf6f1 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java @@ -66,6 +66,13 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer { mOnBitmapUpdated = c; } + /** + * @hide + */ + public void use(Consumer<Bitmap> c) { + mTexture.use(c); + } + @Override public boolean isWcgContent() { return mTexture.isWcgContent(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index a5dd6a11c388..01a0f271e5b9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -102,7 +102,7 @@ public class KeyguardService extends Service { "persist.wm.enable_remote_keyguard_animation"; private static final int sEnableRemoteKeyguardAnimation = - SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 0); + SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1); /** * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY @@ -358,7 +358,7 @@ public class KeyguardService extends Service { if (transit == TRANSIT_OLD_KEYGUARD_OCCLUDE) { mBinder.setOccluded(true /* isOccluded */, true /* animate */); } else if (transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE) { - mBinder.setOccluded(false /* isOccluded */, true /* animate */); + mBinder.setOccluded(false /* isOccluded */, false /* animate */); } // TODO(bc-unlock): Implement (un)occlude animation. finishedCallback.onAnimationFinished(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index e51b60213446..2cc564bf8452 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -62,7 +62,7 @@ const val SURFACE_BEHIND_SCALE_PIVOT_Y = 0.66f * The dismiss amount is the inverse of the notification panel expansion, which decreases as the * lock screen is swiped away. */ -const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.1f +const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.25f /** * Dismiss amount at which to complete the keyguard exit animation and hide the keyguard. @@ -70,7 +70,7 @@ const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.1f * The dismiss amount is the inverse of the notification panel expansion, which decreases as the * lock screen is swiped away. */ -const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.3f +const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.4f /** * Initiates, controls, and ends the keyguard unlock animation. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index c5694398cdc6..19ee50ac99cd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -119,12 +119,15 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation; +import com.android.systemui.unfold.config.UnfoldTransitionConfig; import com.android.systemui.util.DeviceConfigProxy; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; import dagger.Lazy; @@ -670,13 +673,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, } } } - - @Override - public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { - synchronized (KeyguardViewMediator.this) { - notifyHasLockscreenWallpaperChanged(hasLockscreenWallpaper); - } - } }; ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() { @@ -815,6 +811,10 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private DeviceConfigProxy mDeviceConfig; private DozeParameters mDozeParameters; + private final UnfoldTransitionConfig mUnfoldTransitionConfig; + private final Lazy<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealAnimation; + private final AtomicInteger mPendingDrawnTasks = new AtomicInteger(); + private final KeyguardStateController mKeyguardStateController; private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy; private boolean mWallpaperSupportsAmbientMode; @@ -837,6 +837,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, NavigationModeController navigationModeController, KeyguardDisplayManager keyguardDisplayManager, DozeParameters dozeParameters, + UnfoldTransitionConfig unfoldTransitionConfig, + Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation, SysuiStatusBarStateController statusBarStateController, KeyguardStateController keyguardStateController, Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy, @@ -870,6 +872,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode); })); mDozeParameters = dozeParameters; + mUnfoldTransitionConfig = unfoldTransitionConfig; + mUnfoldLightRevealAnimation = unfoldLightRevealOverlayAnimation; mStatusBarStateController = statusBarStateController; statusBarStateController.addCallback(this); @@ -2552,6 +2556,24 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurningOn"); synchronized (KeyguardViewMediator.this) { if (DEBUG) Log.d(TAG, "handleNotifyScreenTurningOn"); + + if (mUnfoldTransitionConfig.isEnabled()) { + mPendingDrawnTasks.set(2); // unfold overlay and keyguard drawn + + mUnfoldLightRevealAnimation.get() + .onScreenTurningOn(() -> { + if (mPendingDrawnTasks.decrementAndGet() == 0) { + try { + callback.onDrawn(); + } catch (RemoteException e) { + Slog.w(TAG, "Exception calling onDrawn():", e); + } + } + }); + } else { + mPendingDrawnTasks.set(1); // only keyguard drawn + } + mKeyguardViewControllerLazy.get().onScreenTurningOn(); if (callback != null) { if (mWakeAndUnlocking) { @@ -2582,10 +2604,12 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private void notifyDrawn(final IKeyguardDrawnCallback callback) { Trace.beginSection("KeyguardViewMediator#notifyDrawn"); - try { - callback.onDrawn(); - } catch (RemoteException e) { - Slog.w(TAG, "Exception calling onDrawn():", e); + if (mPendingDrawnTasks.decrementAndGet() == 0) { + try { + callback.onDrawn(); + } catch (RemoteException e) { + Slog.w(TAG, "Exception calling onDrawn():", e); + } } Trace.endSection(); } @@ -2738,6 +2762,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, pw.print(" mHideAnimationRun: "); pw.println(mHideAnimationRun); pw.print(" mPendingReset: "); pw.println(mPendingReset); pw.print(" mPendingLock: "); pw.println(mPendingLock); + pw.print(" mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.get()); pw.print(" mWakeAndUnlocking: "); pw.println(mWakeAndUnlocking); pw.print(" mDrawnCallback: "); pw.println(mDrawnCallback); } @@ -2867,21 +2892,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, } } - private void notifyHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { - int size = mKeyguardStateCallbacks.size(); - for (int i = size - 1; i >= 0; i--) { - try { - mKeyguardStateCallbacks.get(i).onHasLockscreenWallpaperChanged( - hasLockscreenWallpaper); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to call onHasLockscreenWallpaperChanged", e); - if (e instanceof DeadObjectException) { - mKeyguardStateCallbacks.remove(i); - } - } - } - } - public void addStateMonitorCallback(IKeyguardStateCallback callback) { synchronized (this) { mKeyguardStateCallbacks.add(callback); @@ -2891,7 +2901,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, callback.onInputRestrictedStateChanged(mInputRestricted); callback.onTrustedChanged(mUpdateMonitor.getUserHasTrust( KeyguardUpdateMonitor.getCurrentUser())); - callback.onHasLockscreenWallpaperChanged(mUpdateMonitor.hasLockscreenWallpaper()); } catch (RemoteException e) { Slog.w(TAG, "Failed to call to IKeyguardStateCallback", e); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 8a383b974d77..11d4aac9dc27 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -55,6 +55,8 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation; +import com.android.systemui.unfold.config.UnfoldTransitionConfig; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.settings.GlobalSettings; @@ -99,6 +101,8 @@ public class KeyguardModule { NavigationModeController navigationModeController, KeyguardDisplayManager keyguardDisplayManager, DozeParameters dozeParameters, + UnfoldTransitionConfig unfoldTransitionConfig, + Lazy<UnfoldLightRevealOverlayAnimation> unfoldLightRevealOverlayAnimation, SysuiStatusBarStateController statusBarStateController, KeyguardStateController keyguardStateController, Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController, @@ -121,6 +125,8 @@ public class KeyguardModule { navigationModeController, keyguardDisplayManager, dozeParameters, + unfoldTransitionConfig, + unfoldLightRevealOverlayAnimation, statusBarStateController, keyguardStateController, keyguardUnlockAnimationController, 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 84c5a571c857..72601e9816f9 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -111,6 +111,17 @@ public class LogModule { return factory.create("CollapsedSbFragmentLog", 20); } + /** + * Provides a logging buffer for logs related to swiping away the status bar while in immersive + * mode. See {@link com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureLogger}. + */ + @Provides + @SysUISingleton + @SwipeStatusBarAwayLog + public static LogBuffer provideSwipeAwayGestureLogBuffer(LogBufferFactory factory) { + return factory.create("SwipeStatusBarAwayLog", 30); + } + /** 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/log/dagger/SwipeStatusBarAwayLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java new file mode 100644 index 000000000000..dd6837563a74 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/SwipeStatusBarAwayLog.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.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.gesture.SwipeStatusBarAwayGestureLogger}. + */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface SwipeStatusBarAwayLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 0e70945be225..e87558ebee27 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -148,7 +148,7 @@ class MediaCarouselController @Inject constructor( inflateSettingsButton() } - override fun onOverlayChanged() { + override fun onThemeChanged() { recreatePlayers() inflateSettingsButton() } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 424f80177d9c..e73eb66a51bf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -55,6 +55,7 @@ import com.android.systemui.animation.GhostedViewLaunchAnimatorController; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.util.animation.TransitionLayout; @@ -119,6 +120,7 @@ public class MediaControlPanel { private int mSmartspaceMediaItemsCount; private MediaCarouselController mMediaCarouselController; private final MediaOutputDialogFactory mMediaOutputDialogFactory; + private final FalsingManager mFalsingManager; /** * Initialize a new control panel @@ -131,7 +133,8 @@ public class MediaControlPanel { ActivityStarter activityStarter, MediaViewController mediaViewController, SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager, KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory - mediaOutputDialogFactory, MediaCarouselController mediaCarouselController) { + mediaOutputDialogFactory, MediaCarouselController mediaCarouselController, + FalsingManager falsingManager) { mContext = context; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; @@ -141,6 +144,7 @@ public class MediaControlPanel { mKeyguardDismissUtil = keyguardDismissUtil; mMediaOutputDialogFactory = mediaOutputDialogFactory; mMediaCarouselController = mediaCarouselController; + mFalsingManager = falsingManager; loadDimens(); mSeekBarViewModel.setLogSmartspaceClick(() -> { @@ -235,10 +239,14 @@ public class MediaControlPanel { } }); mPlayerViewHolder.getCancel().setOnClickListener(v -> { - closeGuts(); + if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + closeGuts(); + } }); mPlayerViewHolder.getSettings().setOnClickListener(v -> { - mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */); + if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */); + } }); } @@ -259,10 +267,14 @@ public class MediaControlPanel { } }); mRecommendationViewHolder.getCancel().setOnClickListener(v -> { - closeGuts(); + if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + closeGuts(); + } }); mRecommendationViewHolder.getSettings().setOnClickListener(v -> { - mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */); + if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */); + } }); } @@ -299,6 +311,7 @@ public class MediaControlPanel { PendingIntent clickIntent = data.getClickIntent(); if (clickIntent != null) { mPlayerViewHolder.getPlayer().setOnClickListener(v -> { + if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return; if (mMediaViewController.isGutsVisible()) return; logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK @@ -365,8 +378,12 @@ public class MediaControlPanel { setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */); setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */); seamlessView.setOnClickListener( - v -> mMediaOutputDialogFactory.create(data.getPackageName(), true, - mPlayerViewHolder.getSeamlessButton())); + v -> { + if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + mMediaOutputDialogFactory.create(data.getPackageName(), true, + mPlayerViewHolder.getSeamlessButton()); + } + }); ImageView iconView = mPlayerViewHolder.getSeamlessIcon(); TextView deviceName = mPlayerViewHolder.getSeamlessText(); @@ -417,9 +434,11 @@ public class MediaControlPanel { } else { button.setEnabled(true); button.setOnClickListener(v -> { - logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK - /* isRecommendationCard */ false); - action.run(); + if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK + /* isRecommendationCard */ false); + action.run(); + } }); } boolean visibleInCompat = actionsWhenCollapsed.contains(i); @@ -451,6 +470,8 @@ public class MediaControlPanel { mPlayerViewHolder.getDismissLabel().setAlpha(isDismissible ? 1 : DISABLED_ALPHA); mPlayerViewHolder.getDismiss().setEnabled(isDismissible); mPlayerViewHolder.getDismiss().setOnClickListener(v -> { + if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return; + logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS /* isRecommendationCard */ false); @@ -633,6 +654,8 @@ public class MediaControlPanel { mSmartspaceMediaItemsCount = uiComponentIndex; // Set up long press to show guts setting panel. mRecommendationViewHolder.getDismiss().setOnClickListener(v -> { + if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return; + logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS /* isRecommendationCard */ true); closeGuts(); @@ -788,6 +811,8 @@ public class MediaControlPanel { } view.setOnClickListener(v -> { + if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return; + logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK /* isRecommendationCard */ true, interactedSubcardRank, diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt index f17ad6f09128..33ef19ad040f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt @@ -50,6 +50,7 @@ class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarVi holder.seekBar.setProgress(0) holder.elapsedTimeView.setText("") holder.totalTimeView.setText("") + holder.seekBar.contentDescription = "" return } @@ -61,16 +62,22 @@ class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarVi setVerticalPadding(seekBarEnabledVerticalPadding) } - data.duration?.let { - holder.seekBar.setMax(it) - holder.totalTimeView.setText(DateUtils.formatElapsedTime( - it / DateUtils.SECOND_IN_MILLIS)) - } + holder.seekBar.setMax(data.duration) + val totalTimeString = DateUtils.formatElapsedTime( + data.duration / DateUtils.SECOND_IN_MILLIS) + holder.totalTimeView.setText(totalTimeString) data.elapsedTime?.let { holder.seekBar.setProgress(it) - holder.elapsedTimeView.setText(DateUtils.formatElapsedTime( - it / DateUtils.SECOND_IN_MILLIS)) + val elapsedTimeString = DateUtils.formatElapsedTime( + it / DateUtils.SECOND_IN_MILLIS) + holder.elapsedTimeView.setText(elapsedTimeString) + + holder.seekBar.contentDescription = holder.seekBar.context.getString( + R.string.controls_media_seekbar_description, + elapsedTimeString, + totalTimeString + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index d1b6548132a6..125b87b31e63 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -135,14 +135,11 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { if (currentlyConnected && mController.isActiveRemoteDevice(device) && mController.getSelectableMediaDevice().size() > 0) { // Init active device layout - mDivider.setVisibility(View.VISIBLE); - mDivider.setTransitionAlpha(1); mAddIcon.setVisibility(View.VISIBLE); mAddIcon.setTransitionAlpha(1); mAddIcon.setOnClickListener(this::onEndItemClick); } else { // Init non-active device layout - mDivider.setVisibility(View.GONE); mAddIcon.setVisibility(View.GONE); } if (mCurrentActivePosition == position) { @@ -181,7 +178,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { super.onBind(customizedItem, topMargin, bottomMargin); if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) { mCheckBox.setVisibility(View.GONE); - mDivider.setVisibility(View.GONE); mAddIcon.setVisibility(View.GONE); mBottomDivider.setVisibility(View.GONE); setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new), @@ -196,13 +192,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mBottomDivider.setVisibility(View.GONE); mCheckBox.setVisibility(View.GONE); if (mController.getSelectableMediaDevice().size() > 0) { - mDivider.setVisibility(View.VISIBLE); - mDivider.setTransitionAlpha(1); mAddIcon.setVisibility(View.VISIBLE); mAddIcon.setTransitionAlpha(1); mAddIcon.setOnClickListener(this::onEndItemClick); } else { - mDivider.setVisibility(View.GONE); mAddIcon.setVisibility(View.GONE); } mTitleIcon.setImageDrawable(getSpeakerDrawable()); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 0890841eb4fb..1ffc2c4c35ba 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -121,7 +121,6 @@ public abstract class MediaOutputBaseAdapter extends final ProgressBar mProgressBar; final SeekBar mSeekBar; final RelativeLayout mTwoLineLayout; - final View mDivider; final View mBottomDivider; final CheckBox mCheckBox; private String mDeviceId; @@ -136,7 +135,6 @@ public abstract class MediaOutputBaseAdapter extends mTitleIcon = view.requireViewById(R.id.title_icon); mProgressBar = view.requireViewById(R.id.volume_indeterminate_progress); mSeekBar = view.requireViewById(R.id.volume_seekbar); - mDivider = view.requireViewById(R.id.end_divider); mBottomDivider = view.requireViewById(R.id.bottom_divider); mAddIcon = view.requireViewById(R.id.add_icon); mCheckBox = view.requireViewById(R.id.check_box); @@ -151,21 +149,12 @@ public abstract class MediaOutputBaseAdapter extends return; } mTitleIcon.setImageIcon(icon); - setMargin(topMargin, bottomMargin); }); }); } void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) { - setMargin(topMargin, bottomMargin); - } - - private void setMargin(boolean topMargin, boolean bottomMargin) { - ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mContainerLayout - .getLayoutParams(); - params.topMargin = topMargin ? mMargin : 0; - params.bottomMargin = bottomMargin ? mMargin : 0; - mContainerLayout.setLayoutParams(params); + // TODO (b/201718621): clean up method after adjustment } void setSingleLineLayout(CharSequence title, boolean bFocused) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 85d0802012ac..6895ef10fd07 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -175,7 +175,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } if (!mAdapter.isDragging() && !mAdapter.isAnimating()) { int currentActivePosition = mAdapter.getCurrentActivePosition(); - if (currentActivePosition >= 0) { + if (currentActivePosition >= 0 && currentActivePosition < mAdapter.getItemCount()) { mAdapter.notifyItemChanged(currentActivePosition); } else { mAdapter.notifyDataSetChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 437a0c85a6e0..42dd8862576b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -73,6 +73,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { private final String mPackageName; private final Context mContext; private final MediaSessionManager mMediaSessionManager; + private final LocalBluetoothManager mLocalBluetoothManager; private final ShadeController mShadeController; private final ActivityStarter mActivityStarter; private final DialogLaunchAnimator mDialogLaunchAnimator; @@ -85,7 +86,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { private MediaController mMediaController; @VisibleForTesting Callback mCallback; - Callback mPreviousCallback; @VisibleForTesting LocalMediaManager mLocalMediaManager; @@ -101,6 +101,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { mContext = context; mPackageName = packageName; mMediaSessionManager = mediaSessionManager; + mLocalBluetoothManager = lbm; mShadeController = shadeController; mActivityStarter = starter; mAboveStatusbar = aboveStatusbar; @@ -135,19 +136,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { } return; } - - if (mPreviousCallback != null) { - Log.w(TAG, - "Callback started when mPreviousCallback is not null, which is unexpected"); - mPreviousCallback.dismissDialog(); - } - - // If we start the output group dialog when the output dialog is shown, we need to keep a - // reference to the output dialog to set it back as the callback once we dismiss the output - // group dialog. - mPreviousCallback = mCallback; mCallback = cb; - mLocalMediaManager.unregisterCallback(this); mLocalMediaManager.stopScan(); mLocalMediaManager.registerCallback(this); @@ -163,15 +152,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { mLocalMediaManager.stopScan(); } mMediaDevices.clear(); - - // If there was a previous callback, i.e. we just dismissed the output group dialog and are - // now back on the output dialog, then we reset the callback to its previous value. - mCallback = null; - Callback previous = mPreviousCallback; - mPreviousCallback = null; - if (previous != null) { - start(previous); - } } @Override @@ -480,7 +460,11 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { void launchMediaOutputGroupDialog(View mediaOutputDialog) { // We show the output group dialog from the output dialog. - MediaOutputGroupDialog dialog = new MediaOutputGroupDialog(mContext, mAboveStatusbar, this); + MediaOutputController controller = new MediaOutputController(mContext, mPackageName, + mAboveStatusbar, mMediaSessionManager, mLocalBluetoothManager, mShadeController, + mActivityStarter, mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator); + MediaOutputGroupDialog dialog = new MediaOutputGroupDialog(mContext, mAboveStatusbar, + controller); mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java index 968c3506f39f..11d76dba1423 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java @@ -96,7 +96,6 @@ public class MediaOutputGroupAdapter extends MediaOutputBaseAdapter { @Override void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) { super.onBind(device, topMargin, bottomMargin, position); - mDivider.setVisibility(View.GONE); mAddIcon.setVisibility(View.GONE); mBottomDivider.setVisibility(View.GONE); mCheckBox.setVisibility(View.VISIBLE); @@ -135,7 +134,6 @@ public class MediaOutputGroupAdapter extends MediaOutputBaseAdapter { mTitleIcon.setImageDrawable(getSpeakerDrawable()); mBottomDivider.setVisibility(View.VISIBLE); mCheckBox.setVisibility(View.GONE); - mDivider.setVisibility(View.GONE); mAddIcon.setVisibility(View.GONE); initSessionSeekbar(); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 8576a280c560..7809b5fb83ce 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -93,7 +93,6 @@ import android.util.Log; import android.view.Display; import android.view.Gravity; import android.view.HapticFeedbackConstants; -import android.view.IWindowManager; import android.view.InsetsState.InternalInsetsType; import android.view.InsetsVisibilities; import android.view.KeyEvent; @@ -118,20 +117,17 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.LatencyTracker; import com.android.internal.view.AppearanceRegion; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.buttons.ButtonDispatcher; import com.android.systemui.navigationbar.buttons.KeyButtonView; import com.android.systemui.navigationbar.buttons.RotationContextButton; import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle; -import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; @@ -151,8 +147,6 @@ import com.android.systemui.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; -import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.pip.Pip; @@ -162,6 +156,8 @@ import java.util.Locale; import java.util.Optional; import java.util.function.Consumer; +import javax.inject.Inject; + import dagger.Lazy; /** @@ -243,7 +239,13 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private boolean mTransientShown; private int mNavBarMode = NAV_BAR_MODE_3BUTTON; private LightBarController mLightBarController; + private final LightBarController mMainLightBarController; + private final LightBarController.Factory mLightBarControllerFactory; private AutoHideController mAutoHideController; + private final AutoHideController mMainAutoHideController; + private final AutoHideController.Factory mAutoHideControllerFactory; + private final Optional<TelecomManager> mTelecomManagerOptional; + private final InputMethodManager mInputMethodManager; @VisibleForTesting public int mDisplayId; @@ -267,6 +269,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private ViewTreeObserver.OnGlobalLayoutListener mOrientationHandleGlobalLayoutListener; private boolean mShowOrientedHandleForImmersiveMode; + @com.android.internal.annotations.VisibleForTesting public enum NavBarActionEvent implements UiEventLogger.UiEventEnum { @@ -478,11 +481,10 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } }; - public NavigationBar(Context context, + private NavigationBar(Context context, WindowManager windowManager, Lazy<AssistManager> assistManagerLazy, AccessibilityManager accessibilityManager, - AccessibilityManagerWrapper accessibilityManagerWrapper, DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger, OverviewProxyService overviewProxyService, @@ -504,7 +506,13 @@ public class NavigationBar implements View.OnAttachStateChangeListener, NavigationBarOverlayController navbarOverlayController, UiEventLogger uiEventLogger, NavigationBarA11yHelper navigationBarA11yHelper, - UserTracker userTracker) { + UserTracker userTracker, + LightBarController mainLightBarController, + LightBarController.Factory lightBarControllerFactory, + AutoHideController mainAutoHideController, + AutoHideController.Factory autoHideControllerFactory, + Optional<TelecomManager> telecomManagerOptional, + InputMethodManager inputMethodManager) { mContext = context; mWindowManager = windowManager; mAccessibilityManager = accessibilityManager; @@ -531,6 +539,12 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mNavigationBarA11yHelper = navigationBarA11yHelper; mUserTracker = userTracker; mNotificationShadeDepthController = notificationShadeDepthController; + mMainLightBarController = mainLightBarController; + mLightBarControllerFactory = lightBarControllerFactory; + mMainAutoHideController = mainAutoHideController; + mAutoHideControllerFactory = autoHideControllerFactory; + mTelecomManagerOptional = telecomManagerOptional; + mInputMethodManager = inputMethodManager; mNavBarMode = mNavigationModeController.addListener(this); } @@ -548,7 +562,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mNavigationBarView = barView.findViewById(R.id.navigation_bar_view); if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + barView); - mContext.getSystemService(WindowManager.class).addView(mFrame, + mWindowManager.addView(mFrame, getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration .getRotation())); mDisplayId = mContext.getDisplayId(); @@ -606,8 +620,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, public void destroyView() { setAutoHideController(/* autoHideController */ null); mCommandQueue.removeCallback(this); - mContext.getSystemService(WindowManager.class).removeViewImmediate( - mNavigationBarView.getRootView()); + mWindowManager.removeViewImmediate(mNavigationBarView.getRootView()); mNavigationModeController.removeListener(this); mNavigationBarA11yHelper.removeA11yEventListener(mAccessibilityListener); @@ -673,22 +686,16 @@ public class NavigationBar implements View.OnAttachStateChangeListener, // before notifications creation. We cannot directly use getLightBarController() // from NavigationBarFragment directly. LightBarController lightBarController = mIsOnDefaultDisplay - ? Dependency.get(LightBarController.class) - : new LightBarController(mContext, - Dependency.get(DarkIconDispatcher.class), - Dependency.get(BatteryController.class), - Dependency.get(NavigationModeController.class), - Dependency.get(DumpManager.class)); + ? mMainLightBarController : mLightBarControllerFactory.create(mContext); setLightBarController(lightBarController); // TODO(b/118592525): to support multi-display, we start to add something which is // per-display, while others may be global. I think it's time to // add a new class maybe named DisplayDependency to solve // per-display Dependency problem. + // Alternative: this is a good case for a Dagger subcomponent. Same with LightBarController. AutoHideController autoHideController = mIsOnDefaultDisplay - ? Dependency.get(AutoHideController.class) - : new AutoHideController(mContext, mHandler, - Dependency.get(IWindowManager.class)); + ? mMainAutoHideController : mAutoHideControllerFactory.create(mContext); setAutoHideController(autoHideController); restoreAppearanceAndTransientState(); } @@ -1183,9 +1190,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mHomeBlockedThisTouch = false; - TelecomManager telecomManager = - mContext.getSystemService(TelecomManager.class); - if (telecomManager != null && telecomManager.isRinging()) { + if (mTelecomManagerOptional.isPresent() + && mTelecomManagerOptional.get().isRinging()) { if (statusBarOptional.map(StatusBar::isKeyguardShowing).orElse(false)) { Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " + "No heads up"); @@ -1267,7 +1273,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } private void onImeSwitcherClick(View v) { - mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem( + mInputMethodManager.showInputMethodPickerFromSystem( true /* showAuxiliarySubtypes */, mDisplayId); }; @@ -1702,4 +1708,121 @@ public class NavigationBar implements View.OnAttachStateChangeListener, int getNavigationIconHints() { return mNavigationIconHints; } -} + + /** + * Injectable factory for construction a {@link NavigationBar}. + */ + public static class Factory { + private final Lazy<AssistManager> mAssistManagerLazy; + private final AccessibilityManager mAccessibilityManager; + private final DeviceProvisionedController mDeviceProvisionedController; + private final MetricsLogger mMetricsLogger; + private final OverviewProxyService mOverviewProxyService; + private final NavigationModeController mNavigationModeController; + private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; + private final StatusBarStateController mStatusBarStateController; + private final SysUiState mSysUiFlagsContainer; + private final BroadcastDispatcher mBroadcastDispatcher; + private final CommandQueue mCommandQueue; + private final Optional<Pip> mPipOptional; + private final Optional<LegacySplitScreen> mSplitScreenOptional; + private final Optional<Recents> mRecentsOptional; + private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy; + private final ShadeController mShadeController; + private final NotificationRemoteInputManager mNotificationRemoteInputManager; + private final NotificationShadeDepthController mNotificationShadeDepthController; + private final SystemActions mSystemActions; + private final Handler mMainHandler; + private final NavigationBarOverlayController mNavbarOverlayController; + private final UiEventLogger mUiEventLogger; + private final NavigationBarA11yHelper mNavigationBarA11yHelper; + private final UserTracker mUserTracker; + private final LightBarController mMainLightBarController; + private final LightBarController.Factory mLightBarControllerFactory; + private final AutoHideController mMainAutoHideController; + private final AutoHideController.Factory mAutoHideControllerFactory; + private final Optional<TelecomManager> mTelecomManagerOptional; + private final InputMethodManager mInputMethodManager; + + @Inject + public Factory( + Lazy<AssistManager> assistManagerLazy, + AccessibilityManager accessibilityManager, + DeviceProvisionedController deviceProvisionedController, + MetricsLogger metricsLogger, + OverviewProxyService overviewProxyService, + NavigationModeController navigationModeController, + AccessibilityButtonModeObserver accessibilityButtonModeObserver, + StatusBarStateController statusBarStateController, + SysUiState sysUiFlagsContainer, + BroadcastDispatcher broadcastDispatcher, + CommandQueue commandQueue, + Optional<Pip> pipOptional, + Optional<LegacySplitScreen> splitScreenOptional, + Optional<Recents> recentsOptional, + Lazy<Optional<StatusBar>> statusBarOptionalLazy, + ShadeController shadeController, + NotificationRemoteInputManager notificationRemoteInputManager, + NotificationShadeDepthController notificationShadeDepthController, + SystemActions systemActions, + @Main Handler mainHandler, + NavigationBarOverlayController navbarOverlayController, + UiEventLogger uiEventLogger, + NavigationBarA11yHelper navigationBarA11yHelper, + UserTracker userTracker, + LightBarController mainLightBarController, + LightBarController.Factory lightBarControllerFactory, + AutoHideController mainAutoHideController, + AutoHideController.Factory autoHideControllerFactory, + Optional<TelecomManager> telecomManagerOptional, + InputMethodManager inputMethodManager) { + mAssistManagerLazy = assistManagerLazy; + mAccessibilityManager = accessibilityManager; + mDeviceProvisionedController = deviceProvisionedController; + mMetricsLogger = metricsLogger; + mOverviewProxyService = overviewProxyService; + mNavigationModeController = navigationModeController; + mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; + mStatusBarStateController = statusBarStateController; + mSysUiFlagsContainer = sysUiFlagsContainer; + mBroadcastDispatcher = broadcastDispatcher; + mCommandQueue = commandQueue; + mPipOptional = pipOptional; + mSplitScreenOptional = splitScreenOptional; + mRecentsOptional = recentsOptional; + mStatusBarOptionalLazy = statusBarOptionalLazy; + mShadeController = shadeController; + mNotificationRemoteInputManager = notificationRemoteInputManager; + mNotificationShadeDepthController = notificationShadeDepthController; + mSystemActions = systemActions; + mMainHandler = mainHandler; + mNavbarOverlayController = navbarOverlayController; + mUiEventLogger = uiEventLogger; + mNavigationBarA11yHelper = navigationBarA11yHelper; + mUserTracker = userTracker; + mMainLightBarController = mainLightBarController; + mLightBarControllerFactory = lightBarControllerFactory; + mMainAutoHideController = mainAutoHideController; + mAutoHideControllerFactory = autoHideControllerFactory; + mTelecomManagerOptional = telecomManagerOptional; + mInputMethodManager = inputMethodManager; + } + + /** Construct a {@link NavigationBar} */ + public NavigationBar create(Context context) { + final WindowManager wm = context.getSystemService(WindowManager.class); + return new NavigationBar(context, wm, mAssistManagerLazy, + mAccessibilityManager, mDeviceProvisionedController, mMetricsLogger, + mOverviewProxyService, mNavigationModeController, + mAccessibilityButtonModeObserver, mStatusBarStateController, + mSysUiFlagsContainer, mBroadcastDispatcher, mCommandQueue, mPipOptional, + mSplitScreenOptional, mRecentsOptional, mStatusBarOptionalLazy, + mShadeController, mNotificationRemoteInputManager, + mNotificationShadeDepthController, mSystemActions, mMainHandler, + mNavbarOverlayController, mUiEventLogger, mNavigationBarA11yHelper, + mUserTracker, mMainLightBarController, mLightBarControllerFactory, + mMainAutoHideController, mAutoHideControllerFactory, mTelecomManagerOptional, + mInputMethodManager); + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 7622d663301d..97bcb00eddbd 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -32,52 +32,31 @@ import android.util.SparseArray; import android.view.Display; import android.view.IWindowManager; import android.view.View; -import android.view.WindowManager; import android.view.WindowManagerGlobal; -import android.view.accessibility.AccessibilityManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.RegisterStatusBarResult; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.systemui.Dumpable; -import com.android.systemui.accessibility.AccessibilityButtonModeObserver; -import com.android.systemui.accessibility.SystemActions; -import com.android.systemui.assist.AssistManager; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; -import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; -import com.android.systemui.recents.Recents; -import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; -import com.android.systemui.statusbar.phone.ShadeController; -import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; -import com.android.wm.shell.pip.Pip; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.Optional; import javax.inject.Inject; -import dagger.Lazy; - /** A controller to handle navigation bars. */ @SysUISingleton @@ -90,36 +69,12 @@ public class NavigationBarController implements private static final String TAG = NavigationBarController.class.getSimpleName(); private final Context mContext; - private final WindowManager mWindowManager; - private final Lazy<AssistManager> mAssistManagerLazy; - private final AccessibilityManager mAccessibilityManager; - private final AccessibilityManagerWrapper mAccessibilityManagerWrapper; - private final DeviceProvisionedController mDeviceProvisionedController; - private final MetricsLogger mMetricsLogger; - private final OverviewProxyService mOverviewProxyService; - private final NavigationModeController mNavigationModeController; - private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; - private final StatusBarStateController mStatusBarStateController; - private final SysUiState mSysUiFlagsContainer; - private final BroadcastDispatcher mBroadcastDispatcher; - private final CommandQueue mCommandQueue; - private final Optional<Pip> mPipOptional; - private final Optional<LegacySplitScreen> mSplitScreenOptional; - private final Optional<Recents> mRecentsOptional; - private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy; - private final ShadeController mShadeController; - private final NotificationRemoteInputManager mNotificationRemoteInputManager; - private final SystemActions mSystemActions; - private final UiEventLogger mUiEventLogger; private final Handler mHandler; - private final NavigationBarA11yHelper mNavigationBarA11yHelper; + private final NavigationBar.Factory mNavigationBarFactory; private final DisplayManager mDisplayManager; - private final NavigationBarOverlayController mNavBarOverlayController; private final TaskbarDelegate mTaskbarDelegate; - private final NotificationShadeDepthController mNotificationShadeDepthController; private int mNavMode; @VisibleForTesting boolean mIsTablet; - private final UserTracker mUserTracker; /** A displayId - nav bar maps. */ @VisibleForTesting @@ -132,74 +87,30 @@ public class NavigationBarController implements @Inject public NavigationBarController(Context context, - WindowManager windowManager, - Lazy<AssistManager> assistManagerLazy, - AccessibilityManager accessibilityManager, - AccessibilityManagerWrapper accessibilityManagerWrapper, - DeviceProvisionedController deviceProvisionedController, - MetricsLogger metricsLogger, OverviewProxyService overviewProxyService, NavigationModeController navigationModeController, - AccessibilityButtonModeObserver accessibilityButtonModeObserver, - StatusBarStateController statusBarStateController, SysUiState sysUiFlagsContainer, - BroadcastDispatcher broadcastDispatcher, CommandQueue commandQueue, - Optional<Pip> pipOptional, - Optional<LegacySplitScreen> splitScreenOptional, - Optional<Recents> recentsOptional, - Lazy<Optional<StatusBar>> statusBarOptionalLazy, - ShadeController shadeController, - NotificationRemoteInputManager notificationRemoteInputManager, - NotificationShadeDepthController notificationShadeDepthController, - SystemActions systemActions, @Main Handler mainHandler, - UiEventLogger uiEventLogger, - NavigationBarOverlayController navBarOverlayController, ConfigurationController configurationController, NavigationBarA11yHelper navigationBarA11yHelper, TaskbarDelegate taskbarDelegate, - UserTracker userTracker, - DumpManager dumpManager) { + NavigationBar.Factory navigationBarFactory, + DumpManager dumpManager, + AutoHideController autoHideController) { mContext = context; - mWindowManager = windowManager; - mAssistManagerLazy = assistManagerLazy; - mAccessibilityManager = accessibilityManager; - mAccessibilityManagerWrapper = accessibilityManagerWrapper; - mDeviceProvisionedController = deviceProvisionedController; - mMetricsLogger = metricsLogger; - mOverviewProxyService = overviewProxyService; - mNavigationModeController = navigationModeController; - mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; - mStatusBarStateController = statusBarStateController; - mSysUiFlagsContainer = sysUiFlagsContainer; - mBroadcastDispatcher = broadcastDispatcher; - mCommandQueue = commandQueue; - mPipOptional = pipOptional; - mSplitScreenOptional = splitScreenOptional; - mRecentsOptional = recentsOptional; - mStatusBarOptionalLazy = statusBarOptionalLazy; - mShadeController = shadeController; - mNotificationRemoteInputManager = notificationRemoteInputManager; - mNotificationShadeDepthController = notificationShadeDepthController; - mSystemActions = systemActions; - mUiEventLogger = uiEventLogger; mHandler = mainHandler; - mNavigationBarA11yHelper = navigationBarA11yHelper; + mNavigationBarFactory = navigationBarFactory; mDisplayManager = mContext.getSystemService(DisplayManager.class); commandQueue.addCallback(this); configurationController.addCallback(this); mConfigChanges.applyNewConfig(mContext.getResources()); - mNavBarOverlayController = navBarOverlayController; - mNavMode = mNavigationModeController.addListener(this); - mNavigationModeController.addListener(this); + mNavMode = navigationModeController.addListener(this); mTaskbarDelegate = taskbarDelegate; - mTaskbarDelegate.setOverviewProxyService(commandQueue, overviewProxyService, + mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService, navigationBarA11yHelper, navigationModeController, sysUiFlagsContainer, - dumpManager); + dumpManager, autoHideController); mIsTablet = isTablet(mContext); - mUserTracker = userTracker; - dumpManager.registerDumpable(this); } @@ -355,33 +266,8 @@ public class NavigationBarController implements final Context context = isOnDefaultDisplay ? mContext : mContext.createDisplayContext(display); - NavigationBar navBar = new NavigationBar(context, - mWindowManager, - mAssistManagerLazy, - mAccessibilityManager, - mAccessibilityManagerWrapper, - mDeviceProvisionedController, - mMetricsLogger, - mOverviewProxyService, - mNavigationModeController, - mAccessibilityButtonModeObserver, - mStatusBarStateController, - mSysUiFlagsContainer, - mBroadcastDispatcher, - mCommandQueue, - mPipOptional, - mSplitScreenOptional, - mRecentsOptional, - mStatusBarOptionalLazy, - mShadeController, - mNotificationRemoteInputManager, - mNotificationShadeDepthController, - mSystemActions, - mHandler, - mNavBarOverlayController, - mUiEventLogger, - mNavigationBarA11yHelper, - mUserTracker); + NavigationBar navBar = mNavigationBarFactory.create(context); + mNavigationBars.put(displayId, navBar); View navigationBarView = navBar.createView(savedState); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java index 0603bb7f02fa..73a0c542fb09 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationModeController.java @@ -118,7 +118,7 @@ public class NavigationModeController implements Dumpable { configurationController.addCallback(new ConfigurationController.ConfigurationListener() { @Override - public void onOverlayChanged() { + public void onThemeChanged() { if (DEBUG) { Log.d(TAG, "onOverlayChanged"); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 4d29612951cc..d707dbdf5cba 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -19,6 +19,8 @@ package com.android.systemui.navigationbar; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; +import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; +import static android.view.InsetsState.containsType; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -57,7 +59,9 @@ 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.AutoHideUiElement; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.phone.AutoHideController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -77,6 +81,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private NavigationBarA11yHelper mNavigationBarA11yHelper; private NavigationModeController mNavigationModeController; private SysUiState mSysUiState; + private AutoHideController mAutoHideController; private int mDisplayId; private int mNavigationIconHints; private final NavigationBarA11yHelper.NavA11yEventListener mNavA11yEventListener = @@ -87,6 +92,28 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private final Context mContext; private final DisplayManager mDisplayManager; private Context mWindowContext; + /** + * Tracks the system calls for when taskbar should transiently show or hide so we can return + * this value in {@link AutoHideUiElement#isVisible()} below. + * + * This also gets set by {@link #onTaskbarAutohideSuspend(boolean)} to force show the transient + * taskbar if launcher has requested to suspend auto-hide behavior. + */ + private boolean mTaskbarTransientShowing; + private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() { + @Override + public void synchronizeState() { + } + + @Override + public boolean isVisible() { + return mTaskbarTransientShowing; + } + + @Override + public void hide() { + } + }; @Inject public TaskbarDelegate(Context context) { @@ -96,11 +123,12 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mDisplayManager = mContext.getSystemService(DisplayManager.class); } - public void setOverviewProxyService(CommandQueue commandQueue, + public void setDependencies(CommandQueue commandQueue, OverviewProxyService overviewProxyService, NavigationBarA11yHelper navigationBarA11yHelper, NavigationModeController navigationModeController, - SysUiState sysUiState, DumpManager dumpManager) { + SysUiState sysUiState, DumpManager dumpManager, + AutoHideController autoHideController) { // TODO: adding this in the ctor results in a dagger dependency cycle :( mCommandQueue = commandQueue; mOverviewProxyService = overviewProxyService; @@ -108,18 +136,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mNavigationModeController = navigationModeController; mSysUiState = sysUiState; dumpManager.registerDumpable(this); - } - - public void destroy() { - mCommandQueue.removeCallback(this); - mOverviewProxyService.removeCallback(this); - mNavigationModeController.removeListener(this); - mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener); - mEdgeBackGestureHandler.onNavBarDetached(); - if (mWindowContext != null) { - mWindowContext.unregisterComponentCallbacks(this); - mWindowContext = null; - } + mAutoHideController = autoHideController; } public void init(int displayId) { @@ -136,6 +153,20 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mWindowContext.registerComponentCallbacks(this); // Set initial state for any listeners updateSysuiFlags(); + mAutoHideController.setNavigationBar(mAutoHideUiElement); + } + + public void destroy() { + mCommandQueue.removeCallback(this); + mOverviewProxyService.removeCallback(this); + mNavigationModeController.removeListener(this); + mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener); + mEdgeBackGestureHandler.onNavBarDetached(); + if (mWindowContext != null) { + mWindowContext.unregisterComponentCallbacks(this); + mWindowContext = null; + } + mAutoHideController.setNavigationBar(null); } private void updateSysuiFlags() { @@ -209,6 +240,38 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } @Override + public void showTransient(int displayId, int[] types) { + if (displayId != mDisplayId) { + return; + } + if (!containsType(types, ITYPE_NAVIGATION_BAR)) { + return; + } + mTaskbarTransientShowing = true; + } + + @Override + public void abortTransient(int displayId, int[] types) { + if (displayId != mDisplayId) { + return; + } + if (!containsType(types, ITYPE_NAVIGATION_BAR)) { + return; + } + mTaskbarTransientShowing = false; + } + + @Override + public void onTaskbarAutohideSuspend(boolean suspend) { + mTaskbarTransientShowing = suspend; + if (suspend) { + mAutoHideController.suspendAutoHide(); + } else { + mAutoHideController.resumeSuspendedAutoHide(); + } + } + + @Override public void onNavigationModeChanged(int mode) { mEdgeBackGestureHandler.onNavigationModeChanged(mode); } @@ -236,6 +299,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, pw.println(" mDisabledFlags=" + mDisabledFlags); pw.println(" mTaskBarWindowState=" + mTaskBarWindowState); pw.println(" mBehavior=" + mBehavior); + pw.println(" mTaskbarTransientShowing=" + mTaskbarTransientShowing); mEdgeBackGestureHandler.dump(pw); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java index 87c64c78edc8..2f189beb7780 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java @@ -38,6 +38,7 @@ public class PseudoGridView extends ViewGroup { private int mNumColumns = 3; private int mVerticalSpacing; private int mHorizontalSpacing; + private int mFixedChildWidth = -1; public PseudoGridView(Context context, AttributeSet attrs) { super(context, attrs); @@ -53,6 +54,8 @@ public class PseudoGridView extends ViewGroup { mVerticalSpacing = a.getDimensionPixelSize(attr, 0); } else if (attr == R.styleable.PseudoGridView_horizontalSpacing) { mHorizontalSpacing = a.getDimensionPixelSize(attr, 0); + } else if (attr == R.styleable.PseudoGridView_fixedChildWidth) { + mFixedChildWidth = a.getDimensionPixelSize(attr, -1); } } @@ -65,8 +68,15 @@ public class PseudoGridView extends ViewGroup { throw new UnsupportedOperationException("Needs a maximum width"); } int width = MeasureSpec.getSize(widthMeasureSpec); - - int childWidth = (width - (mNumColumns - 1) * mHorizontalSpacing) / mNumColumns; + int childWidth; + int necessarySpaceForChildWidth = + mFixedChildWidth * mNumColumns + mHorizontalSpacing * (mNumColumns - 1); + if (mFixedChildWidth != -1 && necessarySpaceForChildWidth <= width) { + childWidth = mFixedChildWidth; + width = mFixedChildWidth * mNumColumns + mHorizontalSpacing * (mNumColumns - 1); + } else { + childWidth = (width - (mNumColumns - 1) * mHorizontalSpacing) / mNumColumns; + } int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY); int childHeightSpec = MeasureSpec.UNSPECIFIED; int totalHeight = 0; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index bfb63ea5e7c3..90d3448ac1ef 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -35,7 +35,6 @@ import com.android.systemui.qs.TouchAnimator.Builder; import com.android.systemui.qs.TouchAnimator.Listener; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.tileimpl.HeightOverrideable; -import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; import com.android.wm.shell.animation.Interpolators; @@ -170,19 +169,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha } } - void startAlphaAnimation(boolean show) { - if (show == mToShowing) { - return; - } - mToShowing = show; - if (show) { - CrossFadeHelper.fadeIn(mQs.getView(), QQS_FADE_IN_DURATION, 0 /* delay */); - } else { - CrossFadeHelper.fadeOut(mQs.getView(), QQS_FADE_OUT_DURATION, 0 /* delay */, - null /* endRunnable */); - } - } - /** * Sets whether or not the keyguard is currently being shown with a collapsed header. */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index b8376da8a885..7a78601da355 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -39,6 +39,7 @@ import androidx.annotation.VisibleForTesting; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; +import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.MediaHost; import com.android.systemui.plugins.FalsingManager; @@ -89,6 +90,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private int mLayoutDirection; private QSFooter mFooter; private float mLastQSExpansion = -1; + private float mLastPanelFraction; private boolean mQsDisabled; private ImageView mQsDragHandler; @@ -120,7 +122,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca * When true, QS will translate from outside the screen. It will be clipped with parallax * otherwise. */ - private boolean mTranslateWhileExpanding; + private boolean mInSplitShade; private boolean mPulseExpanding; /** @@ -135,6 +137,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private DumpManager mDumpManager; + /** + * Progress of pull down from the center of the lock screen. + * @see com.android.systemui.statusbar.LockscreenShadeTransitionController + */ + private float mFullShadeProgress; + @Inject public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, InjectionInflationController injectionInflater, QSTileHost qsTileHost, @@ -226,7 +234,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { boolean sizeChanged = (oldTop - oldBottom) != (top - bottom); if (sizeChanged) { - setQsExpansion(mLastQSExpansion, mLastHeaderTranslation); + setQsExpansion(mLastQSExpansion, mLastPanelFraction, + mLastHeaderTranslation); } }); mQSPanelController.setUsingHorizontalLayoutChangeListener( @@ -408,7 +417,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQSAnimator.setShowCollapsedOnKeyguard(showCollapsed); } if (!showCollapsed && isKeyguardState()) { - setQsExpansion(mLastQSExpansion, 0); + setQsExpansion(mLastQSExpansion, mLastPanelFraction, 0); } } } @@ -476,33 +485,30 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } @Override - public void setTranslateWhileExpanding(boolean shouldTranslate) { - mTranslateWhileExpanding = shouldTranslate; - mQSAnimator.setTranslateWhileExpanding(shouldTranslate); + public void setInSplitShade(boolean inSplitShade) { + mInSplitShade = inSplitShade; + mQSAnimator.setTranslateWhileExpanding(inSplitShade); } @Override - public void setTransitionToFullShadeAmount(float pxAmount, boolean animated) { + public void setTransitionToFullShadeAmount(float pxAmount, float progress) { boolean isTransitioningToFullShade = pxAmount > 0; if (isTransitioningToFullShade != mTransitioningToFullShade) { mTransitioningToFullShade = isTransitioningToFullShade; updateShowCollapsedOnKeyguard(); - setQsExpansion(mLastQSExpansion, mLastHeaderTranslation); } + mFullShadeProgress = progress; + setQsExpansion(mLastQSExpansion, mLastPanelFraction, mLastHeaderTranslation); } @Override - public void setQsExpansion(float expansion, float proposedTranslation) { - if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + proposedTranslation); + public void setQsExpansion(float expansion, float panelExpansionFraction, + float proposedTranslation) { float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation; - if (mQSAnimator != null) { - final boolean showQSOnLockscreen = expansion > 0; - final boolean showQSUnlocked = headerTranslation == 0 || !mTranslateWhileExpanding; - mQSAnimator.startAlphaAnimation(showQSOnLockscreen || showQSUnlocked - || mTransitioningToFullShade); - } + float progress = mTransitioningToFullShade ? mFullShadeProgress : panelExpansionFraction; + setAlphaAnimationProgress(mInSplitShade ? progress : 1); mContainer.setExpansion(expansion); - final float translationScaleY = (mTranslateWhileExpanding + final float translationScaleY = (mInSplitShade ? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1); boolean onKeyguardAndExpanded = isKeyguardState() && !mShowCollapsedOnKeyguard; if (!mHeaderAnimating && !headerWillBeAnimating()) { @@ -519,6 +525,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca return; } mLastHeaderTranslation = headerTranslation; + mLastPanelFraction = panelExpansionFraction; mLastQSExpansion = expansion; mLastKeyguardAndExpanded = onKeyguardAndExpanded; mLastViewHeight = currentHeight; @@ -560,6 +567,17 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca updateMediaPositions(); } + private void setAlphaAnimationProgress(float progress) { + final View view = getView(); + if (progress == 0 && view.getVisibility() != View.INVISIBLE) { + view.setVisibility(View.INVISIBLE); + } else if (progress > 0 && view.getVisibility() != View.VISIBLE) { + view.setVisibility((View.VISIBLE)); + } + float alpha = ShadeInterpolation.getContentAlpha(progress); + view.setAlpha(alpha); + } + private void updateQsBounds() { if (mLastQSExpansion == 1.0f) { // Fully expanded, let's set the layout bounds as clip bounds. This is necessary because diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java index 04437ea14bb3..821bd5117d18 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java @@ -39,6 +39,8 @@ import com.android.systemui.qs.PseudoGridView; import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.statusbar.policy.UserSwitcherController; +import java.util.function.Consumer; + import javax.inject.Inject; /** @@ -75,6 +77,7 @@ public class UserDetailView extends PseudoGridView { private View mCurrentUserView; private final UiEventLogger mUiEventLogger; private final FalsingManager mFalsingManager; + private Consumer<UserSwitcherController.UserRecord> mClickCallback; @Inject public Adapter(Context context, UserSwitcherController controller, @@ -92,6 +95,10 @@ public class UserDetailView extends PseudoGridView { return createUserDetailItemView(convertView, parent, item); } + public void injectCallback(Consumer<UserSwitcherController.UserRecord> clickCallback) { + mClickCallback = clickCallback; + } + public UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent, UserSwitcherController.UserRecord item) { UserDetailItemView v = UserDetailItemView.convertOrInflate( @@ -167,6 +174,13 @@ public class UserDetailView extends PseudoGridView { } onUserListItemClicked(tag); } + if (mClickCallback != null) { + mClickCallback.accept(tag); + } + } + + public void linkToViewGroup(ViewGroup viewGroup) { + PseudoGridView.ViewGroupAdapterBridge.link(viewGroup, this); } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 11430d93106a..e668cd2969f3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -36,6 +36,7 @@ import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager; import android.text.Html; import android.text.TextUtils; +import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; @@ -63,6 +64,7 @@ import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.systemui.Prefs; import com.android.systemui.R; +import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.phone.SystemUIDialog; @@ -110,6 +112,8 @@ public class InternetDialog extends SystemUIDialog implements private LinearLayout mTurnWifiOnLayout; private LinearLayout mEthernetLayout; private TextView mWifiToggleTitleText; + private LinearLayout mWifiScanNotifyLayout; + private TextView mWifiScanNotifyText; private LinearLayout mSeeAllLayout; private RecyclerView mWifiRecyclerView; private ImageView mConnectedWifiIcon; @@ -220,6 +224,8 @@ public class InternetDialog extends SystemUIDialog implements mMobileNetworkLayout = mDialogView.requireViewById(R.id.mobile_network_layout); mTurnWifiOnLayout = mDialogView.requireViewById(R.id.turn_on_wifi_layout); mWifiToggleTitleText = mDialogView.requireViewById(R.id.wifi_toggle_title); + mWifiScanNotifyLayout = mDialogView.requireViewById(R.id.wifi_scan_notify_layout); + mWifiScanNotifyText = mDialogView.requireViewById(R.id.wifi_scan_notify_text); mConnectedWifListLayout = mDialogView.requireViewById(R.id.wifi_connected_layout); mConnectedWifiIcon = mDialogView.requireViewById(R.id.wifi_connected_icon); mConnectedWifiTitleText = mDialogView.requireViewById(R.id.wifi_connected_title); @@ -313,8 +319,10 @@ public class InternetDialog extends SystemUIDialog implements showProgressBar(); final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked(); final boolean isWifiEnabled = mWifiManager.isWifiEnabled(); + final boolean isWifiScanEnabled = mWifiManager.isScanAlwaysAvailable(); updateWifiToggle(isWifiEnabled, isDeviceLocked); updateConnectedWifi(isWifiEnabled, isDeviceLocked); + updateWifiScanNotify(isWifiEnabled, isWifiScanEnabled, isDeviceLocked); final int visibility = (isDeviceLocked || !isWifiEnabled || mWifiEntriesCount <= 0) ? View.GONE : View.VISIBLE; @@ -411,6 +419,24 @@ public class InternetDialog extends SystemUIDialog implements mContext.getColor(R.color.connected_network_primary_color)); } + @MainThread + private void updateWifiScanNotify(boolean isWifiEnabled, boolean isWifiScanEnabled, + boolean isDeviceLocked) { + if (isWifiEnabled || !isWifiScanEnabled || isDeviceLocked) { + mWifiScanNotifyLayout.setVisibility(View.GONE); + return; + } + if (TextUtils.isEmpty(mWifiScanNotifyText.getText())) { + final AnnotationLinkSpan.LinkInfo linkInfo = new AnnotationLinkSpan.LinkInfo( + AnnotationLinkSpan.LinkInfo.DEFAULT_ANNOTATION, + v -> mInternetDialogController.launchWifiScanningSetting()); + mWifiScanNotifyText.setText(AnnotationLinkSpan.linkify( + getContext().getText(R.string.wifi_scan_notify_message), linkInfo)); + mWifiScanNotifyText.setMovementMethod(LinkMovementMethod.getInstance()); + } + mWifiScanNotifyLayout.setVisibility(View.VISIBLE); + } + void onClickConnectedWifi() { if (mConnectedWifiEntry == null) { return; 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 b9cd08e8ac77..66cd95ffce37 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 @@ -103,6 +103,8 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback, private static final String TAG = "InternetDialogController"; private static final String ACTION_NETWORK_PROVIDER_SETTINGS = "android.settings.NETWORK_PROVIDER_SETTINGS"; + private static final String ACTION_WIFI_SCANNING_SETTINGS = + "android.settings.WIFI_SCANNING_SETTINGS"; private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key"; public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT); public static final int NO_CELL_DATA_TYPE_ICON = 0; @@ -603,6 +605,13 @@ public class InternetDialogController implements WifiEntry.DisconnectCallback, } } + void launchWifiScanningSetting() { + mCallback.dismissDialog(); + final Intent intent = new Intent(ACTION_WIFI_SCANNING_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); + } + void connectCarrierNetwork() { final MergedCarrierEntry mergedCarrierEntry = mAccessPointController.getMergedCarrierEntry(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt new file mode 100644 index 000000000000..01afa56fc496 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt @@ -0,0 +1,86 @@ +/* + * 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.qs.user + +import android.content.Context +import android.os.Bundle +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.WindowInsets +import android.view.WindowManager +import com.android.systemui.qs.PseudoGridView +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.R + +/** + * Dialog for switching users or creating new ones. + */ +class UserDialog( + context: Context +) : SystemUIDialog(context) { + + // create() is no-op after creation + private lateinit var _doneButton: View + /** + * Button with text "Done" in dialog. + */ + val doneButton: View + get() { + create() + return _doneButton + } + + private lateinit var _settingsButton: View + /** + * Button with text "User Settings" in dialog. + */ + val settingsButton: View + get() { + create() + return _settingsButton + } + + private lateinit var _grid: PseudoGridView + /** + * Grid to populate with user avatar from adapter + */ + val grid: ViewGroup + get() { + create() + return _grid + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window?.apply { + setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) + attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars() + attributes.receiveInsetsIgnoringZOrder = true + setLayout( + context.resources.getDimensionPixelSize(R.dimen.notification_panel_width), + ViewGroup.LayoutParams.WRAP_CONTENT + ) + setGravity(Gravity.CENTER) + } + setContentView(R.layout.qs_user_dialog_content) + + _doneButton = requireViewById(R.id.done) + _settingsButton = requireViewById(R.id.settings) + _grid = requireViewById(R.id.grid) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt new file mode 100644 index 000000000000..bae7996517c5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.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.qs.user + +import android.content.Context +import android.content.Intent +import android.provider.Settings +import android.view.View +import androidx.annotation.VisibleForTesting +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.qs.tiles.UserDetailView +import javax.inject.Inject +import javax.inject.Provider + +/** + * Controller for [UserDialog]. + */ +@SysUISingleton +class UserSwitchDialogController @VisibleForTesting constructor( + private val userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>, + private val activityStarter: ActivityStarter, + private val falsingManager: FalsingManager, + private val dialogLaunchAnimator: DialogLaunchAnimator, + private val dialogFactory: (Context) -> UserDialog +) { + + @Inject + constructor( + userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>, + activityStarter: ActivityStarter, + falsingManager: FalsingManager, + dialogLaunchAnimator: DialogLaunchAnimator + ) : this( + userDetailViewAdapterProvider, + activityStarter, + falsingManager, + dialogLaunchAnimator, + { UserDialog(it) } + ) + + companion object { + private val USER_SETTINGS_INTENT = Intent(Settings.ACTION_USER_SETTINGS) + } + + /** + * Show a [UserDialog]. + * + * Populate the dialog with information from and adapter obtained from + * [userDetailViewAdapterProvider] and show it as launched from [view]. + */ + fun showDialog(view: View) { + with(dialogFactory(view.context)) { + setShowForAllUsers(true) + setCanceledOnTouchOutside(true) + create() // Needs to be called before we can retrieve views + + settingsButton.setOnClickListener { + if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() + activityStarter.postStartActivityDismissingKeyguard( + USER_SETTINGS_INTENT, + 0 + ) + } + dismiss() + } + doneButton.setOnClickListener { dismiss() } + + val adapter = userDetailViewAdapterProvider.get() + adapter.injectCallback { + dismiss() + } + adapter.linkToViewGroup(grid) + + dialogLaunchAnimator.showFromView(this, view) + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index c76f01b9f192..721a6af37e21 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -248,6 +248,12 @@ public class OverviewProxyService extends CurrentUserTracker implements onTaskbarStatusUpdated(visible, stashed)); } + @Override + public void notifyTaskbarAutohideSuspend(boolean suspend) { + verifyCallerAndClearCallingIdentityPostMain("notifyTaskbarAutohideSuspend", () -> + onTaskbarAutohideSuspend(suspend)); + } + private boolean sendEvent(int action, int code) { long when = SystemClock.uptimeMillis(); final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */, @@ -818,6 +824,12 @@ public class OverviewProxyService extends CurrentUserTracker implements } } + private void onTaskbarAutohideSuspend(boolean suspend) { + for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { + mConnectionCallbacks.get(i).onTaskbarAutohideSuspend(suspend); + } + } + private void notifyConnectionChanged() { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null); @@ -1000,6 +1012,7 @@ public class OverviewProxyService extends CurrentUserTracker implements default void onNavBarButtonAlphaChanged(float alpha, boolean animate) {} default void onHomeRotationEnabled(boolean enabled) {} default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {} + default void onTaskbarAutohideSuspend(boolean suspend) {} default void onSystemUiStateChanged(int sysuiStateFlags) {} default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {} default void onAssistantGestureCompletion(float velocity) {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 8a397199dc84..74ebfe5ad5e4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -147,7 +147,6 @@ public class KeyguardIndicationController { private boolean mBatteryPresent = true; private long mChargingTimeRemaining; private String mMessageToShowOnScreenOn; - protected int mLockScreenMode; private boolean mInited; private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; @@ -600,10 +599,6 @@ public class KeyguardIndicationController { mHideTransientMessageOnScreenOff = hideOnScreenOff && transientIndication != null; mHandler.removeMessages(MSG_HIDE_TRANSIENT); mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK); - if (mDozing && !TextUtils.isEmpty(mTransientIndication)) { - // Make sure this doesn't get stuck and burns in. Acquire wakelock until its cleared. - mWakeLock.setAcquired(true); - } hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS); updateIndication(false); @@ -623,10 +618,6 @@ public class KeyguardIndicationController { } protected final void updateIndication(boolean animate) { - if (TextUtils.isEmpty(mTransientIndication)) { - mWakeLock.setAcquired(false); - } - if (!mVisible) { return; } @@ -644,24 +635,31 @@ public class KeyguardIndicationController { // colors can be hard to read in low brightness. mTopIndicationView.setTextColor(Color.WHITE); if (!TextUtils.isEmpty(mTransientIndication)) { - mTopIndicationView.switchIndication(mTransientIndication, null); + mWakeLock.setAcquired(true); + mTopIndicationView.switchIndication(mTransientIndication, null, + true, () -> mWakeLock.setAcquired(false)); } else if (!mBatteryPresent) { // If there is no battery detected, hide the indication and bail mIndicationArea.setVisibility(GONE); } else if (!TextUtils.isEmpty(mAlignmentIndication)) { - mTopIndicationView.switchIndication(mAlignmentIndication, null); + mTopIndicationView.switchIndication(mAlignmentIndication, null, + false /* animate */, null /* onAnimationEndCallback */); mTopIndicationView.setTextColor(mContext.getColor(R.color.misalignment_text_color)); } else if (mPowerPluggedIn || mEnableBatteryDefender) { String indication = computePowerIndication(); if (animate) { - animateText(mTopIndicationView, indication); + mWakeLock.setAcquired(true); + mTopIndicationView.switchIndication(indication, null, true /* animate */, + () -> mWakeLock.setAcquired(false)); } else { - mTopIndicationView.switchIndication(indication, null); + mTopIndicationView.switchIndication(indication, null, false /* animate */, + null /* onAnimationEndCallback */); } } else { String percentage = NumberFormat.getPercentInstance() .format(mBatteryLevel / 100f); - mTopIndicationView.switchIndication(percentage, null); + mTopIndicationView.switchIndication(percentage, null /* indication */, + false /* animate */, null /* onAnimationEnd*/); } return; } @@ -819,12 +817,15 @@ public class KeyguardIndicationController { } } - private void showTryFingerprintMsg(String a11yString) { + private void showTryFingerprintMsg(int msgId, String a11yString) { if (mKeyguardUpdateMonitor.isUdfpsAvailable()) { // if udfps available, there will always be a tappable affordance to unlock // For example, the lock icon if (mKeyguardBypassController.getUserHasDeviceEntryIntent()) { showTransientIndication(R.string.keyguard_unlock_press); + } else if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) { + // since face is locked out, simply show "try fingerprint" + showTransientIndication(R.string.keyguard_try_fingerprint); } else { showTransientIndication(R.string.keyguard_face_failed_use_fp); } @@ -860,11 +861,6 @@ public class KeyguardIndicationController { public static final int HIDE_DELAY_MS = 5000; @Override - public void onLockScreenModeChanged(int mode) { - mLockScreenMode = mode; - } - - @Override public void onRefreshBatteryInfo(BatteryStatus status) { boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING || status.status == BatteryManager.BATTERY_STATUS_FULL; @@ -916,7 +912,7 @@ public class KeyguardIndicationController { } else if (mKeyguardUpdateMonitor.isScreenOn()) { if (biometricSourceType == BiometricSourceType.FACE && shouldSuppressFaceMsgAndShowTryFingerprintMsg()) { - showTryFingerprintMsg(helpString); + showTryFingerprintMsg(msgId, helpString); return; } showTransientIndication(helpString, false /* isError */, showActionToUnlock); @@ -936,7 +932,7 @@ public class KeyguardIndicationController { && shouldSuppressFaceMsgAndShowTryFingerprintMsg() && !mStatusBarKeyguardViewManager.isBouncerShowing() && mKeyguardUpdateMonitor.isScreenOn()) { - showTryFingerprintMsg(errString); + showTryFingerprintMsg(msgId, errString); return; } if (msgId == FaceManager.FACE_ERROR_TIMEOUT) { @@ -945,7 +941,7 @@ public class KeyguardIndicationController { if (!mStatusBarKeyguardViewManager.isBouncerShowing() && mKeyguardUpdateMonitor.isUdfpsEnrolled() && mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { - showTryFingerprintMsg(errString); + showTryFingerprintMsg(msgId, errString); } else if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { mStatusBarKeyguardViewManager.showBouncerMessage( mContext.getResources().getString(R.string.keyguard_unlock_press), @@ -989,8 +985,8 @@ public class KeyguardIndicationController { private boolean shouldSuppressFaceMsgAndShowTryFingerprintMsg() { // For dual biometric, don't show face auth messages return mKeyguardUpdateMonitor.isFingerprintDetectionRunning() - && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - true /* isStrongBiometric */); + && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + true /* isStrongBiometric */); } private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 03d8e7e03c0f..77e329f94a36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -208,6 +208,11 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, lateinit var isScrimOpaqueChangedListener: Consumer<Boolean> /** + * A runnable to call when the scrim has been fully revealed. This is only invoked once + */ + var fullyRevealedRunnable: Runnable? = null + + /** * How much of the underlying views are revealed, in percent. 0 means they will be completely * obscured and 1 means they'll be fully visible. */ @@ -218,10 +223,20 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, revealEffect.setRevealAmountOnScrim(value, this) updateScrimOpaque() + maybeTriggerFullyRevealedRunnable() invalidate() } } + private fun maybeTriggerFullyRevealedRunnable() { + if (revealAmount == 1.0f) { + fullyRevealedRunnable?.let { + it.run() + fullyRevealedRunnable = null + } + } + } + /** * The [LightRevealEffect] used to manipulate the radial gradient whenever [revealAmount] * changes. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index ca18b076b1e9..dca7f70d3470 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -298,9 +298,8 @@ class LockscreenShadeTransitionController @Inject constructor( nsslController.setTransitionToFullShadeAmount(field) notificationPanelController.setTransitionToFullShadeAmount(field, false /* animate */, 0 /* delay */) - // TODO: appear qs also in split shade - val qsAmount = if (useSplitShade) 0f else field - qS.setTransitionToFullShadeAmount(qsAmount, false /* animate */) + val progress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance) + qS.setTransitionToFullShadeAmount(field, progress) // TODO: appear media also in split shade val mediaAmount = if (useSplitShade) 0f else field mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 7aa2dc7e0785..5648741e3caf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -32,11 +32,11 @@ import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce import com.android.systemui.Dumpable import com.android.systemui.animation.Interpolators +import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController -import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.PanelExpansionListener import com.android.systemui.statusbar.phone.ScrimController @@ -73,6 +73,10 @@ class NotificationShadeDepthController @Inject constructor( private const val TAG = "DepthController" } + /** + * Did we already unblur while dozing? + */ + private var alreadyUnblurredWhileDozing = false lateinit var root: View private var blurRoot: View? = null private var keyguardAnimator: Animator? = null @@ -181,12 +185,12 @@ class NotificationShadeDepthController @Inject constructor( val animationRadius = MathUtils.constrain(shadeAnimation.radius, blurUtils.minBlurRadius.toFloat(), blurUtils.maxBlurRadius.toFloat()) val expansionRadius = blurUtils.blurRadiusOfRatio( - Interpolators.getNotificationScrimAlpha( - if (shouldApplyShadeBlur()) shadeExpansion else 0f, false)) + ShadeInterpolation.getNotificationScrimAlpha( + if (shouldApplyShadeBlur()) shadeExpansion else 0f)) var combinedBlur = (expansionRadius * INTERACTION_BLUR_FRACTION + animationRadius * ANIMATION_BLUR_FRACTION) - val qsExpandedRatio = Interpolators.getNotificationScrimAlpha(qsPanelExpansion, - false /* notification */) * shadeExpansion + val qsExpandedRatio = ShadeInterpolation.getNotificationScrimAlpha(qsPanelExpansion) * + shadeExpansion combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(qsExpandedRatio)) combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress)) var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius) @@ -229,9 +233,11 @@ class NotificationShadeDepthController @Inject constructor( private val keyguardStateCallback = object : KeyguardStateController.Callback { override fun onKeyguardFadingAwayChanged() { if (!keyguardStateController.isKeyguardFadingAway || - biometricUnlockController.mode != MODE_WAKE_AND_UNLOCK) { + !biometricUnlockController.isWakeAndUnlock) { return } + // When wakeAndUnlocking the screen remains dozing, so we have to manually trigger + // the unblur earlier keyguardAnimator?.cancel() keyguardAnimator = ValueAnimator.ofFloat(1f, 0f).apply { @@ -253,6 +259,7 @@ class NotificationShadeDepthController @Inject constructor( }) start() } + alreadyUnblurredWhileDozing = statusBarStateController.dozeAmount != 0.0f } override fun onKeyguardShowingChanged() { @@ -274,10 +281,24 @@ class NotificationShadeDepthController @Inject constructor( if (isDozing) { shadeAnimation.finishIfRunning() brightnessMirrorSpring.finishIfRunning() + + // unset this for safety, to be ready for the next wakeup + alreadyUnblurredWhileDozing = false } } override fun onDozeAmountChanged(linear: Float, eased: Float) { + if (alreadyUnblurredWhileDozing) { + if (linear == 0.0f) { + // We finished waking up, let's reset + alreadyUnblurredWhileDozing = false + } else { + // We've already handled the unbluring from the keyguardAnimator above. + // if we would continue, we'd play another unzoom / blur animation from the + // dozing changing. + return + } + } wakeAndUnlockBlurRadius = blurUtils.blurRadiusOfRatio(eased) scheduleUpdate() } @@ -435,6 +456,7 @@ class NotificationShadeDepthController @Inject constructor( it.println("blursDisabledForAppLaunch: $blursDisabledForAppLaunch") it.println("qsPanelExpansion: $qsPanelExpansion") it.println("transitionToFullShadeProgress: $transitionToFullShadeProgress") + it.println("alreadyUnblurredWhileDozing: $alreadyUnblurredWhileDozing") it.println("lastAppliedBlur: $lastAppliedBlur") } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 3bd7dd339a9e..0b93fff82725 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -31,7 +31,7 @@ import android.view.animation.PathInterpolator; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; -import com.android.systemui.animation.Interpolators; +import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -168,8 +168,8 @@ public class NotificationShelf extends ActivatableNotificationView implements viewState.clipTopAmount = 0; if (ambientState.isExpansionChanging() && !ambientState.isOnKeyguard()) { - viewState.alpha = Interpolators.getNotificationScrimAlpha( - ambientState.getExpansionFraction(), true /* notification */); + float expansion = ambientState.getExpansionFraction(); + viewState.alpha = ShadeInterpolation.getContentAlpha(expansion); } else { viewState.alpha = 1f - ambientState.getHideAmount(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 19876ba063bb..6da981b72428 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -28,6 +28,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.os.SystemProperties; +import android.os.Trace; import android.text.format.DateFormat; import android.util.FloatProperty; import android.util.Log; @@ -181,6 +182,7 @@ public class StatusBarStateControllerImpl implements } synchronized (mListeners) { + Trace.beginSection(TAG + "#setState(" + StatusBarState.toShortString(state) + ")"); String tag = getClass().getSimpleName() + "#setState(" + state + ")"; DejankUtils.startDetectingBlockingIpcs(tag); for (RankedListener rl : new ArrayList<>(mListeners)) { @@ -198,6 +200,7 @@ public class StatusBarStateControllerImpl implements rl.mListener.onStatePostChange(); } DejankUtils.stopDetectingBlockingIpcs(tag); + Trace.endSection(); } return true; @@ -262,12 +265,14 @@ public class StatusBarStateControllerImpl implements mIsDozing = isDozing; synchronized (mListeners) { + Trace.beginSection(TAG + "#setDozing(" + isDozing + ")"); String tag = getClass().getSimpleName() + "#setIsDozing"; DejankUtils.startDetectingBlockingIpcs(tag); for (RankedListener rl : new ArrayList<>(mListeners)) { rl.mListener.onDozingChanged(isDozing); } DejankUtils.stopDetectingBlockingIpcs(tag); + Trace.endSection(); } return true; @@ -333,12 +338,14 @@ public class StatusBarStateControllerImpl implements mDozeAmount = dozeAmount; float interpolatedAmount = mDozeInterpolator.getInterpolation(dozeAmount); synchronized (mListeners) { + Trace.beginSection(TAG + "#setDozeAmount"); String tag = getClass().getSimpleName() + "#setDozeAmount"; DejankUtils.startDetectingBlockingIpcs(tag); for (RankedListener rl : new ArrayList<>(mListeners)) { rl.mListener.onDozeAmountChanged(mDozeAmount, interpolatedAmount); } DejankUtils.stopDetectingBlockingIpcs(tag); + Trace.endSection(); } } @@ -446,7 +453,7 @@ public class StatusBarStateControllerImpl implements mIsFullscreen = isFullscreen; synchronized (mListeners) { for (RankedListener rl : new ArrayList<>(mListeners)) { - rl.mListener.onFullscreenStateChanged(isFullscreen, true /* isImmersive */); + rl.mListener.onFullscreenStateChanged(isFullscreen); } } } @@ -469,11 +476,13 @@ public class StatusBarStateControllerImpl implements public void setPulsing(boolean pulsing) { if (mPulsing != pulsing) { mPulsing = pulsing; + Trace.beginSection(TAG + "#setPulsing(" + pulsing + ")"); synchronized (mListeners) { for (RankedListener rl : new ArrayList<>(mListeners)) { rl.mListener.onPulsingChanged(pulsing); } } + Trace.endSection(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt index d74297ee8b76..04c60fc197d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt @@ -114,9 +114,6 @@ class WiredChargingRippleController @Inject constructor( override fun onThemeChanged() { updateRippleColor() } - override fun onOverlayChanged() { - updateRippleColor() - } override fun onConfigChanged(newConfig: Configuration?) { normalizedPortPosX = context.resources.getFloat( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index 0352212ced1b..d297d9581d6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -47,6 +47,7 @@ import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.commandline.CommandRegistry; +import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler; import com.android.systemui.statusbar.notification.AssistantFeedbackController; import com.android.systemui.statusbar.notification.DynamicChildBindController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; @@ -255,15 +256,30 @@ public interface StatusBarDependenciesModule { IActivityManager iActivityManager, OngoingCallLogger logger, DumpManager dumpManager, - StatusBarWindowController statusBarWindowController) { + StatusBarWindowController statusBarWindowController, + SwipeStatusBarAwayGestureHandler swipeStatusBarAwayGestureHandler, + StatusBarStateController statusBarStateController) { Optional<StatusBarWindowController> windowController = featureFlags.isOngoingCallInImmersiveEnabled() ? Optional.of(statusBarWindowController) : Optional.empty(); + Optional<SwipeStatusBarAwayGestureHandler> gestureHandler = + featureFlags.isOngoingCallInImmersiveEnabled() + ? Optional.of(swipeStatusBarAwayGestureHandler) + : Optional.empty(); OngoingCallController ongoingCallController = new OngoingCallController( - notifCollection, featureFlags, systemClock, activityStarter, mainExecutor, - iActivityManager, logger, dumpManager, windowController); + notifCollection, + featureFlags, + systemClock, + activityStarter, + mainExecutor, + iActivityManager, + logger, + dumpManager, + windowController, + gestureHandler, + statusBarStateController); ongoingCallController.init(); return ongoingCallController; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt index 1037e576f263..b97bac261ff0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt @@ -24,7 +24,6 @@ import android.util.Log import android.view.Gravity import android.view.View import android.widget.FrameLayout - import com.android.internal.annotations.GuardedBy import com.android.systemui.animation.Interpolators import com.android.systemui.R @@ -44,7 +43,6 @@ import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN import com.android.systemui.util.leak.RotationUtils.Rotation -import java.lang.IllegalStateException import java.util.concurrent.Executor import javax.inject.Inject @@ -71,9 +69,6 @@ class PrivacyDotViewController @Inject constructor( private val contentInsetsProvider: StatusBarContentInsetsProvider, private val animationScheduler: SystemStatusAnimationScheduler ) { - private var sbHeightPortrait = 0 - private var sbHeightLandscape = 0 - private lateinit var tl: View private lateinit var tr: View private lateinit var bl: View @@ -156,16 +151,12 @@ class PrivacyDotViewController @Inject constructor( val newCorner = selectDesignatedCorner(rot, isRtl) val index = newCorner.cornerIndex() + val paddingTop = contentInsetsProvider.getStatusBarPaddingTop(rot) - val h = when (rot) { - 0, 2 -> sbHeightPortrait - 1, 3 -> sbHeightLandscape - else -> 0 - } synchronized(lock) { nextViewState = nextViewState.copy( rotation = rot, - height = h, + paddingTop = paddingTop, designatedCorner = newCorner, cornerIndex = index) } @@ -203,26 +194,17 @@ class PrivacyDotViewController @Inject constructor( } } - @UiThread - private fun updateHeights(rot: Int) { - val height = when (rot) { - 0, 2 -> sbHeightPortrait - 1, 3 -> sbHeightLandscape - else -> 0 - } - - views.forEach { it.layoutParams.height = height } - } - // Update the gravity and margins of the privacy views @UiThread - private fun updateRotations(rotation: Int) { + private fun updateRotations(rotation: Int, paddingTop: Int) { // To keep a view in the corner, its gravity is always the description of its current corner // Therefore, just figure out which view is in which corner. This turns out to be something // like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and // rotating the device counter-clockwise increments rotation by 1 views.forEach { corner -> + corner.setPadding(0, paddingTop, 0, 0) + val rotatedCorner = rotatedCorner(cornerForView(corner), rotation) (corner.layoutParams as FrameLayout.LayoutParams).apply { gravity = rotatedCorner.toGravity() @@ -265,6 +247,7 @@ class PrivacyDotViewController @Inject constructor( var rot = activeRotationForCorner(tl, rtl) var contentInsets = state.contentRectForRotation(rot) + tl.setPadding(0, state.paddingTop, 0, 0) (tl.layoutParams as FrameLayout.LayoutParams).apply { height = contentInsets.height() if (rtl) { @@ -276,6 +259,7 @@ class PrivacyDotViewController @Inject constructor( rot = activeRotationForCorner(tr, rtl) contentInsets = state.contentRectForRotation(rot) + tr.setPadding(0, state.paddingTop, 0, 0) (tr.layoutParams as FrameLayout.LayoutParams).apply { height = contentInsets.height() if (rtl) { @@ -287,6 +271,7 @@ class PrivacyDotViewController @Inject constructor( rot = activeRotationForCorner(br, rtl) contentInsets = state.contentRectForRotation(rot) + br.setPadding(0, state.paddingTop, 0, 0) (br.layoutParams as FrameLayout.LayoutParams).apply { height = contentInsets.height() if (rtl) { @@ -298,6 +283,7 @@ class PrivacyDotViewController @Inject constructor( rot = activeRotationForCorner(bl, rtl) contentInsets = state.contentRectForRotation(rot) + bl.setPadding(0, state.paddingTop, 0, 0) (bl.layoutParams as FrameLayout.LayoutParams).apply { height = contentInsets.height() if (rtl) { @@ -412,6 +398,7 @@ class PrivacyDotViewController @Inject constructor( val right = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_LANDSCAPE) val bottom = contentInsetsProvider .getStatusBarContentInsetsForRotation(ROTATION_UPSIDE_DOWN) + val paddingTop = contentInsetsProvider.getStatusBarPaddingTop() synchronized(lock) { nextViewState = nextViewState.copy( @@ -422,20 +409,12 @@ class PrivacyDotViewController @Inject constructor( portraitRect = top, landscapeRect = right, upsideDownRect = bottom, + paddingTop = paddingTop, layoutRtl = rtl ) } } - /** - * Set the status bar height in portrait and landscape, in pixels. If they are the same you can - * pass the same value twice - */ - fun setStatusBarHeights(portrait: Int, landscape: Int) { - sbHeightPortrait = portrait - sbHeightLandscape = landscape - } - private fun updateStatusBarState() { synchronized(lock) { nextViewState = nextViewState.copy(shadeExpanded = isShadeInQs()) @@ -488,7 +467,7 @@ class PrivacyDotViewController @Inject constructor( if (state.rotation != currentViewState.rotation) { // A rotation has started, hide the views to avoid flicker - updateRotations(state.rotation) + updateRotations(state.rotation, state.paddingTop) } if (state.needsLayout(currentViewState)) { @@ -627,7 +606,7 @@ private data class ViewState( val layoutRtl: Boolean = false, val rotation: Int = 0, - val height: Int = 0, + val paddingTop: Int = 0, val cornerIndex: Int = -1, val designatedCorner: View? = null, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt new file mode 100644 index 000000000000..80577ee24317 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt @@ -0,0 +1,152 @@ +/* + * 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.gesture + +import android.content.Context +import android.os.Looper +import android.view.Choreographer +import android.view.Display +import android.view.InputEvent +import android.view.MotionEvent +import android.view.MotionEvent.* +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shared.system.InputChannelCompat +import com.android.systemui.shared.system.InputMonitorCompat +import com.android.systemui.statusbar.phone.StatusBarWindowController +import javax.inject.Inject + +/** + * A class to detect when a user swipes away the status bar. To be notified when the swipe away + * gesture is detected, add a callback via [addOnGestureDetectedCallback]. + */ +@SysUISingleton +open class SwipeStatusBarAwayGestureHandler @Inject constructor( + context: Context, + private val statusBarWindowController: StatusBarWindowController, + private val logger: SwipeStatusBarAwayGestureLogger +) { + + /** + * Active callbacks, each associated with a tag. Gestures will only be monitored if + * [callbacks.size] > 0. + */ + private val callbacks: MutableMap<String, () -> Unit> = mutableMapOf() + + private var startY: Float = 0f + private var startTime: Long = 0L + private var monitoringCurrentTouch: Boolean = false + + private var inputMonitor: InputMonitorCompat? = null + private var inputReceiver: InputChannelCompat.InputEventReceiver? = null + + private var swipeDistanceThreshold: Int = context.resources.getDimensionPixelSize( + com.android.internal.R.dimen.system_gestures_start_threshold + ) + + /** Adds a callback that will be triggered when the swipe away gesture is detected. */ + fun addOnGestureDetectedCallback(tag: String, callback: () -> Unit) { + val callbacksWasEmpty = callbacks.isEmpty() + callbacks[tag] = callback + if (callbacksWasEmpty) { + startGestureListening() + } + } + + /** Removes the callback. */ + fun removeOnGestureDetectedCallback(tag: String) { + callbacks.remove(tag) + if (callbacks.isEmpty()) { + stopGestureListening() + } + } + + private fun onInputEvent(ev: InputEvent) { + if (ev !is MotionEvent) { + return + } + + when (ev.actionMasked) { + ACTION_DOWN -> { + if ( + // Gesture starts just below the status bar + ev.y >= statusBarWindowController.statusBarHeight + && ev.y <= 3 * statusBarWindowController.statusBarHeight + ) { + logger.logGestureDetectionStarted(ev.y.toInt()) + startY = ev.y + startTime = ev.eventTime + monitoringCurrentTouch = true + } else { + monitoringCurrentTouch = false + } + } + ACTION_MOVE -> { + if (!monitoringCurrentTouch) { + return + } + if ( + // Gesture is up + ev.y < startY + // Gesture went far enough + && (startY - ev.y) >= swipeDistanceThreshold + // Gesture completed quickly enough + && (ev.eventTime - startTime) < SWIPE_TIMEOUT_MS + ) { + monitoringCurrentTouch = false + logger.logGestureDetected(ev.y.toInt()) + callbacks.values.forEach { it.invoke() } + } + } + ACTION_CANCEL, ACTION_UP -> { + if (monitoringCurrentTouch) { + logger.logGestureDetectionEndedWithoutTriggering(ev.y.toInt()) + } + monitoringCurrentTouch = false + } + } + } + + /** Start listening for the swipe gesture. */ + private fun startGestureListening() { + stopGestureListening() + + logger.logInputListeningStarted() + inputMonitor = InputMonitorCompat(TAG, Display.DEFAULT_DISPLAY).also { + inputReceiver = it.getInputReceiver( + Looper.getMainLooper(), + Choreographer.getInstance(), + this::onInputEvent + ) + } + } + + /** Stop listening for the swipe gesture. */ + private fun stopGestureListening() { + inputMonitor?.let { + logger.logInputListeningStopped() + inputMonitor = null + it.dispose() + } + inputReceiver?.let { + inputReceiver = null + it.dispose() + } + } +} + +private const val SWIPE_TIMEOUT_MS: Long = 500 +private val TAG = SwipeStatusBarAwayGestureHandler::class.simpleName diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt new file mode 100644 index 000000000000..17feaa842165 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureLogger.kt @@ -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.statusbar.gesture + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.SwipeStatusBarAwayLog +import javax.inject.Inject + +/** Log messages for [SwipeStatusBarAwayGestureHandler]. */ +class SwipeStatusBarAwayGestureLogger @Inject constructor( + @SwipeStatusBarAwayLog private val buffer: LogBuffer +) { + fun logGestureDetectionStarted(y: Int) { + buffer.log( + TAG, + LogLevel.DEBUG, + { int1 = y }, + { "Beginning gesture detection. y=$int1" } + ) + } + + fun logGestureDetectionEndedWithoutTriggering(y: Int) { + buffer.log( + TAG, + LogLevel.DEBUG, + { int1 = y }, + { "Gesture finished; no swipe up gesture detected. Final y=$int1" } + ) + } + + fun logGestureDetected(y: Int) { + buffer.log( + TAG, + LogLevel.INFO, + { int1 = y }, + { "Gesture detected; notifying callbacks. y=$int1" } + ) + } + + fun logInputListeningStarted() { + buffer.log(TAG, LogLevel.VERBOSE, {}, { "Input listening started "}) + } + + fun logInputListeningStopped() { + buffer.log(TAG, LogLevel.VERBOSE, {}, { "Input listening stopped "}) + } +} + +private const val TAG = "SwipeStatusBarAwayGestureHandler"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index bdade2c6c2c9..bacb85ae89df 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -29,6 +29,7 @@ import android.net.Uri import android.os.Handler import android.os.UserHandle import android.provider.Settings +import android.util.Log import android.view.View import android.view.ViewGroup import com.android.settingslib.Utils @@ -73,6 +74,10 @@ class LockscreenSmartspaceController @Inject constructor( @Main private val handler: Handler, optionalPlugin: Optional<BcSmartspaceDataPlugin> ) { + companion object { + private const val TAG = "LockscreenSmartspaceController" + } + private var session: SmartspaceSession? = null private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null) @@ -210,6 +215,7 @@ class LockscreenSmartspaceController @Inject constructor( val newSession = smartspaceManager.createSmartspaceSession( SmartspaceConfig.Builder(context, "lockscreen").build()) + Log.d(TAG, "Starting smartspace session for lockscreen") newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener) this.session = newSession @@ -231,6 +237,8 @@ class LockscreenSmartspaceController @Inject constructor( * Disconnects the smartspace view from the smartspace service and cleans up any resources. */ fun disconnect() { + if (!smartspaceViews.isEmpty()) return + execution.assertIsMainThread() if (session == null) { @@ -248,6 +256,7 @@ class LockscreenSmartspaceController @Inject constructor( session = null plugin?.onTargetsAvailable(emptyList()) + Log.d(TAG, "Ending smartspace session for lockscreen") } fun addListener(listener: SmartspaceTargetListener) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index acb0e82c24f2..2eb20654716d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -22,7 +22,6 @@ import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Point; -import android.graphics.RectF; import android.util.AttributeSet; import android.util.MathUtils; import android.view.MotionEvent; @@ -111,7 +110,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private Interpolator mCurrentAppearInterpolator; NotificationBackgroundView mBackgroundNormal; - private RectF mAppearAnimationRect = new RectF(); private float mAnimationTranslationY; private boolean mDrawingAppearAnimation; private ValueAnimator mAppearAnimator; @@ -123,13 +121,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private long mLastActionUpTime; private float mNormalBackgroundVisibilityAmount; - private ValueAnimator.AnimatorUpdateListener mBackgroundVisibilityUpdater - = new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - setNormalBackgroundVisibilityAmount(mBackgroundNormal.getAlpha()); - } - }; private FakeShadowView mFakeShadow; private int mCurrentBackgroundTint; private int mTargetTint; @@ -138,11 +129,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private float mOverrideAmount; private boolean mShadowHidden; private boolean mIsHeadsUpAnimation; - private int mHeadsUpAddStartLocation; - private float mHeadsUpLocation; /* In order to track headsup longpress coorindate. */ protected Point mTargetPoint; - private boolean mIsAppearing; private boolean mDismissed; private boolean mRefocusOnDismiss; private AccessibilityManager mAccessibilityManager; @@ -154,7 +142,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView setClipChildren(false); setClipToPadding(false); updateColors(); - initDimens(); } private void updateColors() { @@ -166,17 +153,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView R.color.notification_ripple_untinted_color); } - private void initDimens() { - mHeadsUpAddStartLocation = getResources().getDimensionPixelSize( - com.android.internal.R.dimen.notification_content_margin_start); - } - - @Override - public void onDensityOrFontScaleChanged() { - super.onDensityOrFontScaleChanged(); - initDimens(); - } - /** * Reload background colors from resources and invalidate views. */ @@ -438,7 +414,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAnimation; - mHeadsUpLocation = endLocation; if (mDrawingAppearAnimation) { startAppearAnimation(false /* isAppearing */, translationDirection, delay, duration, onFinishedRunnable, animationListener); @@ -452,7 +427,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAppear; - mHeadsUpLocation = mHeadsUpAddStartLocation; if (mDrawingAppearAnimation) { startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay, duration, null, null); @@ -474,7 +448,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAppearAnimationTranslation = 0; } } - mIsAppearing = isAppearing; float targetValue; if (isAppearing) { @@ -782,8 +755,4 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView void onActivated(ActivatableNotificationView view); void onActivationReset(ActivatableNotificationView view); } - - interface OnDimmedListener { - void onSetDimmed(boolean dimmed); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index c3dc7001f5e8..d59318e45e7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -41,8 +41,11 @@ import android.widget.FrameLayout; import android.widget.FrameLayout.LayoutParams; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.AlphaOptimizedImageView; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -306,7 +309,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl final int showDismissSetting = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.SHOW_NEW_NOTIF_DISMISS, -1); final boolean newFlowHideShelf = showDismissSetting == -1 - ? mContext.getResources().getBoolean(R.bool.flag_notif_updates) + ? Dependency.get(FeatureFlags.class).isEnabled(Flags.NOTIFICATION_UPDATES) : showDismissSetting == 1; if (newFlowHideShelf) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 45fd5efd00f5..a9cc3237d719 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -72,7 +72,6 @@ public class AmbientState { private boolean mPanelFullWidth; private boolean mPulsing; private boolean mUnlockHintRunning; - private int mIntrinsicPadding; private float mHideAmount; private boolean mAppearing; private float mPulseHeight = MAX_PULSE_HEIGHT; @@ -82,6 +81,7 @@ public class AmbientState { private float mAppearFraction; private boolean mIsShadeOpening; private float mOverExpansion; + private int mStackTopMargin; /** Distance of top of notifications panel from top of screen. */ private float mStackY = 0; @@ -94,7 +94,6 @@ public class AmbientState { /** Height of the notifications panel without top padding when expansion completes. */ private float mStackEndHeight; - private float mTransitionToFullShadeAmount; /** * @return Height of the notifications panel without top padding when expansion completes. @@ -493,14 +492,6 @@ public class AmbientState { return mUnlockHintRunning; } - public void setIntrinsicPadding(int intrinsicPadding) { - mIntrinsicPadding = intrinsicPadding; - } - - public int getIntrinsicPadding() { - return mIntrinsicPadding; - } - /** * @return whether a view is dozing and not pulsing right now */ @@ -577,30 +568,11 @@ public class AmbientState { mOnPulseHeightChangedListener = onPulseHeightChangedListener; } - public Runnable getOnPulseHeightChangedListener() { - return mOnPulseHeightChangedListener; - } - public void setTrackedHeadsUpRow(ExpandableNotificationRow row) { mTrackedHeadsUpRow = row; } /** - * Set the amount of pixels we have currently dragged down if we're transitioning to the full - * shade. 0.0f means we're not transitioning yet. - */ - public void setTransitionToFullShadeAmount(float transitionToFullShadeAmount) { - mTransitionToFullShadeAmount = transitionToFullShadeAmount; - } - - /** - * get - */ - public float getTransitionToFullShadeAmount() { - return mTransitionToFullShadeAmount; - } - - /** * Returns the currently tracked heads up row, if there is one and it is currently above the * shelf (still appearing). */ @@ -622,4 +594,12 @@ public class AmbientState { public void setHasAlertEntries(boolean hasAlertEntries) { mHasAlertEntries = hasAlertEntries; } + + public void setStackTopMargin(int stackTopMargin) { + mStackTopMargin = stackTopMargin; + } + + public int getStackTopMargin() { + return mStackTopMargin; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java index 594afceab63a..faf0fdfa0c8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java @@ -21,6 +21,8 @@ import android.util.MathUtils; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; @@ -41,6 +43,7 @@ public class NotificationRoundnessManager { private final ExpandableView[] mTmpFirstInSectionViews; private final ExpandableView[] mTmpLastInSectionViews; private final KeyguardBypassController mBypassController; + private final FeatureFlags mFeatureFlags; private boolean mExpanded; private HashSet<ExpandableView> mAnimatedChildren; private Runnable mRoundingChangedCallback; @@ -55,7 +58,9 @@ public class NotificationRoundnessManager { @Inject NotificationRoundnessManager( KeyguardBypassController keyguardBypassController, - NotificationSectionsFeatureManager sectionsFeatureManager) { + NotificationSectionsFeatureManager sectionsFeatureManager, + FeatureFlags featureFlags) { + mFeatureFlags = featureFlags; int numberOfSections = sectionsFeatureManager.getNumberOfBuckets(); mFirstInSectionViews = new ExpandableView[numberOfSections]; mLastInSectionViews = new ExpandableView[numberOfSections]; @@ -122,9 +127,8 @@ public class NotificationRoundnessManager { void setViewsAffectedBySwipe( ExpandableView viewBefore, ExpandableView viewSwiped, - ExpandableView viewAfter, - boolean cornerAnimationsEnabled) { - if (!cornerAnimationsEnabled) { + ExpandableView viewAfter) { + if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_UPDATES)) { return; } final boolean animate = true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 278f09b45b4c..3e67f7e09615 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -4287,7 +4287,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.COORDINATOR) void setIntrinsicPadding(int intrinsicPadding) { mIntrinsicPadding = intrinsicPadding; - mAmbientState.setIntrinsicPadding(intrinsicPadding); } @ShadeViewRefactor(RefactorComponent.COORDINATOR) @@ -5312,10 +5311,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } mController.getNoticationRoundessManager() - .setViewsAffectedBySwipe((ExpandableView) viewBefore, + .setViewsAffectedBySwipe( + (ExpandableView) viewBefore, (ExpandableView) viewSwiped, - (ExpandableView) viewAfter, - getResources().getBoolean(R.bool.flag_notif_updates)); + (ExpandableView) viewAfter); updateFirstAndLastBackgroundViews(); requestDisallowInterceptTouchEvent(true); @@ -5327,8 +5326,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable void onSwipeEnd() { updateFirstAndLastBackgroundViews(); mController.getNoticationRoundessManager() - .setViewsAffectedBySwipe(null, null, null, - getResources().getBoolean(R.bool.flag_notif_updates)); + .setViewsAffectedBySwipe(null, null, null); // Round bottom corners for notification right before shelf. mShelf.updateAppearance(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 9fe06a0ddbdc..b226aec88cd8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -272,15 +272,6 @@ public class NotificationStackScrollLayoutController { } @Override - public void onOverlayChanged() { - updateShowEmptyShadeView(); - mView.updateCornerRadius(); - mView.updateBgColor(); - mView.updateDecorViews(); - mView.reinflateViews(); - } - - @Override public void onUiModeChanged() { mView.updateBgColor(); mView.updateDecorViews(); @@ -288,6 +279,11 @@ public class NotificationStackScrollLayoutController { @Override public void onThemeChanged() { + updateShowEmptyShadeView(); + mView.updateCornerRadius(); + mView.updateBgColor(); + mView.updateDecorViews(); + mView.reinflateViews(); updateFooter(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 8be5de7ae56e..0accce82b797 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -24,8 +24,10 @@ import android.util.MathUtils; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.VisibleForTesting; + import com.android.systemui.R; -import com.android.systemui.animation.Interpolators; +import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -54,8 +56,7 @@ public class StackScrollAlgorithm { private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState(); private boolean mIsExpanded; private boolean mClipNotificationScrollToTop; - private int mStatusBarHeight; - private float mHeadsUpInset; + @VisibleForTesting float mHeadsUpInset; private int mPinnedZTranslationExtra; private float mNotificationScrimPadding; @@ -75,9 +76,9 @@ public class StackScrollAlgorithm { mPaddingBetweenElements = res.getDimensionPixelSize( R.dimen.notification_divider_height); mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height); - mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height); mClipNotificationScrollToTop = res.getBoolean(R.bool.config_clipNotificationScrollToTop); - mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize( + int statusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height); + mHeadsUpInset = statusBarHeight + res.getDimensionPixelSize( R.dimen.heads_up_status_bar_padding); mPinnedZTranslationExtra = res.getDimensionPixelSize( R.dimen.heads_up_pinned_elevation); @@ -407,8 +408,8 @@ public class StackScrollAlgorithm { viewState.alpha = 1f - ambientState.getHideAmount(); } else if (ambientState.isExpansionChanging()) { // Adjust alpha for shade open & close. - viewState.alpha = Interpolators.getNotificationScrimAlpha( - ambientState.getExpansionFraction(), true /* notification */); + float expansion = ambientState.getExpansionFraction(); + viewState.alpha = ShadeInterpolation.getContentAlpha(expansion); } if (ambientState.isShadeExpanded() && view.mustStayOnScreen() @@ -562,13 +563,14 @@ public class StackScrollAlgorithm { // Move the tracked heads up into position during the appear animation, by interpolating // between the HUN inset (where it will appear as a HUN) and the end position in the shade + float headsUpTranslation = mHeadsUpInset - ambientState.getStackTopMargin(); ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow(); if (trackedHeadsUpRow != null) { ExpandableViewState childState = trackedHeadsUpRow.getViewState(); if (childState != null) { float endPosition = childState.yTranslation - ambientState.getStackTranslation(); childState.yTranslation = MathUtils.lerp( - mHeadsUpInset, endPosition, ambientState.getAppearFraction()); + headsUpTranslation, endPosition, ambientState.getAppearFraction()); } } @@ -602,7 +604,7 @@ public class StackScrollAlgorithm { } } if (row.isPinned()) { - childState.yTranslation = Math.max(childState.yTranslation, mHeadsUpInset); + childState.yTranslation = Math.max(childState.yTranslation, headsUpTranslation); childState.height = Math.max(row.getIntrinsicHeight(), childState.height); childState.hidden = false; ExpandableViewState topState = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java index aeb2efd2026a..111cbbe81755 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java @@ -37,6 +37,7 @@ public class AutoHideController { private final Handler mHandler; private AutoHideUiElement mStatusBar; + /** For tablets, this will represent the Taskbar */ private AutoHideUiElement mNavigationBar; private int mDisplayId; @@ -89,7 +90,7 @@ public class AutoHideController { } } - void resumeSuspendedAutoHide() { + public void resumeSuspendedAutoHide() { if (mAutoHideSuspended) { scheduleAutoHide(); Runnable checkBarModesRunnable = getCheckBarModesRunnable(); @@ -99,7 +100,7 @@ public class AutoHideController { } } - void suspendAutoHide() { + public void suspendAutoHide() { mHandler.removeCallbacks(mAutoHide); Runnable checkBarModesRunnable = getCheckBarModesRunnable(); if (checkBarModesRunnable != null) { @@ -171,4 +172,23 @@ public class AutoHideController { return false; } + + /** + * Injectable factory for creating a {@link AutoHideController}. + */ + public static class Factory { + private final Handler mHandler; + private final IWindowManager mIWindowManager; + + @Inject + public Factory(@Main Handler handler, IWindowManager iWindowManager) { + mHandler = handler; + mIWindowManager = iWindowManager; + } + + /** Create an {@link AutoHideController} */ + public AutoHideController create(Context context) { + return new AutoHideController(context, mHandler, mIWindowManager); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt index 12ae3f1d66cb..96fa8a5cfd71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt @@ -123,7 +123,7 @@ class ConfigurationControllerImpl(context: Context) : ConfigurationController { if (lastConfig.updateFrom(newConfig) and ActivityInfo.CONFIG_ASSETS_PATHS != 0) { listeners.filterForEach({ this.listeners.contains(it) }) { - it.onOverlayChanged() + it.onThemeChanged() } } } 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 5bf982b908cd..49e3fe7df2be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -283,14 +283,12 @@ public class DozeParameters implements } /** - * Sensor to use for brightness changes. + * Gets the brightness string array per posture. Brightness names along with + * doze_brightness_sensor_type is used to determine the brightness sensor to use for + * the current posture. */ - public String brightnessName(@DevicePostureController.DevicePostureInt int posture) { - return AmbientDisplayConfiguration.getSensorFromPostureMapping( - mResources.getStringArray(R.array.doze_brightness_sensor_name_posture_mapping), - null /* defaultValue */, - posture - ); + public String[] brightnessNames() { + return mResources.getStringArray(R.array.doze_brightness_sensor_name_posture_mapping); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index f289b9f20211..57b9c03ce576 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -226,11 +226,11 @@ public final class DozeServiceHost implements DozeHost { return; } - if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) { + if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) { mScrimController.setWakeLockScreenSensorActive(true); } - boolean passiveAuthInterrupt = reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN + boolean passiveAuthInterrupt = reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH && mWakeLockScreenPerformsAuth; // Set the state to pulsing, so ScrimController will know what to do once we ask it to // execute the transition. The pulse callback will then be invoked when the scrims @@ -329,7 +329,7 @@ public final class DozeServiceHost implements DozeHost { @Override public void extendPulse(int reason) { - if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) { + if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_REACH) { mScrimController.setWakeLockScreenSensorActive(true); } if (mDozeScrimController.isPulsing() && mHeadsUpManagerPhone.hasNotifications()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index 878fbbf39627..927b4c8cc919 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -163,7 +163,6 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null); mWakeUpCoordinator.removeListener(this); mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp); - mNotificationPanelViewController.setVerticalTranslationListener(null); mNotificationPanelViewController.setHeadsUpAppearanceController(null); mStackScrollerController.removeOnExpandedHeightChangedListener(mSetExpandedHeight); mDarkIconDispatcher.removeDarkReceiver(this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 4b545ebf2a05..5f402d0d861d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -121,7 +121,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, } @Override - public void onOverlayChanged() { + public void onThemeChanged() { updateResources(); } }); 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 a5b5f1cbf1e7..3a68b9c3d1b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java @@ -95,44 +95,85 @@ public class KeyguardIndicationTextView extends TextView { } /** - * Changes the text with an animation and makes sure a single indication is shown long enough. + * Changes the text with an animation. Makes sure a single indication is shown long enough. + */ + public void switchIndication(CharSequence text, KeyguardIndication indication) { + switchIndication(text, indication, true, null); + } + + /** + * Changes the text with an optional animation. For animating text, makes sure a single + * indication is shown long enough. * * @param text The text to show. * @param indication optional display information for the text + * @param animate whether to animate this indication in - we may not want this on AOD + * @param onAnimationEndCallback runnable called after this indication is animated in */ - public void switchIndication(CharSequence text, KeyguardIndication indication) { + public void switchIndication(CharSequence text, KeyguardIndication indication, + boolean animate, Runnable onAnimationEndCallback) { if (text == null) text = ""; CharSequence lastPendingMessage = mMessages.peekLast(); if (TextUtils.equals(lastPendingMessage, text) || (lastPendingMessage == null && TextUtils.equals(text, getText()))) { + if (onAnimationEndCallback != null) { + onAnimationEndCallback.run(); + } return; } mMessages.add(text); mKeyguardIndicationInfo.add(indication); - final boolean hasIcon = indication != null && indication.getIcon() != null; - final AnimatorSet animSet = new AnimatorSet(); - final AnimatorSet.Builder animSetBuilder = animSet.play(getOutAnimator()); - - // Make sure each animation is visible for a minimum amount of time, while not worrying - // about fading in blank text - long timeInMillis = System.currentTimeMillis(); - long delay = Math.max(0, mNextAnimationTime - timeInMillis); - setNextAnimationTime(timeInMillis + delay + getFadeOutDuration()); - - final long minDurationMillis = - (indication != null && indication.getMinVisibilityMillis() != null) - ? indication.getMinVisibilityMillis() - : MSG_MIN_DURATION_MILLIS_DEFAULT; + if (animate) { + final boolean hasIcon = indication != null && indication.getIcon() != null; + final AnimatorSet animator = new AnimatorSet(); + // Make sure each animation is visible for a minimum amount of time, while not worrying + // about fading in blank text + long timeInMillis = System.currentTimeMillis(); + long delay = Math.max(0, mNextAnimationTime - timeInMillis); + setNextAnimationTime(timeInMillis + delay + getFadeOutDuration()); + final long minDurationMillis = + (indication != null && indication.getMinVisibilityMillis() != null) + ? indication.getMinVisibilityMillis() + : MSG_MIN_DURATION_MILLIS_DEFAULT; + if (!text.equals("") || hasIcon) { + setNextAnimationTime(mNextAnimationTime + minDurationMillis); + Animator inAnimator = getInAnimator(); + inAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (onAnimationEndCallback != null) { + onAnimationEndCallback.run(); + } + } + }); + animator.playSequentially(getOutAnimator(), inAnimator); + } else { + Animator outAnimator = getOutAnimator(); + outAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (onAnimationEndCallback != null) { + onAnimationEndCallback.run(); + } + } + }); + animator.play(outAnimator); + } - if (!text.equals("") || hasIcon) { - setNextAnimationTime(mNextAnimationTime + minDurationMillis); - animSetBuilder.before(getInAnimator()); + animator.setStartDelay(delay); + animator.start(); + } else { + setAlpha(1f); + setTranslationY(0f); + setNextIndication(); + if (onAnimationEndCallback != null) { + onAnimationEndCallback.run(); + } } - - animSet.setStartDelay(delay); - animSet.start(); } private AnimatorSet getOutAnimator() { @@ -143,29 +184,8 @@ public class KeyguardIndicationTextView extends TextView { fadeOut.addListener(new AnimatorListenerAdapter() { @Override 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); - final Drawable icon = info.getIcon(); - if (icon != null) { - icon.setTint(getCurrentTextColor()); - if (icon instanceof AnimatedVectorDrawable) { - ((AnimatedVectorDrawable) icon).start(); - } - } - setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null); - } - setText(mMessages.poll()); + super.onAnimationEnd(animator); + setNextIndication(); } }); @@ -177,6 +197,32 @@ public class KeyguardIndicationTextView extends TextView { return animatorSet; } + private void setNextIndication() { + 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); + final Drawable icon = info.getIcon(); + if (icon != null) { + icon.setTint(getCurrentTextColor()); + if (icon instanceof AnimatedVectorDrawable) { + ((AnimatedVectorDrawable) icon).start(); + } + } + setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null); + } + setText(mMessages.poll()); + } + private AnimatorSet getInAnimator() { AnimatorSet animatorSet = new AnimatorSet(); ObjectAnimator fadeIn = ObjectAnimator.ofFloat(this, View.ALPHA, 1f); @@ -190,6 +236,7 @@ public class KeyguardIndicationTextView extends TextView { yTranslate.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); setTranslationY(0); } }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 5feb4053f833..90550818bbdd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -100,13 +100,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat } @Override - public void onOverlayChanged() { - mView.onOverlayChanged(); - KeyguardStatusBarViewController.this.onThemeChanged(); - } - - @Override public void onThemeChanged() { + mView.onOverlayChanged(); KeyguardStatusBarViewController.this.onThemeChanged(); } }; 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 abee7a51f91f..570b0ca3564c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; @@ -101,7 +102,9 @@ public class LightBarController implements BatteryController.BatteryStateChangeC mNavigationMode = mode; }); - dumpManager.registerDumpable(getClass().getSimpleName(), this); + if (ctx.getDisplayId() == DEFAULT_DISPLAY) { + dumpManager.registerDumpable(getClass().getSimpleName(), this); + } } public void setNavigationBar(LightBarTransitionsController navigationBar) { @@ -298,4 +301,33 @@ public class LightBarController implements BatteryController.BatteryStateChangeC pw.println(); } } + + /** + * Injectable factory for creating a {@link LightBarController}. + */ + public static class Factory { + private final DarkIconDispatcher mDarkIconDispatcher; + private final BatteryController mBatteryController; + private final NavigationModeController mNavModeController; + private final DumpManager mDumpManager; + + @Inject + public Factory( + DarkIconDispatcher darkIconDispatcher, + BatteryController batteryController, + NavigationModeController navModeController, + DumpManager dumpManager) { + + mDarkIconDispatcher = darkIconDispatcher; + mBatteryController = batteryController; + mNavModeController = navModeController; + mDumpManager = dumpManager; + } + + /** Create an {@link LightBarController} */ + public LightBarController create(Context context) { + return new LightBarController(context, mDarkIconDispatcher, mBatteryController, + mNavModeController, mDumpManager); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java index 78fcd82dc1f5..2a13e6bbd37e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -119,7 +119,6 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen LoaderResult result = loadBitmap(mCurrentUserId, mSelectedUser); if (result.success) { mCached = true; - mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null); mCache = result.bitmap; } return mCache; @@ -235,7 +234,6 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen if (result.success) { mCached = true; mCache = result.bitmap; - mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null); mMediaManager.updateMediaMetaData( true /* metaDataChanged */, true /* allowEnterAnimation */); } 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 3fdf1ceaa2d9..dd21c8af37e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java @@ -23,11 +23,13 @@ import android.view.View; import android.view.ViewGroup; import com.android.systemui.R; +import com.android.systemui.flags.FeatureFlags; 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.qs.user.UserSwitchDialogController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.ViewController; @@ -39,6 +41,8 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { private final UserSwitcherController mUserSwitcherController; private final QSDetailDisplayer mQsDetailDisplayer; private final FalsingManager mFalsingManager; + private final UserSwitchDialogController mUserSwitchDialogController; + private final FeatureFlags mFeatureFlags; private UserSwitcherController.BaseUserAdapter mUserListener; @@ -49,14 +53,18 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { return; } - View center = mView.getChildCount() > 0 ? mView.getChildAt(0) : mView; + if (mFeatureFlags.useNewUserSwitcher()) { + mUserSwitchDialogController.showDialog(v); + } else { + View center = mView.getChildCount() > 0 ? mView.getChildAt(0) : mView; - int[] tmpInt = new int[2]; - center.getLocationInWindow(tmpInt); - tmpInt[0] += center.getWidth() / 2; - tmpInt[1] += center.getHeight() / 2; + int[] tmpInt = new int[2]; + center.getLocationInWindow(tmpInt); + tmpInt[0] += center.getWidth() / 2; + tmpInt[1] += center.getHeight() / 2; - mQsDetailDisplayer.showDetailAdapter(getUserDetailAdapter(), tmpInt[0], tmpInt[1]); + mQsDetailDisplayer.showDetailAdapter(getUserDetailAdapter(), tmpInt[0], tmpInt[1]); + } } }; @@ -66,31 +74,40 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { private final UserSwitcherController mUserSwitcherController; private final QSDetailDisplayer mQsDetailDisplayer; private final FalsingManager mFalsingManager; + private final UserSwitchDialogController mUserSwitchDialogController; + private final FeatureFlags mFeatureFlags; @Inject public Factory(UserManager userManager, UserSwitcherController userSwitcherController, - QSDetailDisplayer qsDetailDisplayer, FalsingManager falsingManager) { + QSDetailDisplayer qsDetailDisplayer, FalsingManager falsingManager, + UserSwitchDialogController userSwitchDialogController, + FeatureFlags featureFlags) { mUserManager = userManager; mUserSwitcherController = userSwitcherController; mQsDetailDisplayer = qsDetailDisplayer; mFalsingManager = falsingManager; + mUserSwitchDialogController = userSwitchDialogController; + mFeatureFlags = featureFlags; } public MultiUserSwitchController create(FooterActionsView view) { return new MultiUserSwitchController(view.findViewById(R.id.multi_user_switch), mUserManager, mUserSwitcherController, mQsDetailDisplayer, - mFalsingManager); + mFalsingManager, mUserSwitchDialogController, mFeatureFlags); } } private MultiUserSwitchController(MultiUserSwitch view, UserManager userManager, UserSwitcherController userSwitcherController, QSDetailDisplayer qsDetailDisplayer, - FalsingManager falsingManager) { + FalsingManager falsingManager, UserSwitchDialogController userSwitchDialogController, + FeatureFlags featureFlags) { super(view); mUserManager = userManager; mUserSwitcherController = userSwitcherController; mQsDetailDisplayer = qsDetailDisplayer; mFalsingManager = falsingManager; + mUserSwitchDialogController = userSwitchDialogController; + mFeatureFlags = featureFlags; } @Override 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 90e554041dbc..53def0110b62 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -443,7 +443,6 @@ public class NotificationPanelViewController extends PanelViewController { private ArrayList<Consumer<ExpandableNotificationRow>> mTrackingHeadsUpListeners = new ArrayList<>(); - private Runnable mVerticalTranslationListener; private HeadsUpAppearanceController mHeadsUpAppearanceController; private int mPanelAlpha; @@ -984,7 +983,7 @@ public class NotificationPanelViewController extends PanelViewController { Utils.shouldUseSplitNotificationShade(mResources); mScrimController.setClipsQsScrim(!mShouldUseSplitNotificationShade); if (mQs != null) { - mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade); + mQs.setInSplitShade(mShouldUseSplitNotificationShade); } int topMargin = mShouldUseSplitNotificationShade ? mSplitShadeStatusBarHeight : @@ -1012,6 +1011,7 @@ public class NotificationPanelViewController extends PanelViewController { constraintSet.setMargin(R.id.notification_stack_scroller, TOP, topMargin); constraintSet.setMargin(R.id.qs_frame, TOP, topMargin); constraintSet.applyTo(mNotificationContainerParent); + mAmbientState.setStackTopMargin(topMargin); mNotificationsQSContainerController.setSplitShadeEnabled(mShouldUseSplitNotificationShade); updateKeyguardStatusViewAlignment(/* animate= */false); @@ -1247,6 +1247,7 @@ public class NotificationPanelViewController extends PanelViewController { stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded; } + mSplitShadeHeaderController.setShadeExpandedFraction(getExpandedFraction()); mNotificationStackScrollLayoutController.setIntrinsicPadding(stackScrollerPadding); mKeyguardBottomArea.setAntiBurnInOffsetX(mClockPositionResult.clockX); @@ -1525,6 +1526,24 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScrollLayoutController.resetScrollPosition(); } + /** Collapses the panel. */ + public void collapsePanel(boolean animate, boolean delayed, float speedUpFactor) { + boolean waiting = false; + if (animate && !isFullyCollapsed()) { + collapse(delayed, speedUpFactor); + waiting = true; + } else { + resetViews(false /* animate */); + setExpandedFraction(0); // just in case + } + if (!waiting) { + // it's possible that nothing animated, so we replicate the termination + // conditions of panelExpansionChanged here + // TODO(b/200063118): This can likely go away in a future refactor CL. + mBar.updateState(STATE_CLOSED); + } + } + @Override public void collapse(boolean delayed, float speedUpFactor) { if (!canPanelBeCollapsed()) { @@ -2202,7 +2221,7 @@ public class NotificationPanelViewController extends PanelViewController { private void updateQsExpansion() { if (mQs == null) return; float qsExpansionFraction = computeQsExpansionFraction(); - mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation()); + mQs.setQsExpansion(qsExpansionFraction, getExpandedFraction(), getHeaderTranslation()); mMediaHierarchyManager.setQsExpansion(qsExpansionFraction); int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction); mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY); @@ -2696,8 +2715,9 @@ public class NotificationPanelViewController extends PanelViewController { * @return Whether we should intercept a gesture to open Quick Settings. */ private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { - if (!isQsExpansionEnabled() || mCollapsedOnDown || (mKeyguardShowing - && mKeyguardBypassController.getBypassEnabled())) { + if (!isQsExpansionEnabled() || mCollapsedOnDown + || (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()) + || (mKeyguardShowing && mShouldUseSplitNotificationShade)) { return false; } View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader(); @@ -2965,7 +2985,7 @@ public class NotificationPanelViewController extends PanelViewController { @Override protected void onExpandingFinished() { - super.onExpandingFinished(); + mScrimController.onExpandingFinished(); mNotificationStackScrollLayoutController.onExpansionStopped(); mHeadsUpManager.onExpandingFinished(); mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed()); @@ -3040,6 +3060,7 @@ public class NotificationPanelViewController extends PanelViewController { protected void onTrackingStarted() { mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen()); super.onTrackingStarted(); + mScrimController.onTrackingStarted(); if (mQsFullyExpanded) { mQsExpandImmediate = true; if (!mShouldUseSplitNotificationShade) { @@ -3050,6 +3071,7 @@ public class NotificationPanelViewController extends PanelViewController { mAffordanceHelper.animateHideLeftRightIcon(); } mNotificationStackScrollLayoutController.onPanelTrackingStarted(); + cancelPendingPanelCollapse(); } @Override @@ -3239,8 +3261,7 @@ public class NotificationPanelViewController extends PanelViewController { @Override protected void onClosingFinished() { - super.onClosingFinished(); - resetHorizontalPanelPosition(); + mStatusBar.onClosingFinished(); setClosingWithAlphaFadeout(false); mMediaHierarchyManager.closeGuts(); } @@ -3250,47 +3271,6 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScrollLayoutController.forceNoOverlappingRendering(closing); } - /** - * Updates the horizontal position of the panel so it is positioned closer to the touch - * responsible for opening the panel. - * - * @param x the x-coordinate the touch event - */ - protected void updateHorizontalPanelPosition(float x) { - if (mNotificationStackScrollLayoutController.getWidth() * 1.75f > mView.getWidth() - || mShouldUseSplitNotificationShade) { - resetHorizontalPanelPosition(); - return; - } - float leftMost = mPositionMinSideMargin - + mNotificationStackScrollLayoutController.getWidth() / 2; - float - rightMost = - mView.getWidth() - mPositionMinSideMargin - - mNotificationStackScrollLayoutController.getWidth() / 2; - if (Math.abs(x - mView.getWidth() / 2) - < mNotificationStackScrollLayoutController.getWidth() / 4) { - x = mView.getWidth() / 2; - } - x = Math.min(rightMost, Math.max(leftMost, x)); - float - center = mNotificationStackScrollLayoutController.getLeft() - + mNotificationStackScrollLayoutController.getWidth() / 2; - setHorizontalPanelTranslation(x - center); - } - - private void resetHorizontalPanelPosition() { - setHorizontalPanelTranslation(0f); - } - - protected void setHorizontalPanelTranslation(float translation) { - mNotificationStackScrollLayoutController.setTranslationX(translation); - mQsFrame.setTranslationX(translation); - if (mVerticalTranslationListener != null) { - mVerticalTranslationListener.run(); - } - } - protected void updateExpandedHeight(float expandedHeight) { if (mTracking) { mNotificationStackScrollLayoutController @@ -3337,9 +3317,9 @@ public class NotificationPanelViewController extends PanelViewController { * cases, such as if there's a heads-up notification. */ public void setPanelScrimMinFraction(float minFraction) { - mBar.onPanelMinFractionChanged(minFraction); mMinFraction = minFraction; mDepthController.setPanelPullDownMinFraction(mMinFraction); + mScrimController.setPanelScrimMinFraction(mMinFraction); } public void clearNotificationEffects() { @@ -3455,7 +3435,7 @@ public class NotificationPanelViewController extends PanelViewController { mQs.setExpandClickListener(mOnClickListener); mQs.setHeaderClickable(isQsExpansionEnabled()); mQs.setOverscrolling(mStackScrollerOverscrolling); - mQs.setTranslateWhileExpanding(mShouldUseSplitNotificationShade); + mQs.setInSplitShade(mShouldUseSplitNotificationShade); // recompute internal state when qspanel height changes mQs.getView().addOnLayoutChangeListener( @@ -3603,10 +3583,6 @@ public class NotificationPanelViewController extends PanelViewController { mTrackingHeadsUpListeners.remove(listener); } - public void setVerticalTranslationListener(Runnable verticalTranslationListener) { - mVerticalTranslationListener = verticalTranslationListener; - } - public void setHeadsUpAppearanceController( HeadsUpAppearanceController headsUpAppearanceController) { mHeadsUpAppearanceController = headsUpAppearanceController; @@ -3715,13 +3691,27 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScrollLayoutController.setScrollingEnabled(b); } + private Runnable mHideExpandedRunnable; + private final Runnable mMaybeHideExpandedRunnable = new Runnable() { + @Override + public void run() { + if (getExpansionFraction() == 0.0f) { + mView.post(mHideExpandedRunnable); + } + } + }; + /** * Initialize objects instead of injecting to avoid circular dependencies. + * + * @param hideExpandedRunnable a runnable to run when we need to hide the expanded panel. */ public void initDependencies( StatusBar statusBar, + Runnable hideExpandedRunnable, NotificationShelfController notificationShelfController) { setStatusBar(statusBar); + mHideExpandedRunnable = hideExpandedRunnable; mNotificationStackScrollLayoutController.setShelfController(notificationShelfController); mNotificationShelfController = notificationShelfController; mLockscreenShadeTransitionController.bindController(notificationShelfController); @@ -3778,6 +3768,45 @@ public class NotificationPanelViewController extends PanelViewController { private long mLastTouchDownTime = -1L; @Override + public boolean onTouchForwardedFromStatusBar(MotionEvent event) { + // TODO(b/202981994): Move the touch debugging in this method to a central location. + // (Right now, it's split between StatusBar and here.) + + // If panels aren't enabled, ignore the gesture and don't pass it down to the + // panel view. + if (!mCommandQueue.panelsEnabled()) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + Log.v( + TAG, + String.format( + "onTouchForwardedFromStatusBar: " + + "panel disabled, ignoring touch at (%d,%d)", + (int) event.getX(), + (int) event.getY() + ) + ); + } + return false; + } + + // If the view that would receive the touch is disabled, just have status bar eat + // the gesture. + if (event.getAction() == MotionEvent.ACTION_DOWN && !mView.isEnabled()) { + Log.v(TAG, + String.format( + "onTouchForwardedFromStatusBar: " + + "panel view disabled, eating touch at (%d,%d)", + (int) event.getX(), + (int) event.getY() + ) + ); + return true; + } + + return mView.dispatchTouchEvent(event); + } + + @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (mBlockTouches || mQs.disallowPanelTouches()) { return false; @@ -3788,7 +3817,7 @@ public class NotificationPanelViewController extends PanelViewController { if (mStatusBar.isBouncerShowing()) { return true; } - if (mBar.panelEnabled() + if (mCommandQueue.panelsEnabled() && !mNotificationStackScrollLayoutController.isLongPressInProgress() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); @@ -3874,7 +3903,6 @@ public class NotificationPanelViewController extends PanelViewController { } if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) { mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); - updateHorizontalPanelPosition(event.getX()); handled = true; } @@ -4258,12 +4286,7 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void onThemeChanged() { if (DEBUG) Log.d(TAG, "onThemeChanged"); - final int themeResId = mView.getContext().getThemeResId(); - if (mThemeResId == themeResId) { - return; - } - mThemeResId = themeResId; - + mThemeResId = mView.getContext().getThemeResId(); reInflateViews(); } @@ -4277,12 +4300,6 @@ public class NotificationPanelViewController extends PanelViewController { } @Override - public void onOverlayChanged() { - if (DEBUG) Log.d(TAG, "onOverlayChanged"); - reInflateViews(); - } - - @Override public void onDensityOrFontScaleChanged() { if (DEBUG) Log.d(TAG, "onDensityOrFontScaleChanged"); reInflateViews(); @@ -4363,9 +4380,16 @@ public class NotificationPanelViewController extends PanelViewController { } } } else { - mKeyguardStatusBarViewController.updateViewState( - /* alpha= */ 1f, - keyguardShowing ? View.VISIBLE : View.INVISIBLE); + final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE + && statusBarState == KEYGUARD + && mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying(); + if (!animatingUnlockedShadeToKeyguard) { + // Only make the status bar visible if we're not animating the screen off, since + // we only want to be showing the clock/notifications during the animation. + mKeyguardStatusBarViewController.updateViewState( + /* alpha= */ 1f, + keyguardShowing ? View.VISIBLE : View.INVISIBLE); + } if (keyguardShowing && oldState != mBarState) { if (mQs != null) { mQs.hideImmediately(); @@ -4381,7 +4405,6 @@ public class NotificationPanelViewController extends PanelViewController { // The update needs to happen after the headerSlide in above, otherwise the translation // would reset maybeAnimateBottomAreaAlpha(); - resetHorizontalPanelPosition(); updateQsState(); mSplitShadeHeaderController.setShadeExpanded( mBarState == SHADE || mBarState == SHADE_LOCKED); @@ -4607,9 +4630,6 @@ public class NotificationPanelViewController extends PanelViewController { public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mAffordanceHelper.onConfigurationChanged(); - if (newConfig.orientation != mLastOrientation) { - resetHorizontalPanelPosition(); - } mLastOrientation = newConfig.orientation; } } @@ -4628,6 +4648,11 @@ public class NotificationPanelViewController extends PanelViewController { } } + /** Removes any pending runnables that would collapse the panel. */ + public void cancelPendingPanelCollapse() { + mView.removeCallbacks(mMaybeHideExpandedRunnable); + } + private final PanelBar.PanelStateChangeListener mPanelStateChangeListener = new PanelBar.PanelStateChangeListener() { @@ -4642,6 +4667,14 @@ public class NotificationPanelViewController extends PanelViewController { if (state == STATE_OPEN && mCurrentState != state) { mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } + if (state == STATE_OPENING) { + mStatusBar.makeExpandedVisible(false); + } + if (state == STATE_CLOSED) { + // Close the status bar in the next frame so we can show the end of the + // animation. + mView.post(mMaybeHideExpandedRunnable); + } mCurrentState = state; } }; @@ -4649,4 +4682,10 @@ public class NotificationPanelViewController extends PanelViewController { public PanelBar.PanelStateChangeListener getPanelStateChangeListener() { return mPanelStateChangeListener; } + + + /** Returns the handler that the status bar should forward touches to. */ + public PhoneStatusBarView.TouchEventHandler getStatusBarTouchEventHandler() { + return getTouchHandler()::onTouchForwardedFromStatusBar; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java index 1f1090d7168b..e90258db8571 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -25,7 +25,6 @@ import android.os.Bundle; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; -import android.view.MotionEvent; import android.widget.FrameLayout; import androidx.annotation.Nullable; @@ -53,11 +52,18 @@ public abstract class PanelBar extends FrameLayout { public static final int STATE_OPENING = 1; public static final int STATE_OPEN = 2; - private PanelViewController mPanel; @Nullable private PanelStateChangeListener mPanelStateChangeListener; private int mState = STATE_CLOSED; private boolean mTracking; + /** Updates the panel state if necessary. */ + public void updateState(@PanelState int state) { + if (DEBUG) LOG("update state: %d -> %d", mState, state); + if (mState != state) { + go(state); + } + } + private void go(@PanelState int state) { if (DEBUG) LOG("go state: %d -> %d", mState, state); mState = state; @@ -97,59 +103,11 @@ public abstract class PanelBar extends FrameLayout { super.onFinishInflate(); } - /** Set the PanelViewController */ - public void setPanel(PanelViewController pv) { - mPanel = pv; - pv.setBar(this); - } - /** Sets the listener that will be notified of panel state changes. */ public void setPanelStateChangeListener(PanelStateChangeListener listener) { mPanelStateChangeListener = listener; } - public boolean panelEnabled() { - return true; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - // Allow subclasses to implement enable/disable semantics - if (!panelEnabled()) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - Log.v(TAG, String.format("onTouch: all panels disabled, ignoring touch at (%d,%d)", - (int) event.getX(), (int) event.getY())); - } - return false; - } - - if (event.getAction() == MotionEvent.ACTION_DOWN) { - final PanelViewController panel = mPanel; - if (panel == null) { - // panel is not there, so we'll eat the gesture - Log.v(TAG, String.format("onTouch: no panel for touch at (%d,%d)", - (int) event.getX(), (int) event.getY())); - return true; - } - boolean enabled = panel.isEnabled(); - if (DEBUG) LOG("PanelBar.onTouch: state=%d ACTION_DOWN: panel %s %s", mState, panel, - (enabled ? "" : " (disabled)")); - if (!enabled) { - // panel is disabled, so we'll eat the gesture - Log.v(TAG, String.format( - "onTouch: panel (%s) is disabled, ignoring touch at (%d,%d)", - panel, (int) event.getX(), (int) event.getY())); - return true; - } - } - return mPanel == null || mPanel.getView().dispatchTouchEvent(event); - } - - /** - * Percentage of panel expansion offset, caused by pulling down on a heads-up. - */ - abstract void onPanelMinFractionChanged(float minFraction); - /** * @param frac the fraction from the expansion in [0, 1] * @param expanded whether the panel is currently expanded; this is independent from the @@ -167,7 +125,6 @@ public abstract class PanelBar extends FrameLayout { if (expanded) { if (mState == STATE_CLOSED) { go(STATE_OPENING); - onPanelPeeked(); } fullyClosed = false; fullyOpened = frac >= 1f; @@ -176,44 +133,16 @@ public abstract class PanelBar extends FrameLayout { go(STATE_OPEN); } else if (fullyClosed && !mTracking && mState != STATE_CLOSED) { go(STATE_CLOSED); - onPanelCollapsed(); } if (SPEW) LOG("panelExpansionChanged: end state=%d [%s%s ]", mState, fullyOpened?" fullyOpened":"", fullyClosed?" fullyClosed":""); } - public void collapsePanel(boolean animate, boolean delayed, float speedUpFactor) { - boolean waiting = false; - PanelViewController pv = mPanel; - if (animate && !pv.isFullyCollapsed()) { - pv.collapse(delayed, speedUpFactor); - waiting = true; - } else { - pv.resetViews(false /* animate */); - pv.setExpandedFraction(0); // just in case - } - if (DEBUG) LOG("collapsePanel: animate=%s waiting=%s", animate, waiting); - if (!waiting && mState != STATE_CLOSED) { - // it's possible that nothing animated, so we replicate the termination - // conditions of panelExpansionChanged here - go(STATE_CLOSED); - onPanelCollapsed(); - } - } - - public void onPanelPeeked() { - if (DEBUG) LOG("onPanelPeeked"); - } - public boolean isClosed() { return mState == STATE_CLOSED; } - public void onPanelCollapsed() { - if (DEBUG) LOG("onPanelCollapsed"); - } - public void onTrackingStarted() { mTracking = true; } @@ -222,14 +151,6 @@ public abstract class PanelBar extends FrameLayout { mTracking = false; } - public void onExpandingFinished() { - if (DEBUG) LOG("onExpandingFinished"); - } - - public void onClosingFinished() { - - } - /** An interface that will be notified of panel state changes. */ public interface PanelStateChangeListener { /** Called when the state changes. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 768567b8b474..e5296af86b81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -185,10 +185,9 @@ public abstract class PanelViewController { protected final SysuiStatusBarStateController mStatusBarStateController; protected final AmbientState mAmbientState; protected final LockscreenGestureLogger mLockscreenGestureLogger; + private final TouchHandler mTouchHandler; - protected void onExpandingFinished() { - mBar.onExpandingFinished(); - } + protected abstract void onExpandingFinished(); protected void onExpandingStarted() { } @@ -226,6 +225,7 @@ public abstract class PanelViewController { mView = view; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mLockscreenGestureLogger = lockscreenGestureLogger; + mTouchHandler = createTouchHandler(); mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { @@ -238,7 +238,7 @@ public abstract class PanelViewController { }); mView.addOnLayoutChangeListener(createLayoutChangeListener()); - mView.setOnTouchListener(createTouchHandler()); + mView.setOnTouchListener(mTouchHandler); mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener()); mResources = mView.getResources(); @@ -289,6 +289,10 @@ public abstract class PanelViewController { : mTouchSlop; } + protected TouchHandler getTouchHandler() { + return mTouchHandler; + } + private void addMovement(MotionEvent event) { // Add movement to velocity tracker using raw screen X and Y coordinates instead // of window coordinates because the window frame may be moving at the same time. @@ -385,11 +389,22 @@ public abstract class PanelViewController { final boolean expand; if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { - // If we get a cancel, put the shade back to the state it was in when the gesture - // started - if (onKeyguard) { + // If the keyguard is fading away, don't expand it again. This can happen if you're + // swiping to unlock, the app below the keyguard is in landscape, and the screen + // rotates while your finger is still down after the swipe to unlock. + if (mKeyguardStateController.isKeyguardFadingAway()) { + expand = false; + } else if (onKeyguard) { expand = true; + } else if (mKeyguardStateController.isKeyguardFadingAway()) { + // If we're in the middle of dismissing the keyguard, don't expand due to the + // cancelled gesture. Gesture cancellation during an unlock is expected in some + // situations, such keeping your finger down while swiping to unlock to an app + // that is locked in landscape (the rotation will cancel the touch event). + expand = false; } else { + // If we get a cancel, put the shade back to the state it was in when the + // gesture started expand = !mPanelClosedOnDown; } } else { @@ -443,6 +458,7 @@ public abstract class PanelViewController { protected void onTrackingStopped(boolean expand) { mTracking = false; mBar.onTrackingStopped(expand); + mStatusBar.onTrackingStopped(expand); updatePanelExpansionAndVisibility(); } @@ -450,6 +466,7 @@ public abstract class PanelViewController { endClosing(); mTracking = true; mBar.onTrackingStarted(); + mStatusBar.onTrackingStarted(); notifyExpandingStarted(); updatePanelExpansionAndVisibility(); } @@ -922,10 +939,7 @@ public abstract class PanelViewController { mView.removeCallbacks(mFlingCollapseRunnable); } - protected void onClosingFinished() { - mBar.onClosingFinished(); - } - + protected abstract void onClosingFinished(); protected void startUnlockHintAnimation() { @@ -1148,23 +1162,28 @@ public abstract class PanelViewController { return mView; } - public boolean isEnabled() { - return mView.isEnabled(); - } - public OnLayoutChangeListener createLayoutChangeListener() { return new OnLayoutChangeListener(); } - protected TouchHandler createTouchHandler() { - return new TouchHandler(); - } + protected abstract TouchHandler createTouchHandler(); protected OnConfigurationChangedListener createOnConfigurationChangedListener() { return new OnConfigurationChangedListener(); } - public class TouchHandler implements View.OnTouchListener { + public abstract class TouchHandler implements View.OnTouchListener { + /** + * Method called when a touch has occurred on {@link PhoneStatusBarView}. + * + * Touches that occur on the status bar view may have ramifications for the notification + * panel (e.g. a touch that pulls down the shade could start on the status bar), so we need + * to notify the panel controller when these touches occur. + * + * Returns true if the event was handled and false otherwise. + */ + public abstract boolean onTouchForwardedFromStatusBar(MotionEvent event); + public boolean onInterceptTouchEvent(MotionEvent event) { if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) { @@ -1430,4 +1449,8 @@ public abstract class PanelViewController { private void cancelJankMonitoring(int cuj) { InteractionJankMonitor.getInstance().cancel(cuj); } + + protected float getExpansionFraction() { + return mExpandedFraction; + } } 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 1cca4777da0a..06a31c94896a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -18,15 +18,12 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection; -import static java.lang.Float.isNaN; - import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.EventLog; import android.util.Log; import android.util.Pair; import android.view.DisplayCutout; @@ -39,7 +36,6 @@ import android.view.accessibility.AccessibilityEvent; import android.widget.LinearLayout; import com.android.systemui.Dependency; -import com.android.systemui.EventLogTags; import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; @@ -57,15 +53,6 @@ public class PhoneStatusBarView extends PanelBar { StatusBar mBar; private ScrimController mScrimController; - private float mMinFraction; - private Runnable mHideExpandedRunnable = new Runnable() { - @Override - public void run() { - if (mPanelFraction == 0.0f) { - mBar.makeExpandedInvisible(); - } - } - }; private DarkReceiver mBattery; private DarkReceiver mClock; private int mRotationOrientation = -1; @@ -79,15 +66,12 @@ public class PhoneStatusBarView extends PanelBar { @Nullable private List<StatusBar.ExpansionChangedListener> mExpansionChangedListeners; @Nullable - private PanelExpansionStateChangedListener mPanelExpansionStateChangedListener; - - private PanelEnabledProvider mPanelEnabledProvider; + private TouchEventHandler mTouchEventHandler; /** * Draw this many pixels into the left/right side of the cutout to optimally use the space */ private int mCutoutSideNudge = 0; - private boolean mHeadsUpVisible; public PhoneStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); @@ -103,8 +87,8 @@ public class PhoneStatusBarView extends PanelBar { mExpansionChangedListeners = listeners; } - void setPanelExpansionStateChangedListener(PanelExpansionStateChangedListener listener) { - mPanelExpansionStateChangedListener = listener; + void setTouchEventHandler(TouchEventHandler handler) { + mTouchEventHandler = handler; } public void setScrimController(ScrimController scrimController) { @@ -181,15 +165,6 @@ public class PhoneStatusBarView extends PanelBar { } @Override - public boolean panelEnabled() { - if (mPanelEnabledProvider == null) { - Log.e(TAG, "panelEnabledProvider is null; defaulting to super class."); - return super.panelEnabled(); - } - return mPanelEnabledProvider.panelEnabled(); - } - - @Override public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { if (super.onRequestSendAccessibilityEventInternal(child, event)) { // The status bar is very small so augment the view that the user is touching @@ -205,91 +180,31 @@ public class PhoneStatusBarView extends PanelBar { } @Override - public void onPanelPeeked() { - super.onPanelPeeked(); - mBar.makeExpandedVisible(false); - } - - @Override - public void onPanelCollapsed() { - super.onPanelCollapsed(); - // Close the status bar in the next frame so we can show the end of the animation. - post(mHideExpandedRunnable); - } - - public void removePendingHideExpandedRunnables() { - removeCallbacks(mHideExpandedRunnable); - } - - @Override public boolean onTouchEvent(MotionEvent event) { - boolean barConsumedEvent = mBar.interceptTouchEvent(event); - - if (DEBUG_GESTURES) { - if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { - EventLog.writeEvent(EventLogTags.SYSUI_PANELBAR_TOUCH, - event.getActionMasked(), (int) event.getX(), (int) event.getY(), - barConsumedEvent ? 1 : 0); - } + mBar.onTouchEvent(event); + if (mTouchEventHandler == null) { + Log.w( + TAG, + String.format( + "onTouch: No touch handler provided; eating gesture at (%d,%d)", + (int) event.getX(), + (int) event.getY() + ) + ); + return true; } - - return barConsumedEvent || super.onTouchEvent(event); - } - - @Override - public void onTrackingStarted() { - super.onTrackingStarted(); - mBar.onTrackingStarted(); - mScrimController.onTrackingStarted(); - removePendingHideExpandedRunnables(); - } - - @Override - public void onClosingFinished() { - super.onClosingFinished(); - mBar.onClosingFinished(); - } - - @Override - public void onTrackingStopped(boolean expand) { - super.onTrackingStopped(expand); - mBar.onTrackingStopped(expand); - } - - @Override - public void onExpandingFinished() { - super.onExpandingFinished(); - mScrimController.onExpandingFinished(); + return mTouchEventHandler.handleTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { - return mBar.interceptTouchEvent(event) || super.onInterceptTouchEvent(event); - } - - @Override - public void onPanelMinFractionChanged(float minFraction) { - if (isNaN(minFraction)) { - throw new IllegalArgumentException("minFraction cannot be NaN"); - } - if (mMinFraction != minFraction) { - mMinFraction = minFraction; - updateScrimFraction(); - } + mBar.onTouchEvent(event); + return super.onInterceptTouchEvent(event); } @Override public void panelExpansionChanged(float frac, boolean expanded) { super.panelExpansionChanged(frac, expanded); - updateScrimFraction(); - if ((frac == 0 || frac == 1)) { - if (mPanelExpansionStateChangedListener != null) { - mPanelExpansionStateChangedListener.onPanelExpansionStateChanged(); - } else { - Log.w(TAG, "No PanelExpansionStateChangedListener provided."); - } - } - if (mExpansionChangedListeners != null) { for (StatusBar.ExpansionChangedListener listener : mExpansionChangedListeners) { listener.onExpansionChanged(frac, expanded); @@ -297,20 +212,6 @@ public class PhoneStatusBarView extends PanelBar { } } - /** Set the {@link PanelEnabledProvider} to use. */ - public void setPanelEnabledProvider(PanelEnabledProvider panelEnabledProvider) { - mPanelEnabledProvider = panelEnabledProvider; - } - - private void updateScrimFraction() { - float scrimFraction = mPanelFraction; - if (mMinFraction < 1.0f) { - scrimFraction = Math.max((mPanelFraction - mMinFraction) / (1.0f - mMinFraction), - 0); - } - mScrimController.setPanelExpansion(scrimFraction); - } - public void updateResources() { mCutoutSideNudge = getResources().getDimensionPixelSize( R.dimen.display_cutout_margin_consumption); @@ -390,15 +291,14 @@ public class PhoneStatusBarView extends PanelBar { getPaddingBottom()); } - /** An interface that will provide whether panel is enabled. */ - interface PanelEnabledProvider { - /** 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(); + /** + * A handler repsonsible for all touch event handling on the status bar. + * + * The handler will be notified each time {@link this#onTouchEvent} is called, and the return + * value from the handler will be returned from {@link this#onTouchEvent}. + **/ + public interface TouchEventHandler { + /** Called each time {@link this#onTouchEvent} is called. */ + boolean handleTouchEvent(MotionEvent event); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index 4c0332a75df1..e27f4585e320 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -26,19 +26,15 @@ import com.android.systemui.util.ViewController /** Controller for [PhoneStatusBarView]. */ class PhoneStatusBarViewController( view: PhoneStatusBarView, - commandQueue: CommandQueue, statusBarMoveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?, - panelExpansionStateChangedListener: PhoneStatusBarView.PanelExpansionStateChangedListener, + touchEventHandler: PhoneStatusBarView.TouchEventHandler, ) : ViewController<PhoneStatusBarView>(view) { override fun onViewAttached() {} override fun onViewDetached() {} init { - mView.setPanelEnabledProvider { - commandQueue.panelsEnabled() - } - mView.setPanelExpansionStateChangedListener(panelExpansionStateChangedListener) + mView.setTouchEventHandler(touchEventHandler) statusBarMoveFromCenterAnimationController?.let { animationController -> val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index a564637aa510..1921357ddf7c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -34,6 +34,7 @@ import android.view.ViewTreeObserver; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; +import androidx.annotation.FloatRange; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; @@ -46,7 +47,7 @@ import com.android.settingslib.Utils; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; import com.android.systemui.R; -import com.android.systemui.animation.Interpolators; +import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dock.DockManager; @@ -183,8 +184,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private float mScrimBehindAlphaKeyguard = KEYGUARD_SCRIM_ALPHA; private final float mDefaultScrimAlpha; - // Assuming the shade is expanded during initialization - private float mPanelExpansion = 1f; + private float mRawPanelExpansionFraction; + private float mPanelScrimMinFraction; + // Calculated based on mRawPanelExpansionFraction and mPanelScrimMinFraction + private float mPanelExpansionFraction = 1f; // Assume shade is expanded during initialization private float mQsExpansion; private boolean mQsBottomVisible; @@ -262,11 +265,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } @Override - public void onOverlayChanged() { - ScrimController.this.onThemeChanged(); - } - - @Override public void onUiModeChanged() { ScrimController.this.onThemeChanged(); } @@ -483,14 +481,39 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump * * The expansion fraction is tied to the scrim opacity. * - * @param fraction From 0 to 1 where 0 means collapsed and 1 expanded. + * See {@link PanelBar#panelExpansionChanged}. + * + * @param rawPanelExpansionFraction From 0 to 1 where 0 means collapsed and 1 expanded. */ - public void setPanelExpansion(float fraction) { - if (isNaN(fraction)) { - throw new IllegalArgumentException("Fraction should not be NaN"); + public void setRawPanelExpansionFraction( + @FloatRange(from = 0.0, to = 1.0) float rawPanelExpansionFraction) { + if (isNaN(rawPanelExpansionFraction)) { + throw new IllegalArgumentException("rawPanelExpansionFraction should not be NaN"); } - if (mPanelExpansion != fraction) { - mPanelExpansion = fraction; + mRawPanelExpansionFraction = rawPanelExpansionFraction; + calculateAndUpdatePanelExpansion(); + } + + /** See {@link NotificationPanelViewController#setPanelScrimMinFraction(float)}. */ + public void setPanelScrimMinFraction(float minFraction) { + if (isNaN(minFraction)) { + throw new IllegalArgumentException("minFraction should not be NaN"); + } + mPanelScrimMinFraction = minFraction; + calculateAndUpdatePanelExpansion(); + } + + private void calculateAndUpdatePanelExpansion() { + float panelExpansionFraction = mRawPanelExpansionFraction; + if (mPanelScrimMinFraction < 1.0f) { + panelExpansionFraction = Math.max( + (mRawPanelExpansionFraction - mPanelScrimMinFraction) + / (1.0f - mPanelScrimMinFraction), + 0); + } + + if (mPanelExpansionFraction != panelExpansionFraction) { + mPanelExpansionFraction = panelExpansionFraction; boolean relevantState = (mState == ScrimState.UNLOCKED || mState == ScrimState.KEYGUARD @@ -556,8 +579,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (isNaN(expansionFraction)) { return; } - expansionFraction = Interpolators - .getNotificationScrimAlpha(expansionFraction, false /* notification */); + expansionFraction = ShadeInterpolation.getNotificationScrimAlpha(expansionFraction); boolean qsBottomVisible = qsPanelBottomY > 0; if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) { mQsExpansion = expansionFraction; @@ -652,6 +674,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } mInFrontAlpha = 0; } + } else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) { + float behindFraction = getInterpolatedFraction(); + behindFraction = (float) Math.pow(behindFraction, 0.8f); + + mBehindAlpha = behindFraction * mDefaultScrimAlpha; + mNotificationsAlpha = mBehindAlpha; } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED || mState == ScrimState.PULSING) { Pair<Integer, Float> result = calculateBackStateForState(mState); @@ -892,7 +920,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } private float getInterpolatedFraction() { - return Interpolators.getNotificationScrimAlpha(mPanelExpansion, false /* notification */); + return ShadeInterpolation.getNotificationScrimAlpha(mPanelExpansionFraction); } private void setScrimAlpha(ScrimView scrim, float alpha) { @@ -1228,8 +1256,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump pw.println(mTracking); pw.print(" mDefaultScrimAlpha="); pw.println(mDefaultScrimAlpha); - pw.print(" mExpansionFraction="); - pw.println(mPanelExpansion); + pw.print(" mPanelExpansionFraction="); + pw.println(mPanelExpansionFraction); pw.print(" mExpansionAffectsAlpha="); pw.println(mExpansionAffectsAlpha); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 850b98691e1b..9246c0e73289 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -87,6 +87,17 @@ public enum ScrimState { } }, + AUTH_SCRIMMED_SHADE { + @Override + public void prepare(ScrimState previousState) { + // notif & behind scrim alpha values are determined by ScrimController#applyState + // based on the shade expansion + + mFrontTint = Color.BLACK; + mFrontAlpha = .66f; + } + }, + AUTH_SCRIMMED { @Override public void prepare(ScrimState previousState) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java index 768222d34862..a54251a46901 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java @@ -128,7 +128,8 @@ public class ShadeControllerImpl implements ShadeController { mNotificationShadeWindowController.setNotificationShadeFocusable(false); getStatusBar().getNotificationShadeWindowViewController().cancelExpandHelper(); - getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor); + getNotificationPanelViewController() + .collapsePanel(true /* animate */, delayed, speedUpFactor); } else if (mBubblesOptional.isPresent()) { mBubblesOptional.get().collapseStack(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt index 4b7fe4e7ea29..a7ecd0619d26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone import android.view.View import com.android.systemui.R +import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.flags.FeatureFlags @@ -53,6 +54,14 @@ class SplitShadeHeaderController @Inject constructor( updateVisibility() } + var shadeExpandedFraction = -1f + set(value) { + if (visible && field != value) { + statusBar.alpha = ShadeInterpolation.getContentAlpha(value) + field = value + } + } + init { batteryMeterViewController.init() val batteryIcon: BatteryMeterView = statusBar.findViewById(R.id.batteryRemainingIcon) 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 78ab0d7f8a2f..caa0dbe1a2c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -336,6 +336,7 @@ public class StatusBar extends SystemUI implements } private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; + private boolean mCallingFadingAwayAfterReveal; private StatusBarCommandQueueCallbacks mCommandQueueCallbacks; void setWindowState(int state) { @@ -628,6 +629,7 @@ public class StatusBar extends SystemUI implements private final Executor mUiBgExecutor; protected boolean mDozing; + private boolean mIsFullscreen; private final NotificationMediaManager mMediaManager; private final NotificationLockscreenUserManager mLockscreenUserManager; @@ -898,6 +900,9 @@ public class StatusBar extends SystemUI implements lockscreenShadeTransitionController.setStatusbar(this); mExpansionChangedListeners = new ArrayList<>(); + addExpansionChangedListener( + (expansion, expanded) -> mScrimController.setRawPanelExpansionFraction(expansion)); + addExpansionChangedListener(this::onPanelExpansionChanged); mBubbleExpandListener = (isExpanding, key) -> mContext.getMainExecutor().execute(() -> { @@ -909,6 +914,9 @@ public class StatusBar extends SystemUI implements mActivityLaunchAnimator = activityLaunchAnimator; mDialogLaunchAnimator = dialogLaunchAnimator; + // The status bar background may need updating when the ongoing call status changes. + mOngoingCallController.addCallback((animate) -> maybeUpdateBarMode()); + // TODO(b/190746471): Find a better home for this. DateTimeView.setReceiverHandler(timeTickHandler); @@ -1160,7 +1168,6 @@ public class StatusBar extends SystemUI implements PhoneStatusBarView oldStatusBarView = mStatusBarView; mStatusBarView = (PhoneStatusBarView) statusBarFragment.getView(); mStatusBarView.setBar(this); - mStatusBarView.setPanel(mNotificationPanelViewController); mStatusBarView.setPanelStateChangeListener( mNotificationPanelViewController.getPanelStateChangeListener()); mStatusBarView.setScrimController(mScrimController); @@ -1169,6 +1176,8 @@ public class StatusBar extends SystemUI implements sendInitialExpansionAmount(listener); } + mNotificationPanelViewController.setBar(mStatusBarView); + StatusBarMoveFromCenterAnimationController moveFromCenterAnimation = null; if (mUnfoldTransitionConfig.isEnabled()) { moveFromCenterAnimation = mMoveFromCenterAnimation.get(); @@ -1176,9 +1185,9 @@ public class StatusBar extends SystemUI implements mPhoneStatusBarViewController = new PhoneStatusBarViewController( mStatusBarView, - mCommandQueue, moveFromCenterAnimation, - this::onPanelExpansionStateChanged); + mNotificationPanelViewController.getStatusBarTouchEventHandler() + ); mPhoneStatusBarViewController.init(); mBatteryMeterViewController = new BatteryMeterViewController( @@ -1309,6 +1318,7 @@ public class StatusBar extends SystemUI implements mNotificationPanelViewController.initDependencies( this, + this::makeExpandedInvisible, mNotificationShelfController); BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop); @@ -1400,6 +1410,12 @@ public class StatusBar extends SystemUI implements mDeviceProvisionedController.addCallback(mUserSetupObserver); mUserSetupObserver.onUserSetupChanged(); + for (ExpansionChangedListener listener : mExpansionChangedListeners) { + // The initial expansion amount comes from mNotificationPanelViewController, so we + // should send the amount once we've fully set up that controller. + sendInitialExpansionAmount(listener); + } + // disable profiling bars, since they overlap and clutter the output on app windows ThreadedRenderer.overrideProperty("disableProfileBars", "true"); @@ -1440,12 +1456,14 @@ public class StatusBar extends SystemUI implements } } - private void onPanelExpansionStateChanged() { - if (getNavigationBarView() != null) { - getNavigationBarView().onStatusBarPanelStateChanged(); - } - if (getNotificationPanelViewController() != null) { - getNotificationPanelViewController().updateSystemUiStateFlags(); + private void onPanelExpansionChanged(float frac, boolean expanded) { + if (frac == 0 || frac == 1) { + if (getNavigationBarView() != null) { + getNavigationBarView().onStatusBarPanelStateChanged(); + } + if (getNotificationPanelViewController() != null) { + getNotificationPanelViewController().updateSystemUiStateFlags(); + } } } @@ -2144,7 +2162,8 @@ public class StatusBar extends SystemUI implements public void animateCollapseQuickSettings() { if (mState == StatusBarState.SHADE) { - mStatusBarView.collapsePanel(true, false /* delayed */, 1.0f /* speedUpFactor */); + mNotificationPanelViewController.collapsePanel( + true, false /* delayed */, 1.0f /* speedUpFactor */); } } @@ -2157,7 +2176,7 @@ public class StatusBar extends SystemUI implements } // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868) - mStatusBarView.collapsePanel(/*animate=*/ false, false /* delayed*/, + mNotificationPanelViewController.collapsePanel(/*animate=*/ false, false /* delayed*/, 1.0f /* speedUpFactor */); mNotificationPanelViewController.closeQs(); @@ -2191,7 +2210,10 @@ public class StatusBar extends SystemUI implements } } - public boolean interceptTouchEvent(MotionEvent event) { + /** Called when a touch event occurred on {@link PhoneStatusBarView}. */ + public void onTouchEvent(MotionEvent event) { + // TODO(b/202981994): Move this touch debugging to a central location. (Right now, it's + // split between NotificationPanelViewController and here.) if (DEBUG_GESTURES) { if (event.getActionMasked() != MotionEvent.ACTION_MOVE) { EventLog.writeEvent(EventLogTags.SYSUI_STATUSBAR_TOUCH, @@ -2223,7 +2245,6 @@ public class StatusBar extends SystemUI implements event.getAction() == MotionEvent.ACTION_CANCEL; setInteracting(StatusBarManager.WINDOW_STATUS_BAR, !upOrCancel || mExpandedVisible); } - return false; } boolean isSameStatusBarState(int state) { @@ -2242,7 +2263,7 @@ public class StatusBar extends SystemUI implements if (!mTransientShown) { mTransientShown = true; mNoAnimationOnNextBarModeChange = true; - handleTransientChanged(); + maybeUpdateBarMode(); } } @@ -2250,11 +2271,11 @@ public class StatusBar extends SystemUI implements void clearTransient() { if (mTransientShown) { mTransientShown = false; - handleTransientChanged(); + maybeUpdateBarMode(); } } - private void handleTransientChanged() { + private void maybeUpdateBarMode() { final int barMode = barMode(mTransientShown, mAppearance); if (updateBarMode(barMode)) { mLightBarController.onStatusBarModeChanged(barMode); @@ -2272,9 +2293,11 @@ public class StatusBar extends SystemUI implements return false; } - private static @TransitionMode int barMode(boolean isTransient, int appearance) { + private @TransitionMode int barMode(boolean isTransient, int appearance) { final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_STATUS_BARS; - if (isTransient) { + if (mOngoingCallController.hasOngoingCall() && mIsFullscreen) { + return MODE_SEMI_TRANSPARENT; + } else if (isTransient) { return MODE_SEMI_TRANSPARENT; } else if ((appearance & lightsOutOpaque) == lightsOutOpaque) { return MODE_LIGHTS_OUT; @@ -3125,8 +3148,20 @@ public class StatusBar extends SystemUI implements public void fadeKeyguardWhilePulsing() { mNotificationPanelViewController.fadeOut(0, FADE_KEYGUARD_DURATION_PULSING, ()-> { - hideKeyguard(); - mStatusBarKeyguardViewManager.onKeyguardFadedAway(); + Runnable finishFading = () -> { + mCallingFadingAwayAfterReveal = false; + hideKeyguard(); + mStatusBarKeyguardViewManager.onKeyguardFadedAway(); + }; + if (mLightRevealScrim.getRevealAmount() != 1.0f) { + mCallingFadingAwayAfterReveal = true; + // We're still revealing the Light reveal, let's only go to keyguard once + // that has finished and nothing moves anymore. + // Going there introduces lots of jank + mLightRevealScrim.setFullyRevealedRunnable(finishFading); + } else { + finishFading.run(); + } }).start(); } @@ -3260,16 +3295,16 @@ public class StatusBar extends SystemUI implements * Switches theme from light to dark and vice-versa. */ protected void updateTheme() { - // Lock wallpaper defines the color of the majority of the views, hence we'll use it // to set our default theme. final boolean lockDarkText = mColorExtractor.getNeutralColors().supportsDarkText(); final int themeResId = lockDarkText ? R.style.Theme_SystemUI_LightWallpaper : R.style.Theme_SystemUI; - if (mContext.getThemeResId() != themeResId) { - mContext.setTheme(themeResId); - mConfigurationController.notifyThemeChanged(); + if (mContext.getThemeResId() == themeResId) { + return; } + mContext.setTheme(themeResId); + mConfigurationController.notifyThemeChanged(); } private void updateDozingState() { @@ -3810,7 +3845,11 @@ public class StatusBar extends SystemUI implements mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview); if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { - mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED); + if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED) { + mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE); + } else { + mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED); + } } else if (mBouncerShowing) { // Bouncer needs the front scrim when it's on top of an activity, // tapping on a notification, editing QS or being dismissed by @@ -4227,9 +4266,11 @@ public class StatusBar extends SystemUI implements } private void sendInitialExpansionAmount(ExpansionChangedListener expansionChangedListener) { - expansionChangedListener.onExpansionChanged( - mNotificationPanelViewController.getExpandedFraction(), - mNotificationPanelViewController.isExpanded()); + if (mNotificationPanelViewController != null) { + expansionChangedListener.onExpansionChanged( + mNotificationPanelViewController.getExpandedFraction(), + mNotificationPanelViewController.isExpanded()); + } } public void removeExpansionChangedListener(@NonNull ExpansionChangedListener listener) { @@ -4285,7 +4326,7 @@ public class StatusBar extends SystemUI implements + "mStatusBarKeyguardViewManager was null"); return; } - if (mKeyguardStateController.isKeyguardFadingAway()) { + if (mKeyguardStateController.isKeyguardFadingAway() && !mCallingFadingAwayAfterReveal) { mStatusBarKeyguardViewManager.onKeyguardFadedAway(); } } @@ -4380,6 +4421,13 @@ public class StatusBar extends SystemUI implements @Override public void onThemeChanged() { + if (mBrightnessMirrorController != null) { + mBrightnessMirrorController.onOverlayChanged(); + } + // We need the new R.id.keyguard_indication_area before recreating + // mKeyguardIndicationController + mNotificationPanelViewController.onThemeChanged(); + if (mStatusBarKeyguardViewManager != null) { mStatusBarKeyguardViewManager.onThemeChanged(); } @@ -4390,17 +4438,6 @@ public class StatusBar extends SystemUI implements } @Override - public void onOverlayChanged() { - if (mBrightnessMirrorController != null) { - mBrightnessMirrorController.onOverlayChanged(); - } - // We need the new R.id.keyguard_indication_area before recreating - // mKeyguardIndicationController - mNotificationPanelViewController.onThemeChanged(); - onThemeChanged(); - } - - @Override public void onUiModeChanged() { if (mBrightnessMirrorController != null) { mBrightnessMirrorController.onUiModeChanged(); @@ -4435,7 +4472,7 @@ public class StatusBar extends SystemUI implements mNavigationBarController.touchAutoDim(mDisplayId); Trace.beginSection("StatusBar#updateKeyguardState"); if (mState == StatusBarState.KEYGUARD && mStatusBarView != null) { - mStatusBarView.removePendingHideExpandedRunnables(); + mNotificationPanelViewController.cancelPendingPanelCollapse(); } updateDozingState(); checkBarModes(); @@ -4476,6 +4513,12 @@ public class StatusBar extends SystemUI implements updateReportRejectedTouchVisibility(); Trace.endSection(); } + + @Override + public void onFullscreenStateChanged(boolean isFullscreen) { + mIsFullscreen = isFullscreen; + maybeUpdateBarMode(); + } }; private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback = 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 5301b2571534..bb1daa252cdf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java @@ -536,7 +536,7 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { } if (mStatusBar.getStatusBarView() != null) { if (!showing && mStatusBarStateController.getState() == StatusBarState.SHADE) { - mStatusBar.getStatusBarView().collapsePanel( + mNotificationPanelViewController.collapsePanel( false /* animate */, false /* delayed */, 1.0f /* speedUpFactor */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt index 61552f065bc4..5bfb2361d4a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt @@ -91,7 +91,7 @@ class StatusBarContentInsetsProvider @Inject constructor( clearCachedInsets() } - override fun onOverlayChanged() { + override fun onThemeChanged() { clearCachedInsets() } @@ -179,6 +179,11 @@ class StatusBarContentInsetsProvider @Inject constructor( minRight) } + fun getStatusBarPaddingTop(@Rotation rotation: Int? = null): Int { + val res = rotation?.let { it -> getResourcesForRotation(it, context) } ?: context.resources + return res.getDimensionPixelSize(R.dimen.status_bar_padding_top) + } + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { insetsCache.snapshot().forEach { (key, rect) -> pw.println("$key -> $rect") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 832f317d5783..c655964e64bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -25,7 +25,6 @@ import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; -import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; @@ -248,7 +247,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, } @Override - public void onOverlayChanged() { + public void onThemeChanged() { onDensityOrFontScaleChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index d3d90639546a..eb405e9bdea3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -83,7 +83,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { } @Override - public void onOverlayChanged() { + public void onThemeChanged() { initResources(); } }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java index b708861ecc74..235a8e85a223 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java @@ -208,9 +208,18 @@ public class StatusBarWindowController { apply(mCurrentState); } - /** Sets whether there is currently an ongoing call. */ - public void setIsCallOngoing(boolean isCallOngoing) { - mCurrentState.mIsCallOngoing = isCallOngoing; + /** + * Sets whether an ongoing process requires the status bar to be forced visible. + * + * This method is separate from {@link this#setForceStatusBarVisible} because the ongoing + * process **takes priority**. For example, if {@link this#setForceStatusBarVisible} is set to + * false but this method is set to true, then the status bar **will** be visible. + * + * TODO(b/195839150): We should likely merge this method and + * {@link this#setForceStatusBarVisible} together and use some sort of ranking system instead. + */ + public void setOngoingProcessRequiresStatusBarVisible(boolean visible) { + mCurrentState.mOngoingProcessRequiresStatusBarVisible = visible; apply(mCurrentState); } @@ -254,13 +263,14 @@ public class StatusBarWindowController { private static class State { boolean mForceStatusBarVisible; boolean mIsLaunchAnimationRunning; - boolean mIsCallOngoing; + boolean mOngoingProcessRequiresStatusBarVisible; } private void applyForceStatusBarVisibleFlag(State state) { if (state.mForceStatusBarVisible || state.mIsLaunchAnimationRunning - || state.mIsCallOngoing) { + // Don't force-show the status bar if the user has already dismissed it. + || state.mOngoingProcessRequiresStatusBarVisible) { mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; } else { mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java index 0c5502bac8fc..26ba31c6f526 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainViewController.java @@ -43,11 +43,6 @@ public class TapAgainViewController extends ViewController<TapAgainView> { @VisibleForTesting final ConfigurationListener mConfigurationListener = new ConfigurationListener() { @Override - public void onOverlayChanged() { - mView.updateColor(); - } - - @Override public void onUiModeChanged() { mView.updateColor(); } 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 143aaba648da..fdc0ec5b9089 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -4,10 +4,10 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context -import android.content.res.Configuration import android.database.ContentObserver import android.os.Handler import android.provider.Settings +import android.view.Surface import android.view.View import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton @@ -226,6 +226,12 @@ class UnlockedScreenOffAnimationController @Inject constructor( return false } + // If animations are disabled system-wide, don't play this one either. + if (Settings.Global.getString( + context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE) == "0") { + return false + } + // We only play the unlocked screen off animation if we are... unlocked. if (statusBarStateControllerImpl.state != StatusBarState.SHADE) { return false @@ -239,10 +245,11 @@ class UnlockedScreenOffAnimationController @Inject constructor( return false } - // If we're not allowed to rotate the keyguard, then only do the screen off animation if - // we're in portrait. Otherwise, AOD will animate in sideways, which looks weird. + // If we're not allowed to rotate the keyguard, it can only be displayed in zero-degree + // portrait. If we're in another orientation, disable the screen off animation so we don't + // animate in the keyguard AOD UI sideways or upside down. if (!keyguardStateController.isKeyguardScreenRotationAllowed && - context.resources.configuration.orientation != Configuration.ORIENTATION_PORTRAIT) { + context.display.rotation != Surface.ROTATION_0) { return false } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index b7c80def7dad..3806d9a2925c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -34,6 +34,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.flags.FeatureFlags +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener @@ -60,8 +62,11 @@ class OngoingCallController @Inject constructor( private val logger: OngoingCallLogger, private val dumpManager: DumpManager, private val statusBarWindowController: Optional<StatusBarWindowController>, + private val swipeStatusBarAwayGestureHandler: Optional<SwipeStatusBarAwayGestureHandler>, + private val statusBarStateController: StatusBarStateController, ) : CallbackController<OngoingCallListener>, Dumpable { + private var isFullscreen: Boolean = false /** Non-null if there's an active call notification. */ private var callNotificationInfo: CallNotificationInfo? = null /** True if the application managing the call is visible to the user. */ @@ -96,7 +101,8 @@ class OngoingCallController @Inject constructor( entry.sbn.notification.contentIntent?.intent, entry.sbn.uid, entry.sbn.notification.extras.getInt( - Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING + Notification.EXTRA_CALL_TYPE, -1) == CALL_TYPE_ONGOING, + statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false ) if (newOngoingCallInfo == callNotificationInfo) { return @@ -122,6 +128,7 @@ class OngoingCallController @Inject constructor( dumpManager.registerDumpable(this) if (featureFlags.isOngoingCallStatusBarChipEnabled) { notifCollection.addCollectionListener(notifListener) + statusBarStateController.addCallback(statusBarStateListener) } } @@ -175,10 +182,8 @@ class OngoingCallController @Inject constructor( val currentChipView = chipView val timeView = currentChipView?.getTimeView() - val backgroundView = - currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background) - if (currentChipView != null && timeView != null && backgroundView != null) { + if (currentChipView != null && timeView != null) { if (currentCallNotificationInfo.hasValidStartTime()) { timeView.setShouldHideText(false) timeView.base = currentCallNotificationInfo.callStartTime - @@ -189,22 +194,15 @@ class OngoingCallController @Inject constructor( timeView.setShouldHideText(true) timeView.stop() } + updateChipClickListener() - currentCallNotificationInfo.intent?.let { intent -> - currentChipView.setOnClickListener { - logger.logChipClicked() - activityStarter.postStartActivityDismissingKeyguard( - intent, - 0, - ActivityLaunchAnimator.Controller.fromView( - backgroundView, - InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP) - ) + setUpUidObserver(currentCallNotificationInfo) + if (!currentCallNotificationInfo.statusBarSwipedAway) { + statusBarWindowController.ifPresent { + it.setOngoingProcessRequiresStatusBarVisible(true) } } - - setUpUidObserver(currentCallNotificationInfo) - statusBarWindowController.ifPresent { it.setIsCallOngoing(true) } + updateGestureListening() mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) } } else { // If we failed to update the chip, don't store the call info. Then [hasOngoingCall] @@ -218,6 +216,30 @@ class OngoingCallController @Inject constructor( } } + private fun updateChipClickListener() { + if (callNotificationInfo == null) { return } + if (isFullscreen && !featureFlags.isOngoingCallInImmersiveChipTapEnabled) { + chipView?.setOnClickListener(null) + } else { + val currentChipView = chipView + val backgroundView = + currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background) + val intent = callNotificationInfo?.intent + if (currentChipView != null && backgroundView != null && intent != null) { + currentChipView.setOnClickListener { + logger.logChipClicked() + activityStarter.postStartActivityDismissingKeyguard( + intent, + 0, + ActivityLaunchAnimator.Controller.fromView( + backgroundView, + InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP) + ) + } + } + } + } + /** * Sets up an [IUidObserver] to monitor the status of the application managing the ongoing call. */ @@ -268,10 +290,23 @@ class OngoingCallController @Inject constructor( return procState <= ActivityManager.PROCESS_STATE_TOP } + private fun updateGestureListening() { + if (callNotificationInfo == null + || callNotificationInfo?.statusBarSwipedAway == true + || !isFullscreen) { + swipeStatusBarAwayGestureHandler.ifPresent { it.removeOnGestureDetectedCallback(TAG) } + } else { + swipeStatusBarAwayGestureHandler.ifPresent { + it.addOnGestureDetectedCallback(TAG, this::onSwipeAwayGestureDetected) + } + } + } + private fun removeChip() { callNotificationInfo = null tearDownChipView() - statusBarWindowController.ifPresent { it.setIsCallOngoing(false) } + statusBarWindowController.ifPresent { it.setOngoingProcessRequiresStatusBarVisible(false) } + swipeStatusBarAwayGestureHandler.ifPresent { it.removeOnGestureDetectedCallback(TAG) } mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) } if (uidObserver != null) { iActivityManager.unregisterUidObserver(uidObserver) @@ -286,13 +321,42 @@ class OngoingCallController @Inject constructor( return this.findViewById(R.id.ongoing_call_chip_time) } + /** + * If there's an active ongoing call, then we will force the status bar to always show, even if + * the user is in immersive mode. However, we also want to give users the ability to swipe away + * the status bar if they need to access the area under the status bar. + * + * This method updates the status bar window appropriately when the swipe away gesture is + * detected. + */ + private fun onSwipeAwayGestureDetected() { + if (DEBUG) { Log.d(TAG, "Swipe away gesture detected") } + callNotificationInfo = callNotificationInfo?.copy(statusBarSwipedAway = true) + statusBarWindowController.ifPresent { + it.setOngoingProcessRequiresStatusBarVisible(false) + } + swipeStatusBarAwayGestureHandler.ifPresent { + it.removeOnGestureDetectedCallback(TAG) + } + } + + private val statusBarStateListener = object : StatusBarStateController.StateListener { + override fun onFullscreenStateChanged(isFullscreen: Boolean) { + this@OngoingCallController.isFullscreen = isFullscreen + updateChipClickListener() + updateGestureListening() + } + } + private data class CallNotificationInfo( val key: String, val callStartTime: Long, val intent: Intent?, val uid: Int, /** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */ - val isOngoing: Boolean + val isOngoing: Boolean, + /** True if the user has swiped away the status bar while in this phone call. */ + val statusBarSwipedAway: Boolean ) { /** * Returns true if the notification information has a valid call start time. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java index d38284a26a07..479ca8f1ff96 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java @@ -14,7 +14,6 @@ package com.android.systemui.statusbar.policy; -import android.content.Context; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; @@ -35,8 +34,8 @@ public class AccessibilityManagerWrapper implements private final AccessibilityManager mAccessibilityManager; @Inject - public AccessibilityManagerWrapper(Context context) { - mAccessibilityManager = context.getSystemService(AccessibilityManager.class); + public AccessibilityManagerWrapper(AccessibilityManager accessibilityManager) { + mAccessibilityManager = accessibilityManager; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java index e679c4c97f18..6b80a9dab7cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java @@ -38,7 +38,6 @@ public interface ConfigurationController extends CallbackController<Configuratio default void onDensityOrFontScaleChanged() {} default void onSmallestScreenWidthChanged() {} default void onMaxBoundsChanged() {} - default void onOverlayChanged() {} default void onUiModeChanged() {} default void onThemeChanged() {} default void onLocaleListChanged() {} 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 d838a05135e7..b4aba380d971 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -35,10 +35,12 @@ import com.android.keyguard.dagger.KeyguardUserSwitcherScope; import com.android.settingslib.drawable.CircleFramedDrawable; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.tiles.UserDetailView; +import com.android.systemui.qs.user.UserSwitchDialogController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -76,6 +78,8 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> private final ConfigurationController mConfigurationController; private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; private final KeyguardUserDetailAdapter mUserDetailAdapter; + private final FeatureFlags mFeatureFlags; + private final UserSwitchDialogController mUserSwitchDialogController; private NotificationPanelViewController mNotificationPanelViewController; private UserAvatarView mUserAvatarView; UserSwitcherController.UserRecord mCurrentUser; @@ -124,7 +128,9 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> SysuiStatusBarStateController statusBarStateController, DozeParameters dozeParameters, Provider<UserDetailView.Adapter> userDetailViewAdapterProvider, - UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, + FeatureFlags featureFlags, + UserSwitchDialogController userSwitchDialogController) { super(view); if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController"); mContext = context; @@ -139,6 +145,8 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> keyguardStateController, dozeParameters, unlockedScreenOffAnimationController, /* animateYPos= */ false); mUserDetailAdapter = new KeyguardUserDetailAdapter(context, userDetailViewAdapterProvider); + mFeatureFlags = featureFlags; + mUserSwitchDialogController = userSwitchDialogController; } @Override @@ -162,7 +170,11 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> } // Tapping anywhere in the view will open QS user panel - openQsUserPanel(); + if (mFeatureFlags.useNewUserSwitcher()) { + mUserSwitchDialogController.showDialog(mView); + } else { + openQsUserPanel(); + } }); mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index dadc01664b4d..b630689567ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -59,6 +59,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.LatencyTracker; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.systemui.Dumpable; import com.android.systemui.GuestResumeSessionReceiver; @@ -128,6 +129,7 @@ public class UserSwitcherController implements Dumpable { private final TelephonyListenerManager mTelephonyListenerManager; private final IActivityTaskManager mActivityTaskManager; private final InteractionJankMonitor mInteractionJankMonitor; + private final LatencyTracker mLatencyTracker; private ArrayList<UserRecord> mUsers = new ArrayList<>(); @VisibleForTesting @@ -174,6 +176,7 @@ public class UserSwitcherController implements Dumpable { SecureSettings secureSettings, @Background Executor bgExecutor, InteractionJankMonitor interactionJankMonitor, + LatencyTracker latencyTracker, DumpManager dumpManager) { mContext = context; mActivityManager = activityManager; @@ -184,6 +187,7 @@ public class UserSwitcherController implements Dumpable { mUiEventLogger = uiEventLogger; mFalsingManager = falsingManager; mInteractionJankMonitor = interactionJankMonitor; + mLatencyTracker = latencyTracker; mGuestResumeSessionReceiver = new GuestResumeSessionReceiver( this, mUserTracker, mUiEventLogger, secureSettings); mUserDetailAdapter = userDetailAdapter; @@ -499,6 +503,7 @@ public class UserSwitcherController implements Dumpable { mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mRootView) .setTimeout(MULTI_USER_JOURNEY_TIMEOUT)); + mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH); pauseRefreshUsers(); mActivityManager.switchUser(id); } catch (RemoteException e) { diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index 3c3cc64a49cd..f0760d4e2187 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -19,13 +19,27 @@ import android.content.Context import android.graphics.PixelFormat import android.hardware.devicestate.DeviceStateManager import android.hardware.devicestate.DeviceStateManager.FoldStateListener +import android.hardware.display.DisplayManager +import android.os.Handler +import android.os.Trace +import android.view.Choreographer +import android.view.Display +import android.view.DisplayInfo import android.view.Surface +import android.view.SurfaceControl +import android.view.SurfaceControlViewHost +import android.view.SurfaceSession import android.view.WindowManager +import android.view.WindowlessWindowManager import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.dagger.qualifiers.UiBackground +import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.LinearLightRevealEffect +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.wm.shell.displayareahelper.DisplayAreaHelper +import java.util.Optional import java.util.concurrent.Executor import java.util.function.Consumer import javax.inject.Inject @@ -34,53 +48,148 @@ import javax.inject.Inject class UnfoldLightRevealOverlayAnimation @Inject constructor( private val context: Context, private val deviceStateManager: DeviceStateManager, + private val displayManager: DisplayManager, private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider, + private val displayAreaHelper: Optional<DisplayAreaHelper>, @Main private val executor: Executor, - private val windowManager: WindowManager + @Main private val handler: Handler, + @UiBackground private val backgroundExecutor: Executor ) { private val transitionListener = TransitionListener() + private val displayListener = DisplayChangeListener() + + private lateinit var wwm: WindowlessWindowManager + private lateinit var unfoldedDisplayInfo: DisplayInfo + private lateinit var overlayContainer: SurfaceControl + + private var root: SurfaceControlViewHost? = null private var scrimView: LightRevealScrim? = null + private var isFolded: Boolean = false + private var isUnfoldHandled: Boolean = true + + private var currentRotation: Int = context.display!!.rotation fun init() { deviceStateManager.registerCallback(executor, FoldListener()) unfoldTransitionProgressProvider.addCallback(transitionListener) - } - private inner class TransitionListener : TransitionProgressListener { + val containerBuilder = SurfaceControl.Builder(SurfaceSession()) + .setContainerLayer() + .setName("unfold-overlay-container") - override fun onTransitionProgress(progress: Float) { - scrimView?.revealAmount = progress - } + displayAreaHelper.get().attachToRootDisplayArea(Display.DEFAULT_DISPLAY, + containerBuilder) { builder -> + executor.execute { + overlayContainer = builder.build() - override fun onTransitionFinished() { - removeOverlayView() + SurfaceControl.Transaction() + .setLayer(overlayContainer, Integer.MAX_VALUE) + .show(overlayContainer) + .apply() + + wwm = WindowlessWindowManager(context.resources.configuration, + overlayContainer, null) + } } - override fun onTransitionStarted() { - // When unfolding the view is added earlier, add view for folding case - if (scrimView == null) { - addOverlayView() + displayManager.registerDisplayListener(displayListener, handler, + DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) + + // Get unfolded display size immediately as 'current display info' might be + // not up-to-date during unfolding + unfoldedDisplayInfo = getUnfoldedDisplayInfo() + } + + /** + * Called when screen starts turning on, the contents of the screen might not be visible yet. + * This method reports back that the overlay is ready in [onOverlayReady] callback. + * + * @param onOverlayReady callback when the overlay is drawn and visible on the screen + * @see [com.android.systemui.keyguard.KeyguardViewMediator] + */ + fun onScreenTurningOn(onOverlayReady: Runnable) { + Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn") + try { + // Add the view only if we are unfolding and this is the first screen on + if (!isFolded && !isUnfoldHandled) { + addView(onOverlayReady) + isUnfoldHandled = true + } else { + // No unfold transition, immediately report that overlay is ready + ensureOverlayRemoved() + onOverlayReady.run() } + } finally { + Trace.endSection() } } - private inner class FoldListener : FoldStateListener(context, Consumer { isFolded -> - if (isFolded) { - removeOverlayView() - } else { - // Add overlay view before starting the transition as soon as we unfolded the device - addOverlayView() + private fun addView(onOverlayReady: Runnable? = null) { + if (!::wwm.isInitialized) { + // Surface overlay is not created yet on the first SysUI launch + onOverlayReady?.run() + return } - }) - private fun addOverlayView() { + ensureOverlayRemoved() + + val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, false) + val newView = LightRevealScrim(context, null) + .apply { + revealEffect = createLightRevealEffect() + isScrimOpaqueChangedListener = Consumer {} + revealAmount = 0f + } + + val params = getLayoutParams() + newRoot.setView(newView, params) + + onOverlayReady?.let { callback -> + Trace.beginAsyncSection( + "UnfoldLightRevealOverlayAnimation#relayout", 0) + + newRoot.relayout(params) { transaction -> + val vsyncId = Choreographer.getSfInstance().vsyncId + + backgroundExecutor.execute { + // Apply the transaction that contains the first frame of the overlay + // synchronously and apply another empty transaction with + // 'vsyncId + 1' to make sure that it is actually displayed on + // the screen. The second transaction is necessary to remove the screen blocker + // (turn on the brightness) only when the content is actually visible as it + // might be presented only in the next frame. + // See b/197538198 + transaction.setFrameTimelineVsync(vsyncId) + .apply(/* sync */true) + + transaction + .setFrameTimelineVsync(vsyncId + 1) + .apply(/* sync */ true) + + Trace.endAsyncSection( + "UnfoldLightRevealOverlayAnimation#relayout", 0) + callback.run() + } + } + } + + scrimView = newView + root = newRoot + } + + private fun getLayoutParams(): WindowManager.LayoutParams { val params: WindowManager.LayoutParams = WindowManager.LayoutParams() - params.height = WindowManager.LayoutParams.MATCH_PARENT - params.width = WindowManager.LayoutParams.MATCH_PARENT - params.format = PixelFormat.TRANSLUCENT - // TODO(b/193801466): create a separate type for this overlay + val rotation = context.display!!.rotation + val isNatural = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 + + params.height = if (isNatural) + unfoldedDisplayInfo.naturalHeight else unfoldedDisplayInfo.naturalWidth + params.width = if (isNatural) + unfoldedDisplayInfo.naturalWidth else unfoldedDisplayInfo.naturalHeight + + params.format = PixelFormat.TRANSLUCENT params.type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY params.title = "Unfold Light Reveal Animation" params.layoutInDisplayCutoutMode = @@ -90,41 +199,72 @@ class UnfoldLightRevealOverlayAnimation @Inject constructor( or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) params.setTrustedOverlay() - val rotation = windowManager.defaultDisplay.rotation - val isVerticalFold = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 + val packageName: String = context.opPackageName + params.packageName = packageName - val newScrimView = LightRevealScrim(context, null) - .apply { - revealEffect = LinearLightRevealEffect(isVerticalFold) - isScrimOpaqueChangedListener = Consumer {} - revealAmount = 0f - } + return params + } - val packageName: String = newScrimView.context.opPackageName - params.packageName = packageName - params.hideTimeoutMilliseconds = OVERLAY_HIDE_TIMEOUT_MILLIS + private fun createLightRevealEffect(): LightRevealEffect { + val isVerticalFold = currentRotation == Surface.ROTATION_0 || + currentRotation == Surface.ROTATION_180 + return LinearLightRevealEffect(isVertical = isVerticalFold) + } + + private fun ensureOverlayRemoved() { + root?.release() + root = null + scrimView = null + } - if (scrimView?.parent != null) { - windowManager.removeView(scrimView) + private fun getUnfoldedDisplayInfo(): DisplayInfo = + displayManager.displays + .asSequence() + .map { DisplayInfo().apply { it.getDisplayInfo(this) } } + .filter { it.type == Display.TYPE_INTERNAL } + .maxByOrNull { it.naturalWidth }!! + + private inner class TransitionListener : TransitionProgressListener { + + override fun onTransitionProgress(progress: Float) { + scrimView?.revealAmount = progress } - this.scrimView = newScrimView + override fun onTransitionFinished() { + ensureOverlayRemoved() + } - try { - windowManager.addView(scrimView, params) - } catch (e: WindowManager.BadTokenException) { - e.printStackTrace() + override fun onTransitionStarted() { + // Add view for folding case (when unfolding the view is added earlier) + if (scrimView == null) { + addView() + } } } - private fun removeOverlayView() { - scrimView?.let { - if (it.parent != null) { - windowManager.removeViewImmediate(it) + private inner class DisplayChangeListener : DisplayManager.DisplayListener { + + override fun onDisplayChanged(displayId: Int) { + val newRotation: Int = context.display!!.rotation + if (currentRotation != newRotation) { + currentRotation = newRotation + scrimView?.revealEffect = createLightRevealEffect() + root?.relayout(getLayoutParams()) } - scrimView = null + } + + override fun onDisplayAdded(displayId: Int) { + } + + override fun onDisplayRemoved(displayId: Int) { } } -} -private const val OVERLAY_HIDE_TIMEOUT_MILLIS = 10_000L + private inner class FoldListener : FoldStateListener(context, Consumer { isFolded -> + if (isFolded) { + ensureOverlayRemoved() + isUnfoldHandled = false + } + this.isFolded = isFolded + }) +} diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt index 8dd3d6beb9c3..4f45aafce416 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionWallpaperController.kt @@ -33,7 +33,14 @@ class UnfoldTransitionWallpaperController @Inject constructor( private inner class TransitionListener : TransitionProgressListener { override fun onTransitionProgress(progress: Float) { - wallpaperController.setUnfoldTransitionZoom(progress) + // Fully zoomed in when fully unfolded + wallpaperController.setUnfoldTransitionZoom(1 - progress) + } + + override fun onTransitionFinished() { + // Resets wallpaper zoom-out to 0f when fully folded + // When fully unfolded it is set to 0f by onTransitionProgress + wallpaperController.setUnfoldTransitionZoom(0f) } } } diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java index 0ba072e9e72f..f97f4d0edc24 100644 --- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java +++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java @@ -31,7 +31,6 @@ import java.util.Arrays; public class NotificationChannels extends SystemUI { public static String ALERTS = "ALR"; - public static String SCREENSHOTS_LEGACY = "SCN"; public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP"; public static String GENERAL = "GEN"; public static String STORAGE = "DSK"; @@ -84,18 +83,11 @@ public class NotificationChannels extends SystemUI { general, storage, createScreenshotChannel( - context.getString(R.string.notification_channel_screenshot), - nm.getNotificationChannel(SCREENSHOTS_LEGACY)), + context.getString(R.string.notification_channel_screenshot)), batteryChannel, hint )); - // Delete older SS channel if present. - // Screenshots promoted to heads-up in P, this cleans up the lower priority channel from O. - // This line can be deleted in Q. - nm.deleteNotificationChannel(SCREENSHOTS_LEGACY); - - if (isTv(context)) { // TV specific notification channel for TV PIP controls. // Importance should be {@link NotificationManager#IMPORTANCE_MAX} to have the highest @@ -113,7 +105,7 @@ public class NotificationChannels extends SystemUI { * @return */ @VisibleForTesting static NotificationChannel createScreenshotChannel( - String name, NotificationChannel legacySS) { + String name) { NotificationChannel screenshotChannel = new NotificationChannel(SCREENSHOTS_HEADSUP, name, NotificationManager.IMPORTANCE_HIGH); // pop on screen @@ -121,24 +113,6 @@ public class NotificationChannels extends SystemUI { new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build()); screenshotChannel.setBlockable(true); - if (legacySS != null) { - // Respect any user modified fields from the old channel. - int userlock = legacySS.getUserLockedFields(); - if ((userlock & NotificationChannel.USER_LOCKED_IMPORTANCE) != 0) { - screenshotChannel.setImportance(legacySS.getImportance()); - } - if ((userlock & NotificationChannel.USER_LOCKED_SOUND) != 0) { - screenshotChannel.setSound(legacySS.getSound(), legacySS.getAudioAttributes()); - } - if ((userlock & NotificationChannel.USER_LOCKED_VIBRATION) != 0) { - screenshotChannel.setVibrationPattern(legacySS.getVibrationPattern()); - } - if ((userlock & NotificationChannel.USER_LOCKED_LIGHTS) != 0) { - screenshotChannel.setLightColor(legacySS.getLightColor()); - } - // skip show_badge, irrelevant for system channel - } - return screenshotChannel; } diff --git a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt index 2c01a7003a16..db2aca873d0c 100644 --- a/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt +++ b/packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt @@ -40,8 +40,8 @@ class WallpaperController @Inject constructor(private val wallpaperManager: Wall this.wallpaperInfo = wallpaperInfo } - private val shouldUseDefaultDisplayStateChangeTransition: Boolean - get() = wallpaperInfo?.shouldUseDefaultDisplayStateChangeTransition() + private val shouldUseDefaultUnfoldTransition: Boolean + get() = wallpaperInfo?.shouldUseDefaultUnfoldTransition() ?: true fun setNotificationShadeZoom(zoomOut: Float) { @@ -50,7 +50,7 @@ class WallpaperController @Inject constructor(private val wallpaperManager: Wall } fun setUnfoldTransitionZoom(zoomOut: Float) { - if (shouldUseDefaultDisplayStateChangeTransition) { + if (shouldUseDefaultUnfoldTransition) { unfoldTransitionZoomOut = zoomOut updateZoom() } diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java index 28cba8e16970..40982bb18023 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java @@ -18,8 +18,7 @@ package com.android.systemui.util.sensors; import android.util.Log; -import androidx.annotation.NonNull; - +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; @@ -42,9 +41,9 @@ class PostureDependentProximitySensor extends ProximitySensorImpl { PostureDependentProximitySensor( @PrimaryProxSensor ThresholdSensor[] postureToPrimaryProxSensorMap, @SecondaryProxSensor ThresholdSensor[] postureToSecondaryProxSensorMap, - @NonNull DelayableExecutor delayableExecutor, - @NonNull Execution execution, - @NonNull DevicePostureController devicePostureController + @Main DelayableExecutor delayableExecutor, + Execution execution, + DevicePostureController devicePostureController ) { super( postureToPrimaryProxSensorMap[0], diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java index 0be6068c22a4..96980b85e410 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java @@ -29,6 +29,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.util.concurrency.DelayableExecutor; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -177,9 +178,7 @@ public class SensorModule { // length and index of sensorMap correspond to DevicePostureController.DevicePostureInt: final ThresholdSensor[] sensorMap = new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE]; - for (int i = 0; i < DevicePostureController.SUPPORTED_POSTURES_SIZE; i++) { - sensorMap[i] = noProxSensor; - } + Arrays.fill(sensorMap, noProxSensor); if (!hasPostureSupport(sensorTypes)) { Log.e("SensorModule", "config doesn't support postures," diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index d3581a9fd177..291c64dd6daf 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -377,10 +377,24 @@ public class BubblesManager implements Dumpable { sysuiMainExecutor.execute(() -> { sysUiState.setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand) .commitUpdate(mContext.getDisplayId()); + if (!shouldExpand) { + sysUiState.setFlag( + QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED, + false).commitUpdate(mContext.getDisplayId()); + } }); } @Override + public void onManageMenuExpandChanged(boolean menuExpanded) { + sysuiMainExecutor.execute(() -> { + sysUiState.setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED, + menuExpanded).commitUpdate(mContext.getDisplayId()); + }); + } + + + @Override public void onUnbubbleConversation(String key) { sysuiMainExecutor.execute(() -> { final NotificationEntry entry = diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index db965db13833..74611cce6f87 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -20,6 +20,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE; @@ -101,6 +102,7 @@ public final class WMShell extends SystemUI | SYSUI_STATE_BOUNCER_SHOWING | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED | SYSUI_STATE_BUBBLES_EXPANDED + | SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED; // Shell interfaces @@ -212,7 +214,7 @@ public final class WMShell extends SystemUI } @Override - public void onOverlayChanged() { + public void onThemeChanged() { pip.onOverlayChanged(); } }); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index ff263109d555..5af2cc545d52 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -28,6 +28,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.WMSingleton; +import com.android.wm.shell.RootDisplayAreaOrganizer; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.ShellCommandHandlerImpl; @@ -54,6 +55,8 @@ import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; +import com.android.wm.shell.displayareahelper.DisplayAreaHelper; +import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.fullscreen.FullscreenTaskListener; @@ -73,6 +76,7 @@ import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.splitscreen.StageTaskUnfoldController; import com.android.wm.shell.startingsurface.StartingSurface; import com.android.wm.shell.startingsurface.StartingWindowController; import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm; @@ -81,10 +85,14 @@ import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController; import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; +import com.android.wm.shell.unfold.UnfoldBackgroundController; import java.util.Optional; +import javax.inject.Provider; + import dagger.BindsOptionalOf; +import dagger.Lazy; import dagger.Module; import dagger.Provides; @@ -234,16 +242,48 @@ public abstract class WMShellBaseModule { static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController( Context context, Optional<ShellUnfoldProgressProvider> progressProvider, - RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + Lazy<UnfoldBackgroundController> unfoldBackgroundController, DisplayInsetsController displayInsetsController, @ShellMainThread ShellExecutor mainExecutor ) { return progressProvider.map(shellUnfoldTransitionProgressProvider -> new FullscreenUnfoldController(context, mainExecutor, - shellUnfoldTransitionProgressProvider, rootTaskDisplayAreaOrganizer, + unfoldBackgroundController.get(), shellUnfoldTransitionProgressProvider, displayInsetsController)); } + @Provides + static Optional<StageTaskUnfoldController> provideStageTaskUnfoldController( + Optional<ShellUnfoldProgressProvider> progressProvider, + Context context, + TransactionPool transactionPool, + Lazy<UnfoldBackgroundController> unfoldBackgroundController, + DisplayInsetsController displayInsetsController, + @ShellMainThread ShellExecutor mainExecutor + ) { + return progressProvider.map(shellUnfoldTransitionProgressProvider -> + new StageTaskUnfoldController( + context, + transactionPool, + shellUnfoldTransitionProgressProvider, + displayInsetsController, + unfoldBackgroundController.get(), + mainExecutor + )); + } + + @WMSingleton + @Provides + static UnfoldBackgroundController provideUnfoldBackgroundController( + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + Context context + ) { + return new UnfoldBackgroundController( + context, + rootTaskDisplayAreaOrganizer + ); + } + // // Freeform (optional feature) // @@ -305,13 +345,21 @@ public abstract class WMShellBaseModule { return taskSurfaceController.map((controller) -> controller.asTaskSurfaceHelper()); } - @WMSingleton @Provides static Optional<TaskSurfaceHelperController> provideTaskSurfaceHelperController( ShellTaskOrganizer taskOrganizer, @ShellMainThread ShellExecutor mainExecutor) { return Optional.ofNullable(new TaskSurfaceHelperController(taskOrganizer, mainExecutor)); } + @WMSingleton + @Provides + static Optional<DisplayAreaHelper> provideDisplayAreaHelper( + @ShellMainThread ShellExecutor mainExecutor, + RootDisplayAreaOrganizer rootDisplayAreaOrganizer) { + return Optional.ofNullable(new DisplayAreaHelperController(mainExecutor, + rootDisplayAreaOrganizer)); + } + // // Pip (optional feature) // @@ -387,6 +435,13 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides + static RootDisplayAreaOrganizer provideRootDisplayAreaOrganizer( + @ShellMainThread ShellExecutor mainExecutor) { + return new RootDisplayAreaOrganizer(mainExecutor); + } + + @WMSingleton + @Provides static Optional<SplitScreen> provideSplitScreen( Optional<SplitScreenController> splitScreenController) { return splitScreenController.map((controller) -> controller.asSplitScreen()); @@ -401,12 +456,13 @@ public abstract class WMShellBaseModule { @ShellMainThread ShellExecutor mainExecutor, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, Transitions transitions, - TransactionPool transactionPool) { + TransactionPool transactionPool, + Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) { if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) { return Optional.of(new SplitScreenController(shellTaskOrganizer, syncQueue, context, rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController, displayInsetsController, transitions, - transactionPool)); + transactionPool, stageTaskUnfoldControllerProvider)); } else { return Optional.empty(); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 5c7885f47c59..fe0a2a4bbb14 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -19,7 +19,6 @@ package com.android.keyguard; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -30,6 +29,7 @@ import android.content.res.Resources; import android.testing.AndroidTestingRunner; import android.view.View; import android.widget.FrameLayout; +import android.widget.LinearLayout; import android.widget.RelativeLayout; import androidx.test.filters.SmallTest; @@ -108,7 +108,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { private final View mFakeSmartspaceView = new View(mContext); private KeyguardClockSwitchController mController; - private View mStatusArea; + private View mSliceView; @Before public void setup() { @@ -149,8 +149,10 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors); - mStatusArea = new View(getContext()); - when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea); + mSliceView = new View(getContext()); + when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView); + when(mView.findViewById(R.id.keyguard_status_area)).thenReturn( + new LinearLayout(getContext())); } @Test @@ -215,7 +217,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView); mController.init(); - assertEquals(View.GONE, mStatusArea.getVisibility()); + assertEquals(View.GONE, mSliceView.getVisibility()); } @Test @@ -223,22 +225,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { when(mSmartspaceController.isEnabled()).thenReturn(false); mController.init(); - assertEquals(View.VISIBLE, mStatusArea.getVisibility()); - } - - @Test - public void testDetachDisconnectsSmartspace() { - when(mSmartspaceController.isEnabled()).thenReturn(true); - when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView); - mController.init(); - verify(mView).addView(eq(mFakeSmartspaceView), anyInt(), any()); - - ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor = - ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); - verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture()); - - listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView); - verify(mSmartspaceController).disconnect(); + assertEquals(View.VISIBLE, mSliceView.getVisibility()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java index 1ab08c27088a..77302ce30f09 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java @@ -54,7 +54,7 @@ public class KeyguardSliceViewTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); LayoutInflater layoutInflater = LayoutInflater.from(getContext()); mKeyguardSliceView = (KeyguardSliceView) layoutInflater - .inflate(R.layout.keyguard_status_area, null); + .inflate(R.layout.keyguard_slice_view, null); mSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI); SliceProvider.setSpecs(new HashSet<>(Collections.singletonList(SliceSpecs.LIST))); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 0772b2098565..e53b450a895e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -64,7 +64,6 @@ import android.os.Handler; import android.os.IRemoteCallback; import android.os.UserHandle; import android.os.UserManager; -import android.os.Vibrator; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -80,6 +79,7 @@ import androidx.lifecycle.Observer; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.util.LatencyTracker; import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated; @@ -174,7 +174,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private InteractionJankMonitor mInteractionJankMonitor; @Mock - private Vibrator mVibrator; + private LatencyTracker mLatencyTracker; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor; // Direct executor @@ -242,8 +242,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); - when(mFeatureFlags.isKeyguardLayoutEnabled()).thenReturn(false); - mMockitoSession = ExtendedMockito.mockitoSession() .spyStatic(SubscriptionManager.class).startMocking(); ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID) @@ -746,7 +744,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { public void sendResult(Bundle data) {} // do nothing }; mKeyguardUpdateMonitor.handleUserSwitchComplete(10 /* user */); - verify(mInteractionJankMonitor).end(eq(InteractionJankMonitor.CUJ_USER_SWITCH)); + verify(mInteractionJankMonitor).end(InteractionJankMonitor.CUJ_USER_SWITCH); + verify(mLatencyTracker).onActionEnd(LatencyTracker.ACTION_USER_SWITCH); } @Test @@ -1064,7 +1063,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mRingerModeTracker, mBackgroundExecutor, mStatusBarStateController, mLockPatternUtils, mAuthController, mTelephonyListenerManager, mFeatureFlags, - mInteractionJankMonitor, mVibrator); + mInteractionJankMonitor, mLatencyTracker); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java index c4f480d7e7aa..55ee433c8d9d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java @@ -91,7 +91,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { @Test public void testA11yDisablesGesture() { assertThat(mBrightLineFalsingManager.isFalseTap(1)).isTrue(); - when(mAccessibilityManager.isEnabled()).thenReturn(true); + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse(); } @@ -99,7 +99,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { @Test public void testA11yDisablesTap() { assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); - when(mAccessibilityManager.isEnabled()).thenReturn(true); + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse(); } @@ -107,7 +107,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { @Test public void testA11yDisablesDoubleTap() { assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isTrue(); - when(mAccessibilityManager.isEnabled()).thenReturn(true); + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isFalse(); } 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 866791cc24cb..364b5d9be2b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java @@ -67,7 +67,8 @@ public class DozeConfigurationUtil { when(config.tapGestureEnabled(anyInt())).thenReturn(true); when(config.tapSensorAvailable()).thenReturn(true); - when(config.tapSensorType(anyInt())).thenReturn(FakeSensorManager.TAP_SENSOR_TYPE); + when(config.tapSensorTypeMapping()).thenReturn( + new String[]{FakeSensorManager.TAP_SENSOR_TYPE}); when(config.dozePickupSensorAvailable()).thenReturn(false); when(config.wakeScreenGestureAvailable()).thenReturn(false); 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 e0520b406a0a..886f84e19e0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java @@ -51,6 +51,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.FakeThreadFactory; import com.android.systemui.util.sensors.AsyncSensorManager; @@ -60,6 +61,7 @@ import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -76,6 +78,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { private DozeServiceFake mServiceFake; private FakeSensorManager.FakeGenericSensor mSensor; + private FakeSensorManager.FakeGenericSensor mSensorInner; private AsyncSensorManager mSensorManager; private AlwaysOnDisplayPolicy mAlwaysOnDisplayPolicy; @Mock @@ -86,6 +89,10 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { DozeParameters mDozeParameters; @Mock DockManager mDockManager; + @Mock + DevicePostureController mDevicePostureController; + @Mock + DozeLog mDozeLog; private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor); @@ -111,9 +118,19 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { mAlwaysOnDisplayPolicy.dimBrightness = DIM_BRIGHTNESS; mAlwaysOnDisplayPolicy.dimmingScrimArray = SENSOR_TO_OPACITY; mSensor = fakeSensorManager.getFakeLightSensor(); - mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, - Optional.of(mSensor.getSensor()), mDozeHost, null /* handler */, - mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager); + mSensorInner = fakeSensorManager.getFakeLightSensor2(); + mScreen = new DozeScreenBrightness( + mContext, + mServiceFake, + mSensorManager, + new Optional[]{Optional.of(mSensor.getSensor())}, + mDozeHost, + null /* handler */, + mAlwaysOnDisplayPolicy, + mWakefulnessLifecycle, + mDozeParameters, + mDevicePostureController, + mDozeLog); } @Test @@ -151,7 +168,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { @Test public void doze_doesNotUseLightSensor() { - // GIVEN the device is docked and the display state changes to ON + // GIVEN the device is DOZE and the display state changes to ON mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE); waitForSensorManager(); @@ -166,7 +183,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { @Test public void aod_usesLightSensor() { - // GIVEN the device is docked and the display state changes to ON + // GIVEN the device is DOZE_AOD and the display state changes to ON mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); waitForSensorManager(); @@ -209,9 +226,17 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { @Test public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception { - mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, - Optional.empty() /* sensor */, mDozeHost, null /* handler */, - mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager); + mScreen = new DozeScreenBrightness( + mContext, + mServiceFake, + mSensorManager, + new Optional[] {Optional.empty()} /* sensor */, + mDozeHost, null /* handler */, + mAlwaysOnDisplayPolicy, + mWakefulnessLifecycle, + mDozeParameters, + mDevicePostureController, + mDozeLog); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE); reset(mDozeHost); @@ -238,9 +263,17 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { @Test public void testNullSensor() throws Exception { - mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, - Optional.empty() /* sensor */, mDozeHost, null /* handler */, - mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager); + mScreen = new DozeScreenBrightness( + mContext, + mServiceFake, + mSensorManager, + new Optional[]{Optional.empty()} /* sensor */, + mDozeHost, null /* handler */, + mAlwaysOnDisplayPolicy, + mWakefulnessLifecycle, + mDozeParameters, + mDevicePostureController, + mDozeLog); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); @@ -249,6 +282,130 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { } @Test + public void testSensorsSupportPostures_closed() throws Exception { + // GIVEN the device is CLOSED + when(mDevicePostureController.getDevicePosture()).thenReturn( + DevicePostureController.DEVICE_POSTURE_CLOSED); + + // GIVEN closed and opened postures use different light sensors + mScreen = new DozeScreenBrightness( + mContext, + mServiceFake, + mSensorManager, + new Optional[]{ + Optional.empty() /* unknown */, + Optional.of(mSensor.getSensor()) /* closed */, + Optional.of(mSensorInner.getSensor()) /* half-opened */, + Optional.of(mSensorInner.getSensor()) /* opened */, + Optional.empty() /* flipped */ + }, + mDozeHost, null /* handler */, + mAlwaysOnDisplayPolicy, + mWakefulnessLifecycle, + mDozeParameters, + mDevicePostureController, + mDozeLog); + + // GIVEN the device is in AOD + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mScreen.transitionTo(INITIALIZED, DOZE_AOD); + waitForSensorManager(); + + // WHEN new different events are sent from the inner and outer sensors + mSensor.sendSensorEvent(3); // CLOSED sensor + mSensorInner.sendSensorEvent(4); // OPENED sensor + + // THEN brightness is updated according to the sensor for CLOSED + assertEquals(3, mServiceFake.screenBrightness); + } + + @Test + public void testSensorsSupportPostures_open() throws Exception { + // GIVEN the device is OPENED + when(mDevicePostureController.getDevicePosture()).thenReturn( + DevicePostureController.DEVICE_POSTURE_OPENED); + + // GIVEN closed and opened postures use different light sensors + mScreen = new DozeScreenBrightness( + mContext, + mServiceFake, + mSensorManager, + new Optional[]{ + Optional.empty() /* unknown */, + Optional.of(mSensor.getSensor()) /* closed */, + Optional.of(mSensorInner.getSensor()) /* half-opened */, + Optional.of(mSensorInner.getSensor()) /* opened */, + Optional.empty() /* flipped */ + }, + mDozeHost, null /* handler */, + mAlwaysOnDisplayPolicy, + mWakefulnessLifecycle, + mDozeParameters, + mDevicePostureController, + mDozeLog); + + // GIVEN device is in AOD + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mScreen.transitionTo(INITIALIZED, DOZE_AOD); + waitForSensorManager(); + + // WHEN new different events are sent from the inner and outer sensors + mSensorInner.sendSensorEvent(4); // OPENED sensor + mSensor.sendSensorEvent(3); // CLOSED sensor + + // THEN brightness is updated according to the sensor for OPENED + assertEquals(4, mServiceFake.screenBrightness); + } + + @Test + public void testSensorsSupportPostures_swapPostures() throws Exception { + ArgumentCaptor<DevicePostureController.Callback> postureCallbackCaptor = + ArgumentCaptor.forClass(DevicePostureController.Callback.class); + reset(mDevicePostureController); + + // GIVEN the device starts up AOD OPENED + when(mDevicePostureController.getDevicePosture()).thenReturn( + DevicePostureController.DEVICE_POSTURE_OPENED); + + // GIVEN closed and opened postures use different light sensors + mScreen = new DozeScreenBrightness( + mContext, + mServiceFake, + mSensorManager, + new Optional[]{ + Optional.empty() /* unknown */, + Optional.of(mSensor.getSensor()) /* closed */, + Optional.of(mSensorInner.getSensor()) /* half-opened */, + Optional.of(mSensorInner.getSensor()) /* opened */, + Optional.empty() /* flipped */ + }, + mDozeHost, null /* handler */, + mAlwaysOnDisplayPolicy, + mWakefulnessLifecycle, + mDozeParameters, + mDevicePostureController, + mDozeLog); + verify(mDevicePostureController).addCallback(postureCallbackCaptor.capture()); + + // GIVEN device is in AOD + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mScreen.transitionTo(INITIALIZED, DOZE_AOD); + waitForSensorManager(); + + // WHEN the posture changes to CLOSED + postureCallbackCaptor.getValue().onPostureChanged( + DevicePostureController.DEVICE_POSTURE_CLOSED); + waitForSensorManager(); + + // WHEN new different events are sent from the inner and outer sensors + mSensor.sendSensorEvent(3); // CLOSED sensor + mSensorInner.sendSensorEvent(4); // OPENED sensor + + // THEN brightness is updated according to the sensor for CLOSED + assertEquals(3, mServiceFake.screenBrightness); + } + + @Test public void testNoBrightnessDeliveredAfterFinish() throws Exception { mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java index 42e34c81a790..f525fee27e20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java @@ -91,9 +91,9 @@ public class DozeSensorsTest extends SysuiTestCase { @Mock private AuthController mAuthController; @Mock + private DevicePostureController mDevicePostureController; + @Mock private ProximitySensor mProximitySensor; - private @DevicePostureController.DevicePostureInt int mDevicePosture = - DevicePostureController.DEVICE_POSTURE_UNKNOWN; private FakeSettings mFakeSettings = new FakeSettings(); private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener; private TestableLooper mTestableLooper; @@ -104,13 +104,14 @@ public class DozeSensorsTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); mTestableLooper = TestableLooper.get(this); + when(mAmbientDisplayConfiguration.tapSensorTypeMapping()) + .thenReturn(new String[]{"tapSEnsor"}); when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L); when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); doAnswer(invocation -> { ((Runnable) invocation.getArgument(0)).run(); return null; }).when(mWakeLock).wrap(any(Runnable.class)); - mDevicePosture = DevicePostureController.DEVICE_POSTURE_UNKNOWN; mDozeSensors = new TestableDozeSensors(); } @@ -127,14 +128,14 @@ public class DozeSensorsTest extends SysuiTestCase { mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class)); mTestableLooper.processAllMessages(); - verify(mCallback).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN), + verify(mCallback).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH), anyFloat(), anyFloat(), eq(null)); mDozeSensors.requestTemporaryDisable(); reset(mCallback); mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class)); mTestableLooper.processAllMessages(); - verify(mCallback, never()).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN), + verify(mCallback, never()).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH), anyFloat(), anyFloat(), eq(null)); } @@ -269,15 +270,80 @@ public class DozeSensorsTest extends SysuiTestCase { } @Test - public void testPostureOpen_registersCorrectTapGesture() { - // GIVEN device posture open - mDevicePosture = DevicePostureController.DEVICE_POSTURE_OPENED; + public void testPostureStartStateClosed_registersCorrectSensor() throws Exception { + // GIVEN doze sensor that supports postures + Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT); + Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT); + TriggerSensor triggerSensor = mDozeSensors.createDozeSensor( + new Sensor[] { + null /* unknown */, + closedSensor, + null /* half-opened */, + openedSensor}, + DevicePostureController.DEVICE_POSTURE_CLOSED); + + // WHEN trigger sensor requests listening + triggerSensor.setListening(true); + + // THEN the correct sensor is registered + verify(mSensorManager).requestTriggerSensor(eq(triggerSensor), eq(closedSensor)); + verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), eq(openedSensor)); + } + + @Test + public void testPostureChange_registersCorrectSensor() throws Exception { + // GIVEN doze sensor that supports postures + Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT); + Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT); + TriggerSensor triggerSensor = mDozeSensors.createDozeSensor( + new Sensor[] { + null /* unknown */, + closedSensor, + null /* half-opened */, + openedSensor}, + DevicePostureController.DEVICE_POSTURE_CLOSED); + + // GIVEN sensor is listening + when(mSensorManager.requestTriggerSensor(any(), any())).thenReturn(true); + triggerSensor.setListening(true); + reset(mSensorManager); + assertTrue(triggerSensor.mRegistered); + + // WHEN posture changes + boolean sensorChanged = + triggerSensor.setPosture(DevicePostureController.DEVICE_POSTURE_OPENED); + + // THEN the correct sensor is registered + assertTrue(sensorChanged); + verify(mSensorManager).requestTriggerSensor(eq(triggerSensor), eq(openedSensor)); + verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), eq(closedSensor)); + } + + @Test + public void testPostureChange_noSensorChange() throws Exception { + // GIVEN doze sensor that supports postures + Sensor closedSensor = createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT); + Sensor openedSensor = createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_LIGHT); + TriggerSensor triggerSensor = mDozeSensors.createDozeSensor( + new Sensor[] { + null /* unknown */, + closedSensor, + openedSensor /* half-opened uses the same sensor as opened*/, + openedSensor}, + DevicePostureController.DEVICE_POSTURE_HALF_OPENED); + + // GIVEN sensor is listening + when(mSensorManager.requestTriggerSensor(any(), any())).thenReturn(true); + triggerSensor.setListening(true); + reset(mSensorManager); - // WHEN DozeSensors are initialized - new TestableDozeSensors(); + // WHEN posture changes + boolean sensorChanged = + triggerSensor.setPosture(DevicePostureController.DEVICE_POSTURE_OPENED); - // THEN we use the posture to determine which tap sensor to use - verify(mAmbientDisplayConfiguration).tapSensorType(eq(mDevicePosture)); + // THEN no change in sensor + assertFalse(sensorChanged); + verify(mSensorManager, never()).requestTriggerSensor(eq(triggerSensor), any()); } @Test @@ -311,13 +377,12 @@ public class DozeSensorsTest extends SysuiTestCase { private class TestableDozeSensors extends DozeSensors { - TestableDozeSensors() { super(getContext(), mSensorManager, mDozeParameters, mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog, mProximitySensor, mFakeSettings, mAuthController, - mDevicePosture); - for (TriggerSensor sensor : mSensors) { + mDevicePostureController); + for (TriggerSensor sensor : mTriggerSensors) { if (sensor instanceof PluginSensor && ((PluginSensor) sensor).mPluginSensor.getType() == TYPE_WAKE_LOCK_SCREEN) { @@ -326,7 +391,7 @@ public class DozeSensorsTest extends SysuiTestCase { mSensorTap = sensor; } } - mSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap}; + mTriggerSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap}; } public TriggerSensor createDozeSensor(Sensor sensor, boolean settingEnabled, @@ -337,8 +402,25 @@ public class DozeSensorsTest extends SysuiTestCase { /* configured */ true, /* pulseReason*/ 0, /* reportsTouchCoordinate*/ false, - requiresTouchScreen, - mDozeLog); + /* requiresTouchscreen */ false, + /* ignoresSetting */ false, + requiresTouchScreen); + } + + /** + * create a doze sensor that supports postures and is enabled + */ + public TriggerSensor createDozeSensor(Sensor[] sensors, int posture) { + return new TriggerSensor(/* sensor */ sensors, + /* setting name */ "test_setting", + /* settingDefault */ true, + /* configured */ true, + /* pulseReason*/ 0, + /* reportsTouchCoordinate*/ false, + /* requiresTouchscreen */ false, + /* ignoresSetting */ true, + /* requiresProx */false, + posture); } } 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 31fa3f841b19..35dca7ef5fce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -204,7 +204,7 @@ public class DozeTriggersTest extends SysuiTestCase { public void testProximitySensorNotAvailablel() { mProximitySensor.setSensorAvailable(false); mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, 100, 100, null); - mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN, 100, 100, + mTriggers.onSensor(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH, 100, 100, new float[]{1}); mTriggers.onSensor(DozeLog.REASON_SENSOR_TAP, 100, 100, null); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java new file mode 100644 index 000000000000..172dcda5321f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java @@ -0,0 +1,46 @@ +/* + * 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.flags; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockitoAnnotations; + +@SmallTest +public class FeatureFlagManagerTest extends SysuiTestCase { + FeatureFlagManager mFeatureFlagManager; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + mFeatureFlagManager = new FeatureFlagManager(); + } + + @Test + public void testIsEnabled() { + mFeatureFlagManager.setEnabled(1, true); + // Again, nothing changes. + assertThat(mFeatureFlagManager.isEnabled(1, false)).isFalse(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java index 7bc5f86510a1..fc6f3fd1d9c6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagReaderTest.java @@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dump.DumpManager; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.util.wrapper.BuildInfo; @@ -44,8 +45,9 @@ import org.mockito.MockitoAnnotations; public class FeatureFlagReaderTest extends SysuiTestCase { @Mock private Resources mResources; @Mock private BuildInfo mBuildInfo; - @Mock private PluginManager mPluginManager; + @Mock private DumpManager mDumpManager; @Mock private SystemPropertiesHelper mSystemPropertiesHelper; + @Mock private FlagReader mFlagReader; private FeatureFlagReader mReader; @@ -66,7 +68,7 @@ public class FeatureFlagReaderTest extends SysuiTestCase { when(mBuildInfo.isDebuggable()).thenReturn(isDebuggable); when(mResources.getBoolean(R.bool.are_flags_overrideable)).thenReturn(isOverrideable); mReader = new FeatureFlagReader( - mResources, mBuildInfo, mPluginManager, mSystemPropertiesHelper); + mResources, mBuildInfo, mDumpManager, mSystemPropertiesHelper, mFlagReader); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java index 1a961787ee79..a850f70ae318 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java @@ -23,7 +23,6 @@ import static org.mockito.Mockito.verify; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.FlagReaderPlugin; import org.junit.Before; import org.junit.Test; @@ -51,11 +50,10 @@ public class FeatureFlagsTest extends SysuiTestCase { mFeatureFlags.addFlag(flag); // Assert and capture that a plugin listener was added. - ArgumentCaptor<FlagReaderPlugin.Listener> pluginListenerCaptor = - ArgumentCaptor.forClass(FlagReaderPlugin.Listener.class); - + ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor = + ArgumentCaptor.forClass(FlagReader.Listener.class); verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture()); - FlagReaderPlugin.Listener pluginListener = pluginListenerCaptor.getValue(); + FlagReader.Listener pluginListener = pluginListenerCaptor.getValue(); // Signal a change. No listeners, so no real effect. pluginListener.onFlagChanged(flag.getId()); @@ -81,11 +79,10 @@ public class FeatureFlagsTest extends SysuiTestCase { mFeatureFlags.addFlag(flag); // Assert and capture that a plugin listener was added. - ArgumentCaptor<FlagReaderPlugin.Listener> pluginListenerCaptor = - ArgumentCaptor.forClass(FlagReaderPlugin.Listener.class); - + ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor = + ArgumentCaptor.forClass(FlagReader.Listener.class); verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture()); - FlagReaderPlugin.Listener pluginListener = pluginListenerCaptor.getValue(); + FlagReader.Listener pluginListener = pluginListenerCaptor.getValue(); // Add a listener for the flag final Flag<?>[] changedFlag = {null}; diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index 0e344a6269b7..3b1c5f32a772 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -173,14 +173,14 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { public void testShouldLogShow() { mGlobalActionsDialogLite.onShow(null); mTestableLooper.processAllMessages(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_OPEN); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_POWER_MENU_OPEN); } @Test public void testShouldLogDismiss() { mGlobalActionsDialogLite.onDismiss(mGlobalActionsDialogLite.mDialog); mTestableLooper.processAllMessages(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_CLOSE); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_POWER_MENU_CLOSE); } @Test @@ -190,16 +190,16 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART, }; doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); dialog.onBackPressed(); mTestableLooper.processAllMessages(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_BACK); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_BACK); } @Test @@ -209,17 +209,17 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART, }; doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener); gestureListener.onSingleTapConfirmed(null); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); } @Test @@ -230,10 +230,10 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); doReturn(true).when(mStatusBar).isKeyguardShowing(); String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART, }; doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); @@ -242,7 +242,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { MotionEvent start = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0); gestureListener.onFling(start, end, 0, 1000); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); verify(mStatusBar).animateExpandSettingsPanel(null); } @@ -254,10 +254,10 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); doReturn(false).when(mStatusBar).isKeyguardShowing(); String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART, }; doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); @@ -266,40 +266,40 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { MotionEvent start = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0); gestureListener.onFling(start, end, 0, 1000); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); verify(mStatusBar).animateExpandNotificationsPanel(); } @Test public void testShouldLogBugreportPress() throws InterruptedException { - GlobalActionsDialog.BugReportAction bugReportAction = + GlobalActionsDialogLite.BugReportAction bugReportAction = mGlobalActionsDialogLite.makeBugReportActionForTesting(); bugReportAction.onPress(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_PRESS); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_BUGREPORT_PRESS); } @Test public void testShouldLogBugreportLongPress() { - GlobalActionsDialog.BugReportAction bugReportAction = + GlobalActionsDialogLite.BugReportAction bugReportAction = mGlobalActionsDialogLite.makeBugReportActionForTesting(); bugReportAction.onLongPress(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS); } @Test public void testShouldLogEmergencyDialerPress() { - GlobalActionsDialog.EmergencyDialerAction emergencyDialerAction = + GlobalActionsDialogLite.EmergencyDialerAction emergencyDialerAction = mGlobalActionsDialogLite.makeEmergencyDialerActionForTesting(); emergencyDialerAction.onPress(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS); } @Test public void testShouldLogScreenshotPress() { - GlobalActionsDialog.ScreenshotAction screenshotAction = + GlobalActionsDialogLite.ScreenshotAction screenshotAction = mGlobalActionsDialogLite.makeScreenshotActionForTesting(); screenshotAction.onPress(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SCREENSHOT_PRESS); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_SCREENSHOT_PRESS); } @Test @@ -308,7 +308,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { com.android.internal.R.integer.config_navBarInteractionMode, WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON); - GlobalActionsDialog.ScreenshotAction screenshotAction = + GlobalActionsDialogLite.ScreenshotAction screenshotAction = mGlobalActionsDialogLite.makeScreenshotActionForTesting(); assertThat(screenshotAction.shouldShow()).isTrue(); } @@ -319,12 +319,12 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { com.android.internal.R.integer.config_navBarInteractionMode, WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON); - GlobalActionsDialog.ScreenshotAction screenshotAction = + GlobalActionsDialogLite.ScreenshotAction screenshotAction = mGlobalActionsDialogLite.makeScreenshotActionForTesting(); assertThat(screenshotAction.shouldShow()).isFalse(); } - private void verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent event) { + private void verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent event) { mTestableLooper.processAllMessages(); verify(mUiEventLogger, times(1)) .log(event); @@ -345,19 +345,19 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any()); String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART, }; doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); mGlobalActionsDialogLite.createActionItems(); assertItemsOfType(mGlobalActionsDialogLite.mItems, - GlobalActionsDialog.EmergencyAction.class, - GlobalActionsDialog.LockDownAction.class, - GlobalActionsDialog.ShutDownAction.class, - GlobalActionsDialog.RestartAction.class); + GlobalActionsDialogLite.EmergencyAction.class, + GlobalActionsDialogLite.LockDownAction.class, + GlobalActionsDialogLite.ShutDownAction.class, + GlobalActionsDialogLite.RestartAction.class); assertThat(mGlobalActionsDialogLite.mOverflowItems).isEmpty(); assertThat(mGlobalActionsDialogLite.mPowerItems).isEmpty(); } @@ -369,19 +369,19 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { // make sure lockdown action will NOT be shown doReturn(false).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any()); String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_EMERGENCY, // lockdown action not allowed - GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialogLite.GLOBAL_ACTION_KEY_RESTART, }; doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions(); mGlobalActionsDialogLite.createActionItems(); assertItemsOfType(mGlobalActionsDialogLite.mItems, - GlobalActionsDialog.EmergencyAction.class, - GlobalActionsDialog.ShutDownAction.class, - GlobalActionsDialog.RestartAction.class); + GlobalActionsDialogLite.EmergencyAction.class, + GlobalActionsDialogLite.ShutDownAction.class, + GlobalActionsDialogLite.RestartAction.class); assertThat(mGlobalActionsDialogLite.mOverflowItems).isEmpty(); assertThat(mGlobalActionsDialogLite.mPowerItems).isEmpty(); } @@ -391,7 +391,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { GlobalActionsDialogLite.LockDownAction lockDownAction = mGlobalActionsDialogLite.new LockDownAction(); lockDownAction.onPress(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_LOCKDOWN_PRESS); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_LOCKDOWN_PRESS); } @Test @@ -399,7 +399,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { GlobalActionsDialogLite.ShutDownAction shutDownAction = mGlobalActionsDialogLite.new ShutDownAction(); shutDownAction.onPress(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SHUTDOWN_PRESS); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_SHUTDOWN_PRESS); } @Test @@ -407,7 +407,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { GlobalActionsDialogLite.ShutDownAction shutDownAction = mGlobalActionsDialogLite.new ShutDownAction(); shutDownAction.onLongPress(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SHUTDOWN_LONG_PRESS); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_SHUTDOWN_LONG_PRESS); } @Test @@ -415,7 +415,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { GlobalActionsDialogLite.RestartAction restartAction = mGlobalActionsDialogLite.new RestartAction(); restartAction.onPress(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_REBOOT_PRESS); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_REBOOT_PRESS); } @Test @@ -423,7 +423,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { GlobalActionsDialogLite.RestartAction restartAction = mGlobalActionsDialogLite.new RestartAction(); restartAction.onLongPress(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_REBOOT_LONG_PRESS); + verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_REBOOT_LONG_PRESS); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java deleted file mode 100644 index f8ab42fe3df5..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ /dev/null @@ -1,576 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.globalactions; - -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.IActivityManager; -import android.app.admin.DevicePolicyManager; -import android.app.trust.TrustManager; -import android.content.pm.PackageManager; -import android.content.pm.UserInfo; -import android.content.res.Resources; -import android.graphics.Color; -import android.media.AudioManager; -import android.os.Handler; -import android.os.RemoteException; -import android.os.UserManager; -import android.service.dreams.IDreamManager; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.IWindowManager; -import android.view.View; -import android.view.WindowManagerPolicyConstants; -import android.widget.FrameLayout; - -import androidx.test.filters.SmallTest; - -import com.android.internal.colorextraction.ColorExtractor; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.model.SysUiState; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.GlobalActions; -import com.android.systemui.plugins.GlobalActionsPanelPlugin; -import com.android.systemui.settings.UserTracker; -import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.telephony.TelephonyListenerManager; -import com.android.systemui.util.RingerModeLiveData; -import com.android.systemui.util.RingerModeTracker; -import com.android.systemui.util.settings.GlobalSettings; -import com.android.systemui.util.settings.SecureSettings; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.Executor; -import java.util.regex.Pattern; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) -public class GlobalActionsDialogTest extends SysuiTestCase { - private static final long UI_TIMEOUT_MILLIS = 5000; // 5 sec - private static final Pattern CANCEL_BUTTON = - Pattern.compile("cancel", Pattern.CASE_INSENSITIVE); - - private GlobalActionsDialog mGlobalActionsDialog; - - @Mock private GlobalActions.GlobalActionsManager mWindowManagerFuncs; - @Mock private AudioManager mAudioManager; - @Mock private IDreamManager mDreamManager; - @Mock private DevicePolicyManager mDevicePolicyManager; - @Mock private LockPatternUtils mLockPatternUtils; - @Mock private BroadcastDispatcher mBroadcastDispatcher; - @Mock private TelephonyListenerManager mTelephonyListenerManager; - @Mock private GlobalSettings mGlobalSettings; - @Mock private Resources mResources; - @Mock private ConfigurationController mConfigurationController; - @Mock private ActivityStarter mActivityStarter; - @Mock private KeyguardStateController mKeyguardStateController; - @Mock private UserManager mUserManager; - @Mock private TrustManager mTrustManager; - @Mock private IActivityManager mActivityManager; - @Mock private MetricsLogger mMetricsLogger; - @Mock private SysuiColorExtractor mColorExtractor; - @Mock private IStatusBarService mStatusBarService; - @Mock private NotificationShadeWindowController mNotificationShadeWindowController; - @Mock private IWindowManager mWindowManager; - @Mock private Executor mBackgroundExecutor; - @Mock private UiEventLogger mUiEventLogger; - @Mock private RingerModeTracker mRingerModeTracker; - @Mock private RingerModeLiveData mRingerModeLiveData; - @Mock private SysUiState mSysUiState; - @Mock GlobalActionsPanelPlugin mWalletPlugin; - @Mock GlobalActionsPanelPlugin.PanelViewController mWalletController; - @Mock private Handler mHandler; - @Mock private UserTracker mUserTracker; - @Mock private PackageManager mPackageManager; - @Mock private SecureSettings mSecureSettings; - @Mock private StatusBar mStatusBar; - @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - - private TestableLooper mTestableLooper; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mTestableLooper = TestableLooper.get(this); - allowTestableLooperAsMainThread(); - - when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); - when(mResources.getConfiguration()).thenReturn( - getContext().getResources().getConfiguration()); - - mGlobalActionsDialog = new GlobalActionsDialog(mContext, - mWindowManagerFuncs, - mAudioManager, - mDreamManager, - mDevicePolicyManager, - mLockPatternUtils, - mBroadcastDispatcher, - mTelephonyListenerManager, - mGlobalSettings, - mSecureSettings, - null, - mResources, - mConfigurationController, - mActivityStarter, - mKeyguardStateController, - mUserManager, - mTrustManager, - mActivityManager, - null, - mMetricsLogger, - mColorExtractor, - mStatusBarService, - mNotificationShadeWindowController, - mWindowManager, - mBackgroundExecutor, - mUiEventLogger, - mRingerModeTracker, - mSysUiState, - mHandler, - mPackageManager, - Optional.of(mStatusBar), - mKeyguardUpdateMonitor - ); - mGlobalActionsDialog.setZeroDialogPressDelayForTesting(); - - ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors(); - backdropColors.setMainColor(Color.BLACK); - when(mColorExtractor.getNeutralColors()).thenReturn(backdropColors); - when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); - } - - @Test - public void testShouldLogShow() { - mGlobalActionsDialog.onShow(null); - mTestableLooper.processAllMessages(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_OPEN); - } - - @Test - public void testShouldLogDismiss() { - mGlobalActionsDialog.onDismiss(mGlobalActionsDialog.mDialog); - mTestableLooper.processAllMessages(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_CLOSE); - } - - @Test - public void testShouldLogBugreportPress() throws InterruptedException { - GlobalActionsDialog.BugReportAction bugReportAction = - mGlobalActionsDialog.makeBugReportActionForTesting(); - bugReportAction.onPress(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_PRESS); - } - - @Test - public void testShouldLogBugreportLongPress() { - GlobalActionsDialog.BugReportAction bugReportAction = - mGlobalActionsDialog.makeBugReportActionForTesting(); - bugReportAction.onLongPress(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS); - } - - @Test - public void testShouldLogEmergencyDialerPress() { - GlobalActionsDialog.EmergencyDialerAction emergencyDialerAction = - mGlobalActionsDialog.makeEmergencyDialerActionForTesting(); - emergencyDialerAction.onPress(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS); - } - - @Test - public void testShouldLogScreenshotPress() { - GlobalActionsDialog.ScreenshotAction screenshotAction = - mGlobalActionsDialog.makeScreenshotActionForTesting(); - screenshotAction.onPress(); - verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SCREENSHOT_PRESS); - } - - @Test - public void testShouldShowScreenshot() { - mContext.getOrCreateTestableResources().addOverride( - com.android.internal.R.integer.config_navBarInteractionMode, - WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON); - - GlobalActionsDialog.ScreenshotAction screenshotAction = - mGlobalActionsDialog.makeScreenshotActionForTesting(); - assertThat(screenshotAction.shouldShow()).isTrue(); - } - - @Test - public void testShouldNotShowScreenshot() { - mContext.getOrCreateTestableResources().addOverride( - com.android.internal.R.integer.config_navBarInteractionMode, - WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON); - - GlobalActionsDialog.ScreenshotAction screenshotAction = - mGlobalActionsDialog.makeScreenshotActionForTesting(); - assertThat(screenshotAction.shouldShow()).isFalse(); - } - - private void verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent event) { - mTestableLooper.processAllMessages(); - verify(mUiEventLogger, times(1)) - .log(event); - } - - @SafeVarargs - private static <T> void assertItemsOfType(List<T> stuff, Class<? extends T>... classes) { - assertThat(stuff).hasSize(classes.length); - for (int i = 0; i < stuff.size(); i++) { - assertThat(stuff.get(i)).isInstanceOf(classes[i]); - } - } - - @Test - public void testCreateActionItems_maxThree_noOverflow() { - mGlobalActionsDialog = spy(mGlobalActionsDialog); - // allow 3 items to be shown - doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); - // ensure items are not blocked by keyguard or device provisioning - doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); - String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, - }; - doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); - mGlobalActionsDialog.createActionItems(); - - assertItemsOfType(mGlobalActionsDialog.mItems, - GlobalActionsDialog.EmergencyAction.class, - GlobalActionsDialog.ShutDownAction.class, - GlobalActionsDialog.RestartAction.class); - assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty(); - assertThat(mGlobalActionsDialog.mPowerItems).isEmpty(); - } - - @Test - public void testCreateActionItems_maxThree_condensePower() { - mGlobalActionsDialog = spy(mGlobalActionsDialog); - // allow 3 items to be shown - doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); - // ensure items are not blocked by keyguard or device provisioning - doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); - // make sure lockdown action will be shown - doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); - String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, - }; - doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); - mGlobalActionsDialog.createActionItems(); - - assertItemsOfType(mGlobalActionsDialog.mItems, - GlobalActionsDialog.EmergencyAction.class, - GlobalActionsDialog.LockDownAction.class, - GlobalActionsDialog.PowerOptionsAction.class); - assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty(); - assertItemsOfType(mGlobalActionsDialog.mPowerItems, - GlobalActionsDialog.ShutDownAction.class, - GlobalActionsDialog.RestartAction.class); - } - - @Test - public void testCreateActionItems_maxThree_condensePower_splitPower() { - mGlobalActionsDialog = spy(mGlobalActionsDialog); - // allow 3 items to be shown - doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); - // make sure lockdown action will be shown - doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); - // make sure bugreport also shown - doReturn(true).when(mGlobalActionsDialog).shouldDisplayBugReport(any()); - // ensure items are not blocked by keyguard or device provisioning - doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); - String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_BUGREPORT, - GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, - }; - doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); - mGlobalActionsDialog.createActionItems(); - - assertItemsOfType(mGlobalActionsDialog.mItems, - GlobalActionsDialog.EmergencyAction.class, - GlobalActionsDialog.LockDownAction.class, - GlobalActionsDialog.PowerOptionsAction.class); - assertItemsOfType(mGlobalActionsDialog.mOverflowItems, - GlobalActionsDialog.BugReportAction.class); - assertItemsOfType(mGlobalActionsDialog.mPowerItems, - GlobalActionsDialog.ShutDownAction.class, - GlobalActionsDialog.RestartAction.class); - } - - @Test - public void testCreateActionItems_maxFour_condensePower() { - mGlobalActionsDialog = spy(mGlobalActionsDialog); - // allow 3 items to be shown - doReturn(4).when(mGlobalActionsDialog).getMaxShownPowerItems(); - // make sure lockdown action will be shown - doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); - // ensure items are not blocked by keyguard or device provisioning - doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); - String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, - GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT - }; - doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); - mGlobalActionsDialog.createActionItems(); - - assertItemsOfType(mGlobalActionsDialog.mItems, - GlobalActionsDialog.EmergencyAction.class, - GlobalActionsDialog.LockDownAction.class, - GlobalActionsDialog.PowerOptionsAction.class, - GlobalActionsDialog.ScreenshotAction.class); - assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty(); - assertItemsOfType(mGlobalActionsDialog.mPowerItems, - GlobalActionsDialog.ShutDownAction.class, - GlobalActionsDialog.RestartAction.class); - } - - @Test - public void testCreateActionItems_maxThree_doNotCondensePower() { - mGlobalActionsDialog = spy(mGlobalActionsDialog); - // allow 3 items to be shown - doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); - // make sure lockdown action will be shown - doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); - // make sure bugreport is also shown - doReturn(true).when(mGlobalActionsDialog).shouldDisplayBugReport(any()); - // ensure items are not blocked by keyguard or device provisioning - doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); - String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_BUGREPORT, - GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, - }; - doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); - mGlobalActionsDialog.createActionItems(); - - assertItemsOfType(mGlobalActionsDialog.mItems, - GlobalActionsDialog.EmergencyAction.class, - GlobalActionsDialog.ShutDownAction.class, - GlobalActionsDialog.BugReportAction.class); - assertItemsOfType(mGlobalActionsDialog.mOverflowItems, - GlobalActionsDialog.LockDownAction.class); - assertThat(mGlobalActionsDialog.mPowerItems).isEmpty(); - } - - @Test - public void testCreateActionItems_maxAny() { - mGlobalActionsDialog = spy(mGlobalActionsDialog); - // allow any number of power menu items to be shown - doReturn(Integer.MAX_VALUE).when(mGlobalActionsDialog).getMaxShownPowerItems(); - // ensure items are not blocked by keyguard or device provisioning - doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); - // make sure lockdown action will be shown - doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); - String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, - GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, - }; - doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); - mGlobalActionsDialog.createActionItems(); - - assertItemsOfType(mGlobalActionsDialog.mItems, - GlobalActionsDialog.EmergencyAction.class, - GlobalActionsDialog.ShutDownAction.class, - GlobalActionsDialog.RestartAction.class, - GlobalActionsDialog.LockDownAction.class); - assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty(); - assertThat(mGlobalActionsDialog.mPowerItems).isEmpty(); - } - - @Test - public void testCreateActionItems_maxThree_lockdownDisabled_doesNotShowLockdown() { - mGlobalActionsDialog = spy(mGlobalActionsDialog); - // allow only 3 items to be shown - doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); - // make sure lockdown action will NOT be shown - doReturn(false).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); - String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - // lockdown action not allowed - GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, - }; - doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); - mGlobalActionsDialog.createActionItems(); - - assertItemsOfType(mGlobalActionsDialog.mItems, - GlobalActionsDialog.EmergencyAction.class, - GlobalActionsDialog.ShutDownAction.class, - GlobalActionsDialog.RestartAction.class); - assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty(); - assertThat(mGlobalActionsDialog.mPowerItems).isEmpty(); - } - - @Test - public void testCreateActionItems_shouldShowAction_excludeBugReport() { - mGlobalActionsDialog = spy(mGlobalActionsDialog); - // allow only 3 items to be shown - doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); - doReturn(true).when(mGlobalActionsDialog).shouldDisplayBugReport(any()); - // exclude bugreport in shouldShowAction to demonstrate how any button can be removed - doAnswer( - invocation -> !(invocation.getArgument(0) - instanceof GlobalActionsDialog.BugReportAction)) - .when(mGlobalActionsDialog).shouldShowAction(any()); - - String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - // bugreport action not allowed - GlobalActionsDialog.GLOBAL_ACTION_KEY_BUGREPORT, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, - }; - doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); - mGlobalActionsDialog.createActionItems(); - - assertItemsOfType(mGlobalActionsDialog.mItems, - GlobalActionsDialog.EmergencyAction.class, - GlobalActionsDialog.ShutDownAction.class, - GlobalActionsDialog.RestartAction.class); - assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty(); - assertThat(mGlobalActionsDialog.mPowerItems).isEmpty(); - } - - @Test - public void testShouldShowLockScreenMessage() throws RemoteException { - mGlobalActionsDialog = spy(mGlobalActionsDialog); - mGlobalActionsDialog.mDialog = null; - when(mKeyguardStateController.isUnlocked()).thenReturn(false); - when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo()); - when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED); - mGlobalActionsDialog.mShowLockScreenCards = false; - setupDefaultActions(); - when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController); - when(mWalletController.getPanelContent()).thenReturn(new FrameLayout(mContext)); - - mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); - - GlobalActionsDialog.ActionsDialog dialog = - (GlobalActionsDialog.ActionsDialog) mGlobalActionsDialog.mDialog; - assertThat(dialog).isNotNull(); - assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.VISIBLE); - - // Dismiss the dialog so that it does not pollute other tests - mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); - } - - @Test - public void testShouldNotShowLockScreenMessage_whenWalletShownOnLockScreen() - throws RemoteException { - mGlobalActionsDialog = spy(mGlobalActionsDialog); - mGlobalActionsDialog.mDialog = null; - when(mKeyguardStateController.isUnlocked()).thenReturn(false); - when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo()); - when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED); - mGlobalActionsDialog.mShowLockScreenCards = true; - setupDefaultActions(); - when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController); - when(mWalletController.getPanelContent()).thenReturn(new FrameLayout(mContext)); - - mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); - - GlobalActionsDialog.ActionsDialog dialog = - (GlobalActionsDialog.ActionsDialog) mGlobalActionsDialog.mDialog; - assertThat(dialog).isNotNull(); - assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.GONE); - - // Dismiss the dialog so that it does not pollute other tests - mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); - } - - @Test - public void testShouldNotShowLockScreenMessage_whenWalletBothDisabled() - throws RemoteException { - mGlobalActionsDialog = spy(mGlobalActionsDialog); - mGlobalActionsDialog.mDialog = null; - when(mKeyguardStateController.isUnlocked()).thenReturn(false); - - when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo()); - when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED); - mGlobalActionsDialog.mShowLockScreenCards = true; - setupDefaultActions(); - when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController); - when(mWalletController.getPanelContent()).thenReturn(null); - - mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); - - GlobalActionsDialog.ActionsDialog dialog = - (GlobalActionsDialog.ActionsDialog) mGlobalActionsDialog.mDialog; - assertThat(dialog).isNotNull(); - assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.GONE); - - // Dismiss the dialog so that it does not pollute other tests - mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); - } - - private UserInfo newUserInfo() { - return new UserInfo(0, null, null, UserInfo.FLAG_PRIMARY, null); - } - - private void setupDefaultActions() { - String[] actions = { - GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, - GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, - GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, - }; - doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 31d70f5c811f..1bb660e4cced 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -34,12 +34,14 @@ import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; import android.os.PowerManager; import android.os.PowerManager.WakeLock; +import android.os.RemoteException; import android.telephony.TelephonyManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.test.filters.SmallTest; +import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardUpdateMonitor; @@ -55,6 +57,8 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation; +import com.android.systemui.unfold.config.UnfoldTransitionConfig; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; @@ -63,6 +67,7 @@ import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -85,11 +90,14 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock NavigationModeController mNavigationModeController; private @Mock KeyguardDisplayManager mKeyguardDisplayManager; private @Mock DozeParameters mDozeParameters; + private @Mock UnfoldTransitionConfig mUnfoldTransitionConfig; + private @Mock UnfoldLightRevealOverlayAnimation mUnfoldAnimation; private @Mock SysuiStatusBarStateController mStatusBarStateController; private @Mock KeyguardStateController mKeyguardStateController; private @Mock NotificationShadeDepthController mNotificationShadeDepthController; private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; + private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -120,6 +128,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mNavigationModeController, mKeyguardDisplayManager, mDozeParameters, + mUnfoldTransitionConfig, + () -> mUnfoldAnimation, mStatusBarStateController, mKeyguardStateController, () -> mKeyguardUnlockAnimationController, @@ -148,6 +158,33 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { } @Test + @TestableLooper.RunWithLooper(setAsMainLooper = true) + public void testUnfoldTransitionEnabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback() + throws RemoteException { + when(mUnfoldTransitionConfig.isEnabled()).thenReturn(true); + + mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback); + TestableLooper.get(this).processAllMessages(); + onUnfoldOverlayReady(); + + // Should be called when both unfold overlay and keyguard drawn ready + verify(mKeyguardDrawnCallback).onDrawn(); + } + + @Test + @TestableLooper.RunWithLooper(setAsMainLooper = true) + public void testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback() + throws RemoteException { + when(mUnfoldTransitionConfig.isEnabled()).thenReturn(false); + + mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback); + TestableLooper.get(this).processAllMessages(); + + // Should be called when only keyguard drawn + verify(mKeyguardDrawnCallback).onDrawn(); + } + + @Test public void testIsAnimatingScreenOff() { when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true); @@ -187,4 +224,11 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { // then make sure it comes back verify(mStatusBarKeyguardViewManager, atLeast(1)).show(null); } + + private void onUnfoldOverlayReady() { + ArgumentCaptor<Runnable> overlayReadyCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mUnfoldAnimation).onScreenTurningOn(overlayReadyCaptor.capture()); + overlayReadyCaptor.getValue().run(); + TestableLooper.get(this).processAllMessages(); + } } 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 c464cad3e0b2..c5436ef6f04b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -16,16 +16,24 @@ package com.android.systemui.keyguard; +import static com.android.keyguard.LockIconView.ICON_LOCK; +import static com.android.keyguard.LockIconView.ICON_UNLOCK; + import static junit.framework.Assert.assertEquals; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; import android.graphics.PointF; +import android.graphics.drawable.AnimatedStateListDrawable; +import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.SensorLocationInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Vibrator; @@ -33,21 +41,25 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.DisplayMetrics; import android.util.Pair; +import android.view.LayoutInflater; import android.view.View; import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; import com.android.keyguard.LockIconView; import com.android.keyguard.LockIconViewController; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.AuthRippleController; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -69,7 +81,10 @@ import java.util.List; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class LockIconViewControllerTest extends SysuiTestCase { + private static final String UNLOCKED_LABEL = "unlocked"; + private @Mock LockIconView mLockIconView; + private @Mock AnimatedStateListDrawable mIconDrawable; private @Mock Context mContext; private @Mock Resources mResources; private @Mock DisplayMetrics mDisplayMetrics; @@ -86,6 +101,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { private @Mock Vibrator mVibrator; private @Mock AuthRippleController mAuthRippleController; private @Mock LottieAnimationView mAodFp; + private @Mock LayoutInflater mLayoutInflater; private LockIconViewController mLockIconViewController; @@ -94,9 +110,22 @@ public class LockIconViewControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); private View.OnAttachStateChangeListener mAttachListener; + @Captor private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateCaptor = + ArgumentCaptor.forClass(KeyguardStateController.Callback.class); + private KeyguardStateController.Callback mKeyguardStateCallback; + + @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor = + ArgumentCaptor.forClass(StatusBarStateController.StateListener.class); + private StatusBarStateController.StateListener mStatusBarStateListener; + @Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor; private AuthController.Callback mAuthControllerCallback; + @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> + mKeyguardUpdateMonitorCallbackCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); + private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback; + @Captor private ArgumentCaptor<PointF> mPointCaptor; @Before @@ -105,9 +134,16 @@ public class LockIconViewControllerTest extends SysuiTestCase { when(mLockIconView.getResources()).thenReturn(mResources); when(mLockIconView.getContext()).thenReturn(mContext); + when(mLockIconView.findViewById(R.layout.udfps_aod_lock_icon)).thenReturn(mAodFp); when(mContext.getResources()).thenReturn(mResources); when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); - when(mLockIconView.findViewById(anyInt())).thenReturn(mAodFp); + when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL); + when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable); + + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false); + when(mStatusBarStateController.isDozing()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); mLockIconViewController = new LockIconViewController( mLockIconView, @@ -122,11 +158,42 @@ public class LockIconViewControllerTest extends SysuiTestCase { mConfigurationController, mDelayableExecutor, mVibrator, - mAuthRippleController + mAuthRippleController, + mResources, + mLayoutInflater ); } @Test + public void testIgnoreUdfpsWhenNotSupported() { + // GIVEN Udpfs sensor is NOT available + mLockIconViewController.init(); + captureAttachListener(); + + // WHEN the view is attached + mAttachListener.onViewAttachedToWindow(mLockIconView); + + // THEN lottie animation should NOT be inflated + verify(mLayoutInflater, never()).inflate(eq(R.layout.udfps_aod_lock_icon), any()); + } + + @Test + public void testInflateUdfpsWhenSupported() { + // GIVEN Udpfs sensor is available + setupUdfps(); + when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true); + + mLockIconViewController.init(); + captureAttachListener(); + + // WHEN the view is attached + mAttachListener.onViewAttachedToWindow(mLockIconView); + + // THEN lottie animation should be inflated + verify(mLayoutInflater).inflate(eq(R.layout.udfps_aod_lock_icon), any()); + } + + @Test public void testUpdateFingerprintLocationOnInit() { // GIVEN fp sensor location is available pre-attached Pair<Integer, PointF> udfps = setupUdfps(); @@ -192,6 +259,109 @@ public class LockIconViewControllerTest extends SysuiTestCase { verify(mLockIconView).setUseBackground(false); } + @Test + public void testUnlockIconShows_biometricUnlockedTrue() { + // GIVEN UDFPS sensor location is available + setupUdfps(); + + // GIVEN lock icon controller is initialized and view is attached + mLockIconViewController.init(); + captureAttachListener(); + mAttachListener.onViewAttachedToWindow(mLockIconView); + captureKeyguardUpdateMonitorCallback(); + + // GIVEN user has unlocked with a biometric auth (ie: face auth) + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); + reset(mLockIconView); + + // WHEN face auth's biometric running state changes + mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, + BiometricSourceType.FACE); + + // THEN the unlock icon is shown + verify(mLockIconView).setContentDescription(UNLOCKED_LABEL); + } + + @Test + public void testLockIconStartState() { + // GIVEN lock icon state + setupShowLockIcon(); + + // WHEN lock icon controller is initialized + mLockIconViewController.init(); + captureAttachListener(); + mAttachListener.onViewAttachedToWindow(mLockIconView); + + // THEN the lock icon should show + verify(mLockIconView).updateIcon(ICON_LOCK, false); + } + + @Test + public void testLockIcon_updateToUnlock() { + // GIVEN starting state for the lock icon + setupShowLockIcon(); + + // GIVEN lock icon controller is initialized and view is attached + mLockIconViewController.init(); + captureAttachListener(); + mAttachListener.onViewAttachedToWindow(mLockIconView); + captureKeyguardStateCallback(); + reset(mLockIconView); + + // WHEN the unlocked state changes to canDismissLockScreen=true + when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); + mKeyguardStateCallback.onUnlockedChanged(); + + // THEN the unlock should show + verify(mLockIconView).updateIcon(ICON_UNLOCK, false); + } + + @Test + public void testLockIcon_clearsIconOnAod_whenUdfpsNotEnrolled() { + // GIVEN udfps not enrolled + setupUdfps(); + when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false); + + // GIVEN starting state for the lock icon + setupShowLockIcon(); + + // GIVEN lock icon controller is initialized and view is attached + mLockIconViewController.init(); + captureAttachListener(); + mAttachListener.onViewAttachedToWindow(mLockIconView); + captureStatusBarStateListener(); + reset(mLockIconView); + + // WHEN the dozing state changes + mStatusBarStateListener.onDozingChanged(true /* isDozing */); + + // THEN the icon is cleared + verify(mLockIconView).clearIcon(); + } + + @Test + public void testLockIcon_updateToAodLock_whenUdfpsEnrolled() { + // GIVEN udfps enrolled + setupUdfps(); + when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true); + + // GIVEN starting state for the lock icon + setupShowLockIcon(); + + // GIVEN lock icon controller is initialized and view is attached + mLockIconViewController.init(); + captureAttachListener(); + mAttachListener.onViewAttachedToWindow(mLockIconView); + captureStatusBarStateListener(); + reset(mLockIconView); + + // WHEN the dozing state changes + mStatusBarStateListener.onDozingChanged(true /* isDozing */); + + // THEN the AOD lock icon should show + verify(mLockIconView).updateIcon(ICON_LOCK, true); + } + private Pair<Integer, PointF> setupUdfps() { final PointF udfpsLocation = new PointF(50, 75); final int radius = 33; @@ -211,6 +381,15 @@ public class LockIconViewControllerTest extends SysuiTestCase { return new Pair(radius, udfpsLocation); } + private void setupShowLockIcon() { + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false); + when(mStatusBarStateController.isDozing()).thenReturn(false); + when(mStatusBarStateController.getDozeAmount()).thenReturn(0f); + when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); + when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false); + } + private void captureAuthControllerCallback() { verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture()); mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue(); @@ -220,4 +399,20 @@ public class LockIconViewControllerTest extends SysuiTestCase { verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture()); mAttachListener = mAttachCaptor.getValue(); } + + private void captureKeyguardStateCallback() { + verify(mKeyguardStateController).addCallback(mKeyguardStateCaptor.capture()); + mKeyguardStateCallback = mKeyguardStateCaptor.getValue(); + } + + private void captureStatusBarStateListener() { + verify(mStatusBarStateController).addCallback(mStatusBarStateCaptor.capture()); + mStatusBarStateListener = mStatusBarStateCaptor.getValue(); + } + + private void captureKeyguardUpdateMonitorCallback() { + verify(mKeyguardUpdateMonitor).registerCallback( + mKeyguardUpdateMonitorCallbackCaptor.capture()); + mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 42629f545559..bf5a6e4086f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.media.dialog.MediaOutputDialogFactory import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.phone.KeyguardDismissUtil import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.concurrency.FakeExecutor @@ -95,6 +96,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var collapsedSet: ConstraintSet @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory @Mock private lateinit var mediaCarouselController: MediaCarouselController + @Mock private lateinit var falsingManager: FalsingManager private lateinit var appIcon: ImageView private lateinit var albumView: ImageView private lateinit var titleText: TextView @@ -131,8 +133,8 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet) player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController, - seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil, - mediaOutputDialogFactory, mediaCarouselController) + seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil, + mediaOutputDialogFactory, mediaCarouselController, falsingManager) whenever(seekBarViewModel.progress).thenReturn(seekBarData) // Mock out a view holder for the player to attach to. diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt index 7d8728e4acab..e77802f8db32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt @@ -76,6 +76,7 @@ public class SeekBarObserverTest : SysuiTestCase() { assertThat(seekBarView.getThumb().getAlpha()).isEqualTo(0) assertThat(elapsedTimeView.getText()).isEqualTo("") assertThat(totalTimeView.getText()).isEqualTo("") + assertThat(seekBarView.contentDescription).isEqualTo("") assertThat(seekBarView.maxHeight).isEqualTo(disabledHeight) } @@ -102,6 +103,9 @@ public class SeekBarObserverTest : SysuiTestCase() { assertThat(seekBarView.max).isEqualTo(120000) assertThat(elapsedTimeView.getText()).isEqualTo("00:03") assertThat(totalTimeView.getText()).isEqualTo("02:00") + + val desc = context.getString(R.string.controls_media_seekbar_description, "00:03", "02:00") + assertThat(seekBarView.contentDescription).isEqualTo(desc) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 25ca8c95500b..750600ad1387 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -119,7 +119,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); @@ -181,7 +180,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(mMediaDevices); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); - assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.VISIBLE); } @@ -190,7 +188,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(new ArrayList<>()); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); - assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); } @@ -198,7 +195,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { public void onBindViewHolder_bindNonActiveConnectedDevice_verifyView() { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); - assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); @@ -215,7 +211,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { when(mMediaDevice2.isConnected()).thenReturn(false); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); - assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); @@ -231,7 +226,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); - assertThat(mViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java index 1f85112dfb74..ca5d570969c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java @@ -99,7 +99,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase { assertThat(mGroupViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); - assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mGroupViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); @@ -114,7 +113,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase { assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); - assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); @@ -141,7 +139,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase { assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); - assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); @@ -167,7 +164,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase { assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); - assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); @@ -186,7 +182,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase { assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); - assertThat(mGroupViewHolder.mDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java index c1a9739cb232..4fc329ffc7af 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java @@ -35,42 +35,24 @@ import static org.mockito.Mockito.verify; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.util.SparseArray; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; -import com.android.systemui.accessibility.AccessibilityButtonModeObserver; -import com.android.systemui.accessibility.SystemActions; -import com.android.systemui.assist.AssistManager; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; -import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; -import com.android.systemui.recents.Recents; -import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationShadeDepthController; -import com.android.systemui.statusbar.phone.ShadeController; -import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; +import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; -import com.android.wm.shell.pip.Pip; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; - -import java.util.Optional; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; /** atest NavigationBarControllerTest */ @RunWith(AndroidTestingRunner.class) @@ -78,47 +60,33 @@ import java.util.Optional; @SmallTest public class NavigationBarControllerTest extends SysuiTestCase { + private static final int SECONDARY_DISPLAY = 1; + private NavigationBarController mNavigationBarController; private NavigationBar mDefaultNavBar; private NavigationBar mSecondaryNavBar; - private CommandQueue mCommandQueue = mock(CommandQueue.class); - - private static final int SECONDARY_DISPLAY = 1; + @Mock + private CommandQueue mCommandQueue; + @Mock + private NavigationBar.Factory mNavigationBarFactory; @Before public void setUp() { + MockitoAnnotations.initMocks(this); mNavigationBarController = spy( new NavigationBarController(mContext, - mock(WindowManager.class), - () -> mock(AssistManager.class), - mock(AccessibilityManager.class), - mock(AccessibilityManagerWrapper.class), - mock(DeviceProvisionedController.class), - mock(MetricsLogger.class), mock(OverviewProxyService.class), mock(NavigationModeController.class), - mock(AccessibilityButtonModeObserver.class), - mock(StatusBarStateController.class), mock(SysUiState.class), - mock(BroadcastDispatcher.class), mCommandQueue, - Optional.of(mock(Pip.class)), - Optional.of(mock(LegacySplitScreen.class)), - Optional.of(mock(Recents.class)), - () -> Optional.of(mock(StatusBar.class)), - mock(ShadeController.class), - mock(NotificationRemoteInputManager.class), - mock(NotificationShadeDepthController.class), - mock(SystemActions.class), Dependency.get(Dependency.MAIN_HANDLER), - mock(UiEventLogger.class), - mock(NavigationBarOverlayController.class), mock(ConfigurationController.class), mock(NavigationBarA11yHelper.class), mock(TaskbarDelegate.class), - mock(UserTracker.class), - mock(DumpManager.class))); + mNavigationBarFactory, + mock(DumpManager.class), + mock(AutoHideController.class))); initializeNavigationBars(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index e37f4224060c..223ffbd7bba5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -19,6 +19,7 @@ package com.android.systemui.navigationbar; import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; +import static android.inputmethodservice.InputMethodService.IME_INVISIBLE; import static android.inputmethodservice.InputMethodService.IME_VISIBLE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; @@ -28,7 +29,6 @@ import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -45,11 +45,11 @@ import android.content.Context; import android.content.IntentFilter; import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; -import android.os.Looper; import android.os.SystemClock; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; +import android.telecom.TelecomManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -60,12 +60,12 @@ import android.view.View; import android.view.WindowManager; import android.view.WindowMetrics; import android.view.accessibility.AccessibilityManager; +import android.view.inputmethod.InputMethodManager; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; -import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestableContext; import com.android.systemui.accessibility.AccessibilityButtonModeObserver; @@ -81,9 +81,10 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.phone.AutoHideController; +import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.utils.leaks.LeakCheckedTest; @@ -110,7 +111,13 @@ public class NavigationBarTest extends SysuiTestCase { private NavigationBar mExternalDisplayNavigationBar; private SysuiTestableContext mSysuiTestableContextExternal; + @Mock private OverviewProxyService mOverviewProxyService; + @Mock + private StatusBarStateController mStatusBarStateController; + @Mock + private NavigationModeController mNavigationModeController; + @Mock private CommandQueue mCommandQueue; private SysUiState mMockSysUiState; @Mock @@ -125,11 +132,25 @@ public class NavigationBarTest extends SysuiTestCase { EdgeBackGestureHandler mEdgeBackGestureHandler; @Mock NavigationBarA11yHelper mNavigationBarA11yHelper; + @Mock + private LightBarController mLightBarController; + @Mock + private LightBarController.Factory mLightBarcontrollerFactory; + @Mock + private AutoHideController mAutoHideController; + @Mock + private AutoHideController.Factory mAutoHideControllerFactory; + @Mock + private WindowManager mWindowManager; + @Mock + private TelecomManager mTelecomManager; + @Mock + private InputMethodManager mInputMethodManager; + @Mock + private AssistManager mAssistManager; @Rule public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck(); - private AccessibilityManagerWrapper mAccessibilityWrapper = - new AccessibilityManagerWrapper(mContext); @Before public void setup() throws Exception { @@ -137,15 +158,19 @@ public class NavigationBarTest extends SysuiTestCase { when(mEdgeBackGestureHandlerFactory.create(any(Context.class))) .thenReturn(mEdgeBackGestureHandler); - mCommandQueue = new CommandQueue(mContext); + when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController); + when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController); setupSysuiDependency(); - mDependency.injectMockDependency(AssistManager.class); + // This class inflates views that call Dependency.get, thus these injections are still + // necessary. + mDependency.injectTestDependency(AssistManager.class, mAssistManager); mDependency.injectMockDependency(KeyguardStateController.class); - mDependency.injectMockDependency(StatusBarStateController.class); + mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController); mDependency.injectMockDependency(NavigationBarController.class); - mOverviewProxyService = mDependency.injectMockDependency(OverviewProxyService.class); mDependency.injectTestDependency(EdgeBackGestureHandler.Factory.class, mEdgeBackGestureHandlerFactory); + mDependency.injectTestDependency(OverviewProxyService.class, mOverviewProxyService); + mDependency.injectTestDependency(NavigationModeController.class, mNavigationModeController); TestableLooper.get(this).runWithLooper(() -> { mNavigationBar = createNavBar(mContext); mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal); @@ -164,25 +189,21 @@ public class NavigationBarTest extends SysuiTestCase { mSysuiTestableContextExternal = (SysuiTestableContext) getContext().createDisplayContext( display); - WindowManager windowManager = mock(WindowManager.class); - Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay(); - when(windowManager.getDefaultDisplay()).thenReturn( - defaultDisplay); - WindowMetrics maximumWindowMetrics = mContext.getSystemService(WindowManager.class) + Display defaultDisplay = mContext.getDisplay(); + when(mWindowManager.getDefaultDisplay()).thenReturn(defaultDisplay); + WindowMetrics metrics = mContext.getSystemService(WindowManager.class) .getMaximumWindowMetrics(); - when(windowManager.getMaximumWindowMetrics()).thenReturn(maximumWindowMetrics); + when(mWindowManager.getMaximumWindowMetrics()).thenReturn(metrics); WindowMetrics currentWindowMetrics = mContext.getSystemService(WindowManager.class) .getCurrentWindowMetrics(); - when(windowManager.getCurrentWindowMetrics()).thenReturn(currentWindowMetrics); - doNothing().when(windowManager).addView(any(), any()); - mContext.addMockSystemService(Context.WINDOW_SERVICE, windowManager); - mSysuiTestableContextExternal.addMockSystemService(Context.WINDOW_SERVICE, windowManager); - - mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper()); - mDependency.injectTestDependency(AccessibilityManagerWrapper.class, mAccessibilityWrapper); - + when(mWindowManager.getCurrentWindowMetrics()).thenReturn(currentWindowMetrics); + doNothing().when(mWindowManager).addView(any(), any()); + doNothing().when(mWindowManager).removeViewImmediate(any()); mMockSysUiState = mock(SysUiState.class); when(mMockSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mMockSysUiState); + + mContext.addMockSystemService(WindowManager.class, mWindowManager); + mSysuiTestableContextExternal.addMockSystemService(WindowManager.class, mWindowManager); } @Test @@ -239,10 +260,8 @@ public class NavigationBarTest extends SysuiTestCase { defaultNavBar.createView(null); externalNavBar.createView(null); - // Set IME window status for default NavBar. - mCommandQueue.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE, - BACK_DISPOSITION_DEFAULT, true, false); - processAllMessages(); + defaultNavBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE, + BACK_DISPOSITION_DEFAULT, true); // Verify IME window state will be updated in default NavBar & external NavBar state reset. assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN, @@ -250,11 +269,10 @@ public class NavigationBarTest extends SysuiTestCase { assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); assertFalse((externalNavBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); - // Set IME window status for external NavBar. - mCommandQueue.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null, - IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true, false); - processAllMessages(); - + externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null, IME_VISIBLE, + BACK_DISPOSITION_DEFAULT, true); + defaultNavBar.setImeWindowStatus( + DEFAULT_DISPLAY, null, IME_INVISIBLE, BACK_DISPOSITION_DEFAULT, false); // Verify IME window state will be updated in external NavBar & default NavBar state reset. assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN, externalNavBar.getNavigationIconHints()); @@ -280,19 +298,15 @@ public class NavigationBarTest extends SysuiTestCase { DeviceProvisionedController deviceProvisionedController = mock(DeviceProvisionedController.class); when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true); - assertNotNull(mAccessibilityWrapper); - return spy(new NavigationBar(context, - mock(WindowManager.class), - () -> mock(AssistManager.class), + NavigationBar.Factory factory = new NavigationBar.Factory( + () -> mAssistManager, mock(AccessibilityManager.class), - context.getDisplayId() == DEFAULT_DISPLAY ? mAccessibilityWrapper - : mock(AccessibilityManagerWrapper.class), deviceProvisionedController, new MetricsLogger(), mOverviewProxyService, - mock(NavigationModeController.class), + mNavigationModeController, mock(AccessibilityButtonModeObserver.class), - mock(StatusBarStateController.class), + mStatusBarStateController, mMockSysUiState, mBroadcastDispatcher, mCommandQueue, @@ -308,7 +322,14 @@ public class NavigationBarTest extends SysuiTestCase { mock(NavigationBarOverlayController.class), mUiEventLogger, mNavigationBarA11yHelper, - mock(UserTracker.class))); + mock(UserTracker.class), + mLightBarController, + mLightBarcontrollerFactory, + mAutoHideController, + mAutoHideControllerFactory, + Optional.of(mTelecomManager), + mInputMethodManager); + return spy(factory.create(context)); } private void processAllMessages() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index c42b64a09985..7cea430e146f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -19,6 +19,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; import android.widget.LinearLayout; +import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; import androidx.test.filters.SmallTest; @@ -73,6 +74,7 @@ public class InternetDialogTest extends SysuiTestCase { private LinearLayout mConnectedWifi; private RecyclerView mWifiList; private LinearLayout mSeeAll; + private LinearLayout mWifiScanNotify; @Before public void setUp() { @@ -104,6 +106,7 @@ public class InternetDialogTest extends SysuiTestCase { mConnectedWifi = mDialogView.requireViewById(R.id.wifi_connected_layout); mWifiList = mDialogView.requireViewById(R.id.wifi_list_layout); mSeeAll = mDialogView.requireViewById(R.id.see_all_layout); + mWifiScanNotify = mDialogView.requireViewById(R.id.wifi_scan_notify_layout); } @After @@ -264,6 +267,50 @@ public class InternetDialogTest extends SysuiTestCase { } @Test + public void updateDialog_wifiOn_hideWifiScanNotify() { + // The preconditions WiFi ON and Internet WiFi are already in setUp() + + mInternetDialog.updateDialog(); + + assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void updateDialog_wifiOffAndWifiScanOff_hideWifiScanNotify() { + when(mWifiManager.isWifiEnabled()).thenReturn(false); + when(mWifiManager.isScanAlwaysAvailable()).thenReturn(false); + + mInternetDialog.updateDialog(); + + assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void updateDialog_wifiOffAndWifiScanOnAndDeviceLocked_hideWifiScanNotify() { + when(mWifiManager.isWifiEnabled()).thenReturn(false); + when(mWifiManager.isScanAlwaysAvailable()).thenReturn(true); + when(mInternetDialogController.isDeviceLocked()).thenReturn(true); + + mInternetDialog.updateDialog(); + + assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void updateDialog_wifiOffAndWifiScanOnAndDeviceUnlocked_showWifiScanNotify() { + when(mWifiManager.isWifiEnabled()).thenReturn(false); + when(mWifiManager.isScanAlwaysAvailable()).thenReturn(true); + when(mInternetDialogController.isDeviceLocked()).thenReturn(false); + + mInternetDialog.updateDialog(); + + assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.VISIBLE); + TextView wifiScanNotifyText = mDialogView.requireViewById(R.id.wifi_scan_notify_text); + assertThat(wifiScanNotifyText.getText().length()).isNotEqualTo(0); + assertThat(wifiScanNotifyText.getMovementMethod()).isNotNull(); + } + + @Test public void onClickSeeMoreButton_clickSeeAll_verifyLaunchNetworkSetting() { mSeeAll.performClick(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt new file mode 100644 index 000000000000..d5fe588b2115 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.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.qs.user + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import android.view.ViewGroup +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class UserDialogTest : SysuiTestCase() { + + private lateinit var dialog: UserDialog + + @Before + fun setUp() { + dialog = UserDialog(mContext) + } + + @After + fun tearDown() { + dialog.dismiss() + } + + @Test + fun doneButtonExists() { + assertThat(dialog.doneButton).isInstanceOf(View::class.java) + } + + @Test + fun settingsButtonExists() { + assertThat(dialog.settingsButton).isInstanceOf(View::class.java) + } + + @Test + fun gridExistsAndIsViewGroup() { + assertThat(dialog.grid).isInstanceOf(ViewGroup::class.java) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt new file mode 100644 index 000000000000..7e900c843cc3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt @@ -0,0 +1,207 @@ +/* + * 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.qs.user + +import android.content.Intent +import android.provider.Settings +import android.testing.AndroidTestingRunner +import android.view.View +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.qs.PseudoGridView +import com.android.systemui.qs.tiles.UserDetailView +import com.android.systemui.statusbar.policy.UserSwitcherController +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatcher +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.any +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.argThat +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.util.function.Consumer + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class UserSwitchDialogControllerTest : SysuiTestCase() { + + @Mock + private lateinit var dialog: UserDialog + @Mock + private lateinit var falsingManager: FalsingManager + @Mock + private lateinit var settingsView: View + @Mock + private lateinit var doneView: View + @Mock + private lateinit var activityStarter: ActivityStarter + @Mock + private lateinit var userDetailViewAdapter: UserDetailView.Adapter + @Mock + private lateinit var launchView: View + @Mock + private lateinit var gridView: PseudoGridView + @Mock + private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + @Captor + private lateinit var clickCaptor: ArgumentCaptor<View.OnClickListener> + + private lateinit var controller: UserSwitchDialogController + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + `when`(dialog.settingsButton).thenReturn(settingsView) + `when`(dialog.doneButton).thenReturn(doneView) + `when`(dialog.grid).thenReturn(gridView) + + `when`(launchView.context).thenReturn(mContext) + + controller = UserSwitchDialogController( + { userDetailViewAdapter }, + activityStarter, + falsingManager, + dialogLaunchAnimator, + { dialog } + ) + } + + @Test + fun showDialog_callsDialogShow() { + controller.showDialog(launchView) + verify(dialogLaunchAnimator).showFromView(dialog, launchView) + } + + @Test + fun createCalledBeforeDoneButton() { + controller.showDialog(launchView) + val inOrder = inOrder(dialog) + inOrder.verify(dialog).create() + inOrder.verify(dialog).doneButton + } + + @Test + fun createCalledBeforeSettingsButton() { + controller.showDialog(launchView) + val inOrder = inOrder(dialog) + inOrder.verify(dialog).create() + inOrder.verify(dialog).settingsButton + } + + @Test + fun createCalledBeforeGrid() { + controller.showDialog(launchView) + val inOrder = inOrder(dialog) + inOrder.verify(dialog).create() + inOrder.verify(dialog).grid + } + + @Test + fun dialog_showForAllUsers() { + controller.showDialog(launchView) + verify(dialog).setShowForAllUsers(true) + } + + @Test + fun dialog_cancelOnTouchOutside() { + controller.showDialog(launchView) + verify(dialog).setCanceledOnTouchOutside(true) + } + + @Test + fun adapterAndGridLinked() { + controller.showDialog(launchView) + verify(userDetailViewAdapter).linkToViewGroup(gridView) + } + + @Test + fun clickDoneButton_dismiss() { + controller.showDialog(launchView) + + verify(doneView).setOnClickListener(capture(clickCaptor)) + + clickCaptor.value.onClick(doneView) + + verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt()) + verify(dialog).dismiss() + } + + @Test + fun clickSettingsButton_noFalsing_opensSettingsAndDismisses() { + `when`(falsingManager.isFalseTap(anyInt())).thenReturn(false) + + controller.showDialog(launchView) + + verify(settingsView).setOnClickListener(capture(clickCaptor)) + + clickCaptor.value.onClick(settingsView) + + verify(activityStarter) + .postStartActivityDismissingKeyguard( + argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)), + eq(0) + ) + verify(dialog).dismiss() + } + + @Test + fun clickSettingsButton_Falsing_notOpensSettingsAndDismisses() { + `when`(falsingManager.isFalseTap(anyInt())).thenReturn(true) + + controller.showDialog(launchView) + + verify(settingsView).setOnClickListener(capture(clickCaptor)) + + clickCaptor.value.onClick(settingsView) + + verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt()) + verify(dialog).dismiss() + } + + @Test + fun callbackFromDetailView_dismissesDialog() { + val captor = argumentCaptor<Consumer<UserSwitcherController.UserRecord>>() + + controller.showDialog(launchView) + verify(userDetailViewAdapter).injectCallback(capture(captor)) + + captor.value.accept(mock(UserSwitcherController.UserRecord::class.java)) + + verify(dialog).dismiss() + } + + private class IntentMatcher(private val action: String) : ArgumentMatcher<Intent> { + override fun matches(argument: Intent?): Boolean { + return argument?.action == action + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index 2416132d8b83..af624ed1ea1a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -15,6 +15,8 @@ package com.android.systemui.statusbar; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; +import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; +import static android.inputmethodservice.InputMethodService.IME_INVISIBLE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; @@ -188,8 +190,13 @@ public class CommandQueueTest extends SysuiTestCase { @Test public void testShowImeButtonForSecondaryDisplay() { + // First show in default display to update the "last updated ime display" + testShowImeButton(); + mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, null, 1, 2, true, false); waitForIdleSync(); + verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(null), eq(IME_INVISIBLE), + eq(BACK_DISPOSITION_DEFAULT), eq(false)); verify(mCallbacks).setImeWindowStatus( eq(SECONDARY_DISPLAY), eq(null), eq(1), eq(2), eq(true)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index f5cab1df9fa2..01f7fae05f76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -166,7 +166,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { private BroadcastReceiver mBroadcastReceiver; private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); - private KeyguardIndicationTextView mTextView; + private KeyguardIndicationTextView mTextView; // AOD text private KeyguardIndicationController mController; private WakeLockFake.Builder mWakeLockBuilder; @@ -412,41 +412,32 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void transientIndication_holdsWakeLock_whenDozing() { + // GIVEN animations are enabled and text is visible + mTextView.setAnimationsEnabled(true); createController(); + mController.setVisible(true); + // WHEN transient text is shown mStatusBarStateListener.onDozingChanged(true); mController.showTransientIndication("Test"); - assertTrue(mWakeLock.isHeld()); + // THEN wake lock is held while the animation is running + assertTrue("WakeLock expected: HELD, was: RELEASED", mWakeLock.isHeld()); } @Test - public void transientIndication_releasesWakeLock_afterHiding() { + public void transientIndication_releasesWakeLock_whenDozing() { + // GIVEN animations aren't enabled + mTextView.setAnimationsEnabled(false); createController(); + mController.setVisible(true); + // WHEN we show the transient indication mStatusBarStateListener.onDozingChanged(true); mController.showTransientIndication("Test"); - mController.hideTransientIndication(); - - assertFalse(mWakeLock.isHeld()); - } - - @Test - public void transientIndication_releasesWakeLock_afterHidingDelayed() throws Throwable { - mInstrumentation.runOnMainSync(() -> { - createController(); - - mStatusBarStateListener.onDozingChanged(true); - mController.showTransientIndication("Test"); - mController.hideTransientIndicationDelayed(0); - }); - mInstrumentation.waitForIdleSync(); - Boolean[] held = new Boolean[1]; - mInstrumentation.runOnMainSync(() -> { - held[0] = mWakeLock.isHeld(); - }); - assertFalse("WakeLock expected: RELEASED, was: HELD", held[0]); + // THEN wake lock is RELEASED, not held + assertFalse("WakeLock expected: RELEASED, was: HELD", mWakeLock.isHeld()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index c50296be94f3..793851160dc2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -209,7 +209,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat()) verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong()) - verify(qS, never()).setTransitionToFullShadeAmount(anyFloat(), anyBoolean()) + verify(qS, never()).setTransitionToFullShadeAmount(anyFloat(), anyFloat()) } @Test @@ -220,7 +220,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { verify(scrimController).setTransitionToFullShadeProgress(anyFloat()) verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong()) - verify(qS).setTransitionToFullShadeAmount(anyFloat(), anyBoolean()) + verify(qS).setTransitionToFullShadeAmount(anyFloat(), anyFloat()) verify(depthController).transitionToFullShadeProgress = anyFloat() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index e5ae65f2dd17..dbd5168386de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -24,7 +24,7 @@ import android.view.View import android.view.ViewRootImpl import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.Interpolators +import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController @@ -208,7 +208,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { notificationShadeDepthController.onPanelExpansionChanged(1f, tracking = false) notificationShadeDepthController.updateBlurCallback.doFrame(0) verify(wallpaperController).setNotificationShadeZoom( - eq(Interpolators.getNotificationScrimAlpha(0.25f, false /* notifications */))) + eq(ShadeInterpolation.getNotificationScrimAlpha(0.25f))) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java index ac699f7192c8..045e6f19c667 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java @@ -52,7 +52,7 @@ public class RankingBuilder { private ArrayList<Notification.Action> mSmartActions = new ArrayList<>(); private ArrayList<CharSequence> mSmartReplies = new ArrayList<>(); private boolean mCanBubble = false; - private boolean mIsVisuallyInterruptive = false; + private boolean mIsTextChanged = false; private boolean mIsConversation = false; private ShortcutInfo mShortcutInfo = null; private int mRankingAdjustment = 0; @@ -81,7 +81,7 @@ public class RankingBuilder { mSmartActions = copyList(ranking.getSmartActions()); mSmartReplies = copyList(ranking.getSmartReplies()); mCanBubble = ranking.canBubble(); - mIsVisuallyInterruptive = ranking.visuallyInterruptive(); + mIsTextChanged = ranking.isTextChanged(); mIsConversation = ranking.isConversation(); mShortcutInfo = ranking.getConversationShortcutInfo(); mRankingAdjustment = ranking.getRankingAdjustment(); @@ -110,7 +110,7 @@ public class RankingBuilder { mSmartActions, mSmartReplies, mCanBubble, - mIsVisuallyInterruptive, + mIsTextChanged, mIsConversation, mShortcutInfo, mRankingAdjustment, @@ -189,8 +189,8 @@ public class RankingBuilder { return this; } - public RankingBuilder setVisuallyInterruptive(boolean interruptive) { - mIsVisuallyInterruptive = interruptive; + public RankingBuilder setTextChanged(boolean textChanged) { + mIsTextChanged = textChanged; return this; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt index ee9c2b82c283..ff91978c54bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt @@ -246,6 +246,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { clearInvocations(plugin) // WHEN the session is closed + controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View) controller.disconnect() // THEN the listener receives an empty list of targets @@ -417,6 +418,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { connectSession() // WHEN we are told to cleanup + controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View) controller.disconnect() // THEN we disconnect from the session and unregister any listeners diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java index 0772c03d10d0..73560be1d265 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java @@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -61,6 +62,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { private ExpandableNotificationRow mSecond; @Mock private KeyguardBypassController mBypassController; + @Mock + private FeatureFlags mFeatureFlags; private float mSmallRadiusRatio; @Before @@ -71,7 +74,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { / resources.getDimension(R.dimen.notification_corner_radius); mRoundnessManager = new NotificationRoundnessManager( mBypassController, - new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext)); + new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext), + mFeatureFlags); allowTestableLooperAsMainThread(); NotificationTestHelper testHelper = new NotificationTestHelper( mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt new file mode 100644 index 000000000000..5b60c9e07342 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -0,0 +1,58 @@ +package com.android.systemui.statusbar.notification.stack + +import android.widget.FrameLayout +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.BypassController +import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` as whenever + +@SmallTest +class StackScrollAlgorithmTest : SysuiTestCase() { + + private val hostView = FrameLayout(context) + private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView) + private val expandableViewState = ExpandableViewState() + private val notificationRow = mock(ExpandableNotificationRow::class.java) + private val ambientState = AmbientState( + context, + SectionProvider { _, _ -> false }, + BypassController { false }) + + @Before + fun setUp() { + whenever(notificationRow.viewState).thenReturn(expandableViewState) + hostView.addView(notificationRow) + } + + @Test + fun testUpTranslationSetToDefaultValue() { + whenever(notificationRow.isPinned).thenReturn(true) + whenever(notificationRow.isHeadsUp).thenReturn(true) + + stackScrollAlgorithm.resetViewStates(ambientState, 0) + + assertThat(expandableViewState.yTranslation).isEqualTo(stackScrollAlgorithm.mHeadsUpInset) + } + + @Test + fun testHeadsUpTranslationChangesBasedOnStackMargin() { + whenever(notificationRow.isPinned).thenReturn(true) + whenever(notificationRow.isHeadsUp).thenReturn(true) + val minHeadsUpTranslation = context.resources + .getDimensionPixelSize(R.dimen.notification_side_paddings) + + // split shade case with top margin introduced by shade's status bar + ambientState.stackTopMargin = 100 + stackScrollAlgorithm.resetViewStates(ambientState, 0) + + // top margin presence should decrease heads up translation up to minHeadsUpTranslation + assertThat(expandableViewState.yTranslation).isEqualTo(minHeadsUpTranslation) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java index 8b5ba3848500..38d7ce76f1b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java @@ -164,7 +164,7 @@ public class DozeServiceHostTest extends SysuiTestCase { @Test public void testPulseWhileDozing_notifyAuthInterrupt() { HashSet<Integer> reasonsWantingAuth = new HashSet<>( - Collections.singletonList(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN)); + Collections.singletonList(DozeLog.PULSE_REASON_SENSOR_WAKE_REACH)); HashSet<Integer> reasonsSkippingAuth = new HashSet<>( Arrays.asList(DozeLog.PULSE_REASON_INTENT, DozeLog.PULSE_REASON_NOTIFICATION, @@ -173,7 +173,7 @@ public class DozeServiceHostTest extends SysuiTestCase { DozeLog.REASON_SENSOR_DOUBLE_TAP, DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, DozeLog.PULSE_REASON_DOCKING, - DozeLog.REASON_SENSOR_WAKE_UP, + DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE, DozeLog.REASON_SENSOR_QUICK_PICKUP, DozeLog.REASON_SENSOR_TAP)); HashSet<Integer> reasonsThatDontPulse = new HashSet<>( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index bca1227b7d35..bafbccdb87d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -198,7 +198,6 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { mHeadsUpAppearanceController.destroy(); verify(mHeadsUpManager).removeListener(any()); verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any()); - verify(mPanelView).setVerticalTranslationListener(isNull()); verify(mPanelView).removeTrackingHeadsUpListener(any()); verify(mPanelView).setHeadsUpAppearanceController(isNull()); verify(mStackScrollerController).removeOnExpandedHeightChangedListener(any()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index b18ea4bca681..2369d8e1b50b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -450,6 +450,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mControlsComponent); mNotificationPanelViewController.initDependencies( mStatusBar, + () -> {}, mNotificationShelfController); mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); mNotificationPanelViewController.setBar(mPanelBar); @@ -518,6 +519,51 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test + public void onTouchForwardedFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() { + when(mCommandQueue.panelsEnabled()).thenReturn(false); + + boolean returnVal = mTouchHandler.onTouchForwardedFromStatusBar( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); + + assertThat(returnVal).isFalse(); + verify(mView, never()).dispatchTouchEvent(any()); + } + + @Test + public void onTouchForwardedFromStatusBar_viewNotEnabled_returnsTrueAndNoViewEvent() { + when(mCommandQueue.panelsEnabled()).thenReturn(true); + when(mView.isEnabled()).thenReturn(false); + + boolean returnVal = mTouchHandler.onTouchForwardedFromStatusBar( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); + + assertThat(returnVal).isTrue(); + verify(mView, never()).dispatchTouchEvent(any()); + } + + @Test + public void onTouchForwardedFromStatusBar_viewNotEnabledButIsMoveEvent_viewReceivesEvent() { + when(mCommandQueue.panelsEnabled()).thenReturn(true); + when(mView.isEnabled()).thenReturn(false); + MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0); + + mTouchHandler.onTouchForwardedFromStatusBar(event); + + verify(mView).dispatchTouchEvent(event); + } + + @Test + public void onTouchForwardedFromStatusBar_panelAndViewEnabled_viewReceivesEvent() { + when(mCommandQueue.panelsEnabled()).thenReturn(true); + when(mView.isEnabled()).thenReturn(true); + MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0); + + mTouchHandler.onTouchForwardedFromStatusBar(event); + + verify(mView).dispatchTouchEvent(event); + } + + @Test public void testA11y_initializeNode() { AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo(); mAccessibiltyDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo); @@ -658,18 +704,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test - public void testOnDragDownEvent_horizontalTranslationIsZeroForSplitShade() { - when(mNotificationStackScrollLayoutController.getWidth()).thenReturn(350f); - when(mView.getWidth()).thenReturn(800); - enableSplitShade(/* enabled= */ true); - - onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, - 200f /* x position */, 0f, 0)); - - verify(mQsFrame).setTranslationX(0); - } - - @Test public void testCanCollapsePanelOnTouch_trueForKeyGuard() { mStatusBarStateController.setState(KEYGUARD); 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 52a5e064f984..033f689945c8 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 @@ -17,13 +17,13 @@ package com.android.systemui.statusbar.phone import android.view.LayoutInflater +import android.view.MotionEvent 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 @@ -31,16 +31,15 @@ import org.junit.Test import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations @SmallTest class PhoneStatusBarViewControllerTest : SysuiTestCase() { - private val stateChangeListener = TestStateChangedListener() + private val touchEventHandler = TestTouchEventHandler() @Mock - private lateinit var commandQueue: CommandQueue - @Mock private lateinit var panelViewController: PanelViewController @Mock private lateinit var panelView: ViewGroup @@ -63,58 +62,41 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { 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) + view.setBar(mock(StatusBar::class.java)) } controller = PhoneStatusBarViewController( view, - commandQueue, null, - stateChangeListener + touchEventHandler, ) } @Test - fun constructor_setsPanelEnabledProviderOnView() { - var providerUsed = false - `when`(commandQueue.panelsEnabled()).then { - providerUsed = true - true - } + fun constructor_setsTouchHandlerOnView() { + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - // If the constructor correctly set a [PanelEnabledProvider], then it should be used - // when [PhoneStatusBarView.panelEnabled] is called. - view.panelEnabled() + view.onTouchEvent(event) - assertThat(providerUsed).isTrue() + assertThat(touchEventHandler.lastEvent).isEqualTo(event) } @Test fun constructor_moveFromCenterAnimationIsNotNull_moveFromCenterAnimationInitialized() { controller = PhoneStatusBarViewController( - view, commandQueue, moveFromCenterAnimation, stateChangeListener + view, moveFromCenterAnimation, touchEventHandler ) 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 + private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler { + var lastEvent: MotionEvent? = null + override fun handleTouchEvent(event: MotionEvent?): Boolean { + lastEvent = event + return false } + } } 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 ec7e07f905c6..fe3490399e81 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,6 +16,7 @@ package com.android.systemui.statusbar.phone +import android.view.MotionEvent import android.view.ViewGroup import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -48,63 +49,25 @@ class PhoneStatusBarViewTest : SysuiTestCase() { `when`(panelViewController.view).thenReturn(panelView) view = PhoneStatusBarView(mContext, null) - view.setPanel(panelViewController) view.setScrimController(scrimController) view.setBar(statusBar) } @Test - fun panelEnabled_providerReturnsTrue_returnsTrue() { - view.setPanelEnabledProvider { true } + fun panelExpansionChanged_expansionChangeListenerNotified() { + val listener = TestExpansionChangedListener() + view.setExpansionChangedListeners(listOf(listener)) + val fraction = 0.4f + val isExpanded = true - assertThat(view.panelEnabled()).isTrue() - } - - @Test - fun panelEnabled_providerReturnsFalse_returnsFalse() { - view.setPanelEnabledProvider { false } - - assertThat(view.panelEnabled()).isFalse() - } - - @Test - fun panelEnabled_noProvider_noCrash() { - view.panelEnabled() - // No assert needed, just testing no crash - } - - @Test - fun panelExpansionChanged_fracZero_stateChangeListenerNotified() { - val listener = TestExpansionStateChangedListener() - view.setPanelExpansionStateChangedListener(listener) + view.panelExpansionChanged(fraction, isExpanded) - view.panelExpansionChanged(0f, false) - - assertThat(listener.stateChangeCalled).isTrue() + assertThat(listener.fraction).isEqualTo(fraction) + assertThat(listener.isExpanded).isEqualTo(isExpanded) } @Test - fun panelExpansionChanged_fracOne_stateChangeListenerNotified() { - val listener = TestExpansionStateChangedListener() - view.setPanelExpansionStateChangedListener(listener) - - view.panelExpansionChanged(1f, false) - - assertThat(listener.stateChangeCalled).isTrue() - } - - @Test - fun panelExpansionChanged_fracHalf_stateChangeListenerNotNotified() { - val listener = TestExpansionStateChangedListener() - view.setPanelExpansionStateChangedListener(listener) - - view.panelExpansionChanged(0.5f, false) - - assertThat(listener.stateChangeCalled).isFalse() - } - - @Test - fun panelExpansionChanged_noStateChangeListener_noCrash() { + fun panelExpansionChanged_noListeners_noCrash() { view.panelExpansionChanged(1f, false) // No assert needed, just testing no crash } @@ -144,17 +107,52 @@ class PhoneStatusBarViewTest : SysuiTestCase() { } @Test - fun panelStateChanged_noListener_noCrash() { - view.panelExpansionChanged(1f, true) + fun onTouchEvent_listenerNotified() { + val handler = TestTouchEventHandler() + view.setTouchEventHandler(handler) + + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + view.onTouchEvent(event) + + assertThat(handler.lastEvent).isEqualTo(event) + } + + @Test + fun onTouchEvent_listenerReturnsTrue_viewReturnsTrue() { + val handler = TestTouchEventHandler() + view.setTouchEventHandler(handler) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + + handler.returnValue = true + + assertThat(view.onTouchEvent(event)).isTrue() + } + + @Test + fun onTouchEvent_listenerReturnsFalse_viewReturnsFalse() { + val handler = TestTouchEventHandler() + view.setTouchEventHandler(handler) + val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + + handler.returnValue = false + + assertThat(view.onTouchEvent(event)).isFalse() + } + + @Test + fun onTouchEvent_noListener_noCrash() { + view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)) // No assert needed, just testing no crash } - private class TestExpansionStateChangedListener - : PhoneStatusBarView.PanelExpansionStateChangedListener { - var stateChangeCalled: Boolean = false + private class TestExpansionChangedListener + : StatusBar.ExpansionChangedListener { + var fraction: Float = 0f + var isExpanded: Boolean = false - override fun onPanelExpansionStateChanged() { - stateChangeCalled = true + override fun onExpansionChanged(expansion: Float, expanded: Boolean) { + this.fraction = expansion + this.isExpanded = expanded } } @@ -164,4 +162,13 @@ class PhoneStatusBarViewTest : SysuiTestCase() { this.state = state } } + + private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler { + var lastEvent: MotionEvent? = null + var returnValue: Boolean = false + override fun handleTouchEvent(event: MotionEvent?): Boolean { + lastEvent = event + return returnValue + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 5ebe900b52d4..6849dab1a19a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -623,7 +623,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToUnlocked() { - mScrimController.setPanelExpansion(0f); + mScrimController.setRawPanelExpansionFraction(0f); mScrimController.transitionTo(ScrimState.UNLOCKED); finishAnimationsImmediately(); assertScrimAlpha(Map.of( @@ -638,7 +638,7 @@ public class ScrimControllerTest extends SysuiTestCase { )); // Back scrim should be visible after start dragging - mScrimController.setPanelExpansion(0.3f); + mScrimController.setRawPanelExpansionFraction(0.3f); assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, mNotificationsScrim, SEMI_TRANSPARENT, @@ -663,20 +663,20 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void panelExpansion() { - mScrimController.setPanelExpansion(0f); - mScrimController.setPanelExpansion(0.5f); + mScrimController.setRawPanelExpansionFraction(0f); + mScrimController.setRawPanelExpansionFraction(0.5f); mScrimController.transitionTo(ScrimState.UNLOCKED); finishAnimationsImmediately(); reset(mScrimBehind); - mScrimController.setPanelExpansion(0f); - mScrimController.setPanelExpansion(1.0f); + mScrimController.setRawPanelExpansionFraction(0f); + mScrimController.setRawPanelExpansionFraction(1.0f); finishAnimationsImmediately(); assertEquals("Scrim alpha should change after setPanelExpansion", mScrimBehindAlpha, mScrimBehind.getViewAlpha(), 0.01f); - mScrimController.setPanelExpansion(0f); + mScrimController.setRawPanelExpansionFraction(0f); finishAnimationsImmediately(); assertEquals("Scrim alpha should change after setPanelExpansion", @@ -723,21 +723,21 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void panelExpansionAffectsAlpha() { - mScrimController.setPanelExpansion(0f); - mScrimController.setPanelExpansion(0.5f); + mScrimController.setRawPanelExpansionFraction(0f); + mScrimController.setRawPanelExpansionFraction(0.5f); mScrimController.transitionTo(ScrimState.UNLOCKED); finishAnimationsImmediately(); final float scrimAlpha = mScrimBehind.getViewAlpha(); reset(mScrimBehind); mScrimController.setExpansionAffectsAlpha(false); - mScrimController.setPanelExpansion(0.8f); + mScrimController.setRawPanelExpansionFraction(0.8f); verifyZeroInteractions(mScrimBehind); assertEquals("Scrim opacity shouldn't change when setExpansionAffectsAlpha " + "is false", scrimAlpha, mScrimBehind.getViewAlpha(), 0.01f); mScrimController.setExpansionAffectsAlpha(true); - mScrimController.setPanelExpansion(0.1f); + mScrimController.setRawPanelExpansionFraction(0.1f); finishAnimationsImmediately(); Assert.assertNotEquals("Scrim opacity should change when setExpansionAffectsAlpha " + "is true", scrimAlpha, mScrimBehind.getViewAlpha(), 0.01f); @@ -747,7 +747,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void transitionToUnlockedFromOff() { // Simulate unlock with fingerprint without AOD mScrimController.transitionTo(ScrimState.OFF); - mScrimController.setPanelExpansion(0f); + mScrimController.setRawPanelExpansionFraction(0f); finishAnimationsImmediately(); mScrimController.transitionTo(ScrimState.UNLOCKED); @@ -769,7 +769,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void transitionToUnlockedFromAod() { // Simulate unlock with fingerprint mScrimController.transitionTo(ScrimState.AOD); - mScrimController.setPanelExpansion(0f); + mScrimController.setRawPanelExpansionFraction(0f); finishAnimationsImmediately(); mScrimController.transitionTo(ScrimState.UNLOCKED); @@ -948,7 +948,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testConservesExpansionOpacityAfterTransition() { mScrimController.transitionTo(ScrimState.UNLOCKED); - mScrimController.setPanelExpansion(0.5f); + mScrimController.setRawPanelExpansionFraction(0.5f); finishAnimationsImmediately(); final float expandedAlpha = mScrimBehind.getViewAlpha(); @@ -1063,7 +1063,7 @@ public class ScrimControllerTest extends SysuiTestCase { HashSet<ScrimState> regularStates = new HashSet<>(Arrays.asList( ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, ScrimState.BOUNCER, ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR, ScrimState.UNLOCKED, - ScrimState.SHADE_LOCKED, ScrimState.AUTH_SCRIMMED)); + ScrimState.SHADE_LOCKED, ScrimState.AUTH_SCRIMMED, ScrimState.AUTH_SCRIMMED_SHADE)); for (ScrimState state : ScrimState.values()) { if (!lowPowerModeStates.contains(state) && !regularStates.contains(state)) { @@ -1075,7 +1075,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testScrimsOpaque_whenShadeFullyExpanded() { mScrimController.transitionTo(ScrimState.UNLOCKED); - mScrimController.setPanelExpansion(1); + mScrimController.setRawPanelExpansionFraction(1); // notifications scrim alpha change require calling setQsPosition mScrimController.setQsPosition(0, 300); finishAnimationsImmediately(); @@ -1087,9 +1087,47 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test + public void testAuthScrim_notifScrimOpaque_whenShadeFullyExpanded() { + // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen + // with the camera app occluding the keyguard) + mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.setRawPanelExpansionFraction(1); + // notifications scrim alpha change require calling setQsPosition + mScrimController.setQsPosition(0, 300); + finishAnimationsImmediately(); + + // WHEN the user triggers the auth bouncer + mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE); + finishAnimationsImmediately(); + + assertEquals("Behind scrim should be opaque", + mScrimBehind.getViewAlpha(), 1, 0.0); + assertEquals("Notifications scrim should be opaque", + mNotificationsScrim.getViewAlpha(), 1, 0.0); + } + + @Test + public void testAuthScrimKeyguard() { + // GIVEN device is on the keyguard + mScrimController.transitionTo(ScrimState.KEYGUARD); + finishAnimationsImmediately(); + + // WHEN the user triggers the auth bouncer + mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED); + finishAnimationsImmediately(); + + // THEN the front scrim is updated and the KEYGUARD scrims are the same as the + // KEYGUARD scrim state + assertScrimAlpha(Map.of( + mScrimInFront, SEMI_TRANSPARENT, + mScrimBehind, SEMI_TRANSPARENT, + mNotificationsScrim, TRANSPARENT)); + } + + @Test public void testScrimsVisible_whenShadeVisible() { mScrimController.transitionTo(ScrimState.UNLOCKED); - mScrimController.setPanelExpansion(0.3f); + mScrimController.setRawPanelExpansionFraction(0.3f); // notifications scrim alpha change require calling setQsPosition mScrimController.setQsPosition(0, 300); finishAnimationsImmediately(); @@ -1124,7 +1162,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void testScrimsVisible_whenShadeVisible_clippingQs() { mScrimController.setClipsQsScrim(true); mScrimController.transitionTo(ScrimState.UNLOCKED); - mScrimController.setPanelExpansion(0.3f); + mScrimController.setRawPanelExpansionFraction(0.3f); // notifications scrim alpha change require calling setQsPosition mScrimController.setQsPosition(0.5f, 300); finishAnimationsImmediately(); @@ -1150,7 +1188,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void testNotificationScrimTransparent_whenOnLockscreen() { mScrimController.transitionTo(ScrimState.KEYGUARD); // even if shade is not pulled down, panel has expansion of 1 on the lockscreen - mScrimController.setPanelExpansion(1); + mScrimController.setRawPanelExpansionFraction(1); mScrimController.setQsPosition(0f, /*qs panel bottom*/ 0); assertScrimAlpha(Map.of( @@ -1160,7 +1198,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testNotificationScrimVisible_afterOpeningShadeFromLockscreen() { - mScrimController.setPanelExpansion(1); + mScrimController.setRawPanelExpansionFraction(1); mScrimController.transitionTo(ScrimState.SHADE_LOCKED); finishAnimationsImmediately(); @@ -1203,11 +1241,11 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testNotificationTransparency_followsTransitionToFullShade() { mScrimController.transitionTo(ScrimState.SHADE_LOCKED); - mScrimController.setPanelExpansion(1.0f); + mScrimController.setRawPanelExpansionFraction(1.0f); finishAnimationsImmediately(); float shadeLockedAlpha = mNotificationsScrim.getViewAlpha(); mScrimController.transitionTo(ScrimState.KEYGUARD); - mScrimController.setPanelExpansion(1.0f); + mScrimController.setRawPanelExpansionFraction(1.0f); finishAnimationsImmediately(); float keyguardAlpha = mNotificationsScrim.getViewAlpha(); @@ -1227,7 +1265,7 @@ public class ScrimControllerTest extends SysuiTestCase { } private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) { - mScrimController.setPanelExpansion(expansion); + mScrimController.setRawPanelExpansionFraction(expansion); finishAnimationsImmediately(); // alpha is not changing linearly thus 0.2 of leeway when asserting assertEquals(expectedAlpha, mNotificationsScrim.getViewAlpha(), 0.2); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt index e97aba2816e1..3d2ff47e3cdd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt @@ -36,6 +36,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.flags.FeatureFlags +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection @@ -50,8 +52,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.nullable +import org.mockito.ArgumentMatchers.* import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.eq @@ -83,10 +84,13 @@ class OngoingCallControllerTest : SysuiTestCase() { private lateinit var controller: OngoingCallController private lateinit var notifCollectionListener: NotifCollectionListener + @Mock private lateinit var mockFeatureFlags: FeatureFlags + @Mock private lateinit var mockSwipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler @Mock private lateinit var mockOngoingCallListener: OngoingCallListener @Mock private lateinit var mockActivityStarter: ActivityStarter @Mock private lateinit var mockIActivityManager: IActivityManager @Mock private lateinit var mockStatusBarWindowController: StatusBarWindowController + @Mock private lateinit var mockStatusBarStateController: StatusBarStateController private lateinit var chipView: View @@ -98,13 +102,12 @@ class OngoingCallControllerTest : SysuiTestCase() { } MockitoAnnotations.initMocks(this) - val featureFlags = mock(FeatureFlags::class.java) - `when`(featureFlags.isOngoingCallStatusBarChipEnabled).thenReturn(true) + `when`(mockFeatureFlags.isOngoingCallStatusBarChipEnabled).thenReturn(true) val notificationCollection = mock(CommonNotifCollection::class.java) controller = OngoingCallController( notificationCollection, - featureFlags, + mockFeatureFlags, clock, mockActivityStarter, mainExecutor, @@ -112,7 +115,9 @@ class OngoingCallControllerTest : SysuiTestCase() { OngoingCallLogger(uiEventLoggerFake), DumpManager(), Optional.of(mockStatusBarWindowController), - ) + Optional.of(mockSwipeStatusBarAwayGestureHandler), + mockStatusBarStateController, + ) controller.init() controller.addCallback(mockOngoingCallListener) controller.setChipView(chipView) @@ -141,7 +146,7 @@ class OngoingCallControllerTest : SysuiTestCase() { fun onEntryUpdated_isOngoingCallNotif_windowControllerUpdated() { notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) - verify(mockStatusBarWindowController).setIsCallOngoing(true) + verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(true) } @Test @@ -242,7 +247,7 @@ class OngoingCallControllerTest : SysuiTestCase() { notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED) - verify(mockStatusBarWindowController).setIsCallOngoing(false) + verify(mockStatusBarWindowController).setOngoingProcessRequiresStatusBarVisible(false) } /** Regression test for b/188491504. */ @@ -435,6 +440,120 @@ class OngoingCallControllerTest : SysuiTestCase() { // Other tests for notifyChipVisibilityChanged are in [OngoingCallLogger], since // [OngoingCallController.notifyChipVisibilityChanged] just delegates to that class. + @Test + fun callNotificationAdded_chipIsClickable() { + notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) + + assertThat(chipView.hasOnClickListeners()).isTrue() + } + + @Test + fun fullscreenIsTrue_thenCallNotificationAdded_chipNotClickable() { + `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false) + + getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true) + notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) + + assertThat(chipView.hasOnClickListeners()).isFalse() + } + + @Test + fun callNotificationAdded_thenFullscreenIsTrue_chipNotClickable() { + `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false) + + notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) + getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true) + + assertThat(chipView.hasOnClickListeners()).isFalse() + } + + @Test + fun fullscreenChangesToFalse_chipClickable() { + `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(false) + + notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) + // First, update to true + getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true) + // Then, update to false + getStateListener().onFullscreenStateChanged(/* isFullscreen= */ false) + + assertThat(chipView.hasOnClickListeners()).isTrue() + } + + @Test + fun fullscreenIsTrue_butChipClickInImmersiveEnabled_chipClickable() { + `when`(mockFeatureFlags.isOngoingCallInImmersiveChipTapEnabled).thenReturn(true) + + notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) + getStateListener().onFullscreenStateChanged(/* isFullscreen= */ true) + + assertThat(chipView.hasOnClickListeners()).isTrue() + } + + // Swipe gesture tests + + @Test + fun callStartedInImmersiveMode_swipeGestureCallbackAdded() { + getStateListener().onFullscreenStateChanged(true) + + notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) + + verify(mockSwipeStatusBarAwayGestureHandler) + .addOnGestureDetectedCallback(anyString(), any()) + } + + @Test + fun callStartedNotInImmersiveMode_swipeGestureCallbackNotAdded() { + getStateListener().onFullscreenStateChanged(false) + + notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) + + verify(mockSwipeStatusBarAwayGestureHandler, never()) + .addOnGestureDetectedCallback(anyString(), any()) + } + + @Test + fun transitionToImmersiveMode_swipeGestureCallbackAdded() { + notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) + + getStateListener().onFullscreenStateChanged(true) + + verify(mockSwipeStatusBarAwayGestureHandler) + .addOnGestureDetectedCallback(anyString(), any()) + } + + @Test + fun transitionOutOfImmersiveMode_swipeGestureCallbackRemoved() { + getStateListener().onFullscreenStateChanged(true) + notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) + reset(mockSwipeStatusBarAwayGestureHandler) + + getStateListener().onFullscreenStateChanged(false) + + verify(mockSwipeStatusBarAwayGestureHandler) + .removeOnGestureDetectedCallback(anyString()) + } + + @Test + fun callEndedWhileInImmersiveMode_swipeGestureCallbackRemoved() { + getStateListener().onFullscreenStateChanged(true) + val ongoingCallNotifEntry = createOngoingCallNotifEntry() + notifCollectionListener.onEntryAdded(ongoingCallNotifEntry) + reset(mockSwipeStatusBarAwayGestureHandler) + + notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED) + + verify(mockSwipeStatusBarAwayGestureHandler) + .removeOnGestureDetectedCallback(anyString()) + } + + // TODO(b/195839150): Add test + // swipeGesturedTriggeredPreviously_entersImmersiveModeAgain_callbackNotAdded(). That's + // difficult to add now because we have no way to trigger [SwipeStatusBarAwayGestureHandler]'s + // callbacks in test. + + // END swipe gesture tests + private fun createOngoingCallNotifEntry() = createCallNotifEntry(ongoingCallStyle) private fun createScreeningCallNotifEntry() = createCallNotifEntry(screeningCallStyle) @@ -459,6 +578,13 @@ class OngoingCallControllerTest : SysuiTestCase() { } private fun createNotCallNotifEntry() = NotificationEntryBuilder().build() + + private fun getStateListener(): StatusBarStateController.StateListener { + val statusBarStateListenerCaptor = ArgumentCaptor.forClass( + StatusBarStateController.StateListener::class.java) + verify(mockStatusBarStateController).addCallback(statusBarStateListenerCaptor.capture()) + return statusBarStateListenerCaptor.value!! + } } private val person = Person.Builder().setName("name").build() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt index dd43ea56609b..69ab9c51db81 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt @@ -34,6 +34,7 @@ import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.internal.util.LatencyTracker import com.android.internal.util.UserIcons import com.android.systemui.GuestResumeSessionReceiver import com.android.systemui.R @@ -83,6 +84,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { @Mock private lateinit var falsingManager: FalsingManager @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor + @Mock private lateinit var latencyTracker: LatencyTracker private lateinit var testableLooper: TestableLooper private lateinit var uiBgExecutor: FakeExecutor private lateinit var uiEventLogger: UiEventLoggerFake @@ -132,6 +134,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { secureSettings, uiBgExecutor, interactionJankMonitor, + latencyTracker, dumpManager) userSwitcherController.mPauseRefreshUsers = true @@ -156,6 +159,7 @@ class UserSwitcherControllerTest : SysuiTestCase() { userSwitcherController.onUserListItemClicked(emptyGuestUserRecord) testableLooper.processAllMessages() verify(interactionJankMonitor).begin(any()) + verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH) verify(activityManager).switchUser(guestInfo.id) assertEquals(1, uiEventLogger.numLogs()) assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_ADD.id, uiEventLogger.eventId(0)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt new file mode 100644 index 000000000000..c31640279305 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt @@ -0,0 +1,32 @@ +package com.android.systemui.unfold + +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener + +class TestUnfoldTransitionProvider : UnfoldTransitionProgressProvider, TransitionProgressListener { + + private val listeners = arrayListOf<TransitionProgressListener>() + + override fun destroy() { + listeners.clear() + } + + override fun addCallback(listener: TransitionProgressListener) { + listeners.add(listener) + } + + override fun removeCallback(listener: TransitionProgressListener) { + listeners.remove(listener) + } + + override fun onTransitionStarted() { + listeners.forEach { it.onTransitionStarted() } + } + + override fun onTransitionFinished() { + listeners.forEach { it.onTransitionFinished() } + } + + override fun onTransitionProgress(progress: Float) { + listeners.forEach { it.onTransitionProgress(progress) } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt new file mode 100644 index 000000000000..6ec0251d41a5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt @@ -0,0 +1,51 @@ +package com.android.systemui.unfold + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.WallpaperController +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.AdditionalMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class UnfoldTransitionWallpaperControllerTest : SysuiTestCase() { + + @Mock + private lateinit var wallpaperController: WallpaperController + + private val progressProvider = TestUnfoldTransitionProvider() + + @JvmField + @Rule + val mockitoRule = MockitoJUnit.rule() + + private lateinit var unfoldWallpaperController: UnfoldTransitionWallpaperController + + @Before + fun setup() { + unfoldWallpaperController = UnfoldTransitionWallpaperController(progressProvider, + wallpaperController) + unfoldWallpaperController.init() + } + + @Test + fun onTransitionProgress_zoomsIn() { + progressProvider.onTransitionProgress(0.8f) + + verify(wallpaperController).setUnfoldTransitionZoom(eq(0.2f, 0.001f)) + } + + @Test + fun onTransitionFinished_resetsZoom() { + progressProvider.onTransitionFinished() + + verify(wallpaperController).setUnfoldTransitionZoom(eq(0f, 0.001f)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt new file mode 100644 index 000000000000..a1d9a7b50d81 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt @@ -0,0 +1,178 @@ +/* + * 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.unfold.updates + +import android.hardware.devicestate.DeviceStateManager +import android.hardware.devicestate.DeviceStateManager.FoldStateListener +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.unfold.updates.hinge.HingeAngleProvider +import com.android.systemui.unfold.updates.screen.ScreenStatusProvider +import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener +import com.android.systemui.util.mockito.any +import com.google.common.truth.Truth.assertThat +import org.junit.Assume.assumeTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class DeviceFoldStateProviderTest : SysuiTestCase() { + + @Mock + private lateinit var hingeAngleProvider: HingeAngleProvider + + @Mock + private lateinit var screenStatusProvider: ScreenStatusProvider + + @Mock + private lateinit var deviceStateManager: DeviceStateManager + + private lateinit var foldStateProvider: FoldStateProvider + + private val foldUpdates: MutableList<Int> = arrayListOf() + private val hingeAngleUpdates: MutableList<Float> = arrayListOf() + + private val foldStateListenerCaptor = ArgumentCaptor.forClass(FoldStateListener::class.java) + private var foldedDeviceState: Int = 0 + private var unfoldedDeviceState: Int = 0 + + private val screenOnListenerCaptor = ArgumentCaptor.forClass(ScreenListener::class.java) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + val foldedDeviceStates: IntArray = context.resources.getIntArray( + com.android.internal.R.array.config_foldedDeviceStates) + assumeTrue("Test should be launched on a foldable device", + foldedDeviceStates.isNotEmpty()) + + foldedDeviceState = foldedDeviceStates.maxOrNull()!! + unfoldedDeviceState = foldedDeviceState + 1 + + foldStateProvider = DeviceFoldStateProvider( + context, + hingeAngleProvider, + screenStatusProvider, + deviceStateManager, + context.mainExecutor + ) + + foldStateProvider.addCallback(object : FoldStateProvider.FoldUpdatesListener { + override fun onHingeAngleUpdate(angle: Float) { + hingeAngleUpdates.add(angle) + } + + override fun onFoldUpdate(update: Int) { + foldUpdates.add(update) + } + }) + foldStateProvider.start() + + verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture()) + verify(screenStatusProvider).addCallback(screenOnListenerCaptor.capture()) + } + + @Test + fun testOnFolded_emitsFinishClosedEvent() { + setFoldState(folded = true) + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_FINISH_CLOSED) + } + + @Test + fun testOnUnfolded_emitsStartOpeningEvent() { + setFoldState(folded = false) + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING) + } + + @Test + fun testOnFolded_stopsHingeAngleProvider() { + setFoldState(folded = true) + + verify(hingeAngleProvider).stop() + } + + @Test + fun testOnUnfolded_startsHingeAngleProvider() { + setFoldState(folded = false) + + verify(hingeAngleProvider).start() + } + + @Test + fun testFirstScreenOnEventWhenFolded_doesNotEmitEvents() { + setFoldState(folded = true) + foldUpdates.clear() + + fireScreenOnEvent() + + // Power button turn on + assertThat(foldUpdates).isEmpty() + } + + @Test + fun testFirstScreenOnEventWhenUnfolded_doesNotEmitEvents() { + setFoldState(folded = false) + foldUpdates.clear() + + fireScreenOnEvent() + + assertThat(foldUpdates).isEmpty() + } + + @Test + fun testFirstScreenOnEventAfterFoldAndUnfold_emitsUnfoldedScreenAvailableEvent() { + setFoldState(folded = false) + setFoldState(folded = true) + fireScreenOnEvent() + setFoldState(folded = false) + foldUpdates.clear() + + fireScreenOnEvent() + + assertThat(foldUpdates).containsExactly(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) + } + + @Test + fun testSecondScreenOnEventWhenUnfolded_doesNotEmitEvents() { + setFoldState(folded = false) + fireScreenOnEvent() + foldUpdates.clear() + + fireScreenOnEvent() + + // No events as this is power button turn on + assertThat(foldUpdates).isEmpty() + } + + private fun setFoldState(folded: Boolean) { + val state = if (folded) foldedDeviceState else unfoldedDeviceState + foldStateListenerCaptor.value.onStateChanged(state) + } + + private fun fireScreenOnEvent() { + screenOnListenerCaptor.value.onScreenTurnedOn() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt index eebcbe63d004..3d554880ed58 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt @@ -31,7 +31,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import java.lang.Exception /** * UsbPermissionActivityTest @@ -54,7 +53,9 @@ class UsbPermissionActivityTest : SysuiTestCase() { putExtra(Intent.EXTRA_INTENT, PendingIntent.getBroadcast( mContext, 334, - Intent("NO_ACTION"), + Intent("NO_ACTION").apply { + setPackage("com.android.systemui.tests") + }, PendingIntent.FLAG_MUTABLE)) putExtra(UsbManager.EXTRA_ACCESSORY, UsbAccessory( "manufacturer", diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java index f1c9980f12c4..c7bcdefdc4c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java @@ -22,7 +22,6 @@ import static org.mockito.Mockito.verify; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; -import android.provider.Settings; import android.test.suitebuilder.annotation.SmallTest; import android.util.ArraySet; @@ -68,51 +67,4 @@ public class ChannelsTest extends SysuiTestCase { list.forEach((chan) -> assertTrue(ALL_CHANNELS.contains(chan.getId()))); } - @Test - public void testChannelSetup_noLegacyScreenshot() { - // Assert old channel cleaned up. - // TODO: remove that code + this test after P. - NotificationChannels.createAll(mContext); - ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class); - verify(mMockNotificationManager).deleteNotificationChannel( - NotificationChannels.SCREENSHOTS_LEGACY); - } - - @Test - public void testInheritFromLegacy_keepsUserLockedLegacySettings() { - NotificationChannel legacyChannel = new NotificationChannel("id", "oldName", - NotificationManager.IMPORTANCE_MIN); - legacyChannel.setImportance(NotificationManager.IMPORTANCE_NONE);; - legacyChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI, - legacyChannel.getAudioAttributes()); - legacyChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE | - NotificationChannel.USER_LOCKED_SOUND); - NotificationChannel newChannel = - NotificationChannels.createScreenshotChannel("newName", legacyChannel); - // NONE importance user locked, so don't use HIGH for new channel. - assertEquals(NotificationManager.IMPORTANCE_NONE, newChannel.getImportance()); - assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, newChannel.getSound()); - } - - @Test - public void testInheritFromLegacy_dropsUnlockedLegacySettings() { - NotificationChannel legacyChannel = new NotificationChannel("id", "oldName", - NotificationManager.IMPORTANCE_MIN); - NotificationChannel newChannel = - NotificationChannels.createScreenshotChannel("newName", legacyChannel); - assertEquals(null, newChannel.getSound()); - assertEquals("newName", newChannel.getName()); - // MIN importance not user locked, so HIGH wins out. - assertEquals(NotificationManager.IMPORTANCE_HIGH, newChannel.getImportance()); - } - - @Test - public void testInheritFromLegacy_noLegacyExists() { - NotificationChannel newChannel = - NotificationChannels.createScreenshotChannel("newName", null); - assertEquals(null, newChannel.getSound()); - assertEquals("newName", newChannel.getName()); - assertEquals(NotificationManager.IMPORTANCE_HIGH, newChannel.getImportance()); - } - } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt index b54aadb8228f..d8e418a7815c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt @@ -138,7 +138,7 @@ class WallpaperControllerTest : SysuiTestCase() { private fun createWallpaperInfo(useDefaultTransition: Boolean = true): WallpaperInfo { val info = mock(WallpaperInfo::class.java) - whenever(info.shouldUseDefaultDisplayStateChangeTransition()) + whenever(info.shouldUseDefaultUnfoldTransition()) .thenReturn(useDefaultTransition) return info } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java index 6e73827fedfb..197873f15d0d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java @@ -55,6 +55,7 @@ public class FakeSensorManager extends SensorManager { private final FakeProximitySensor mFakeProximitySensor; private final FakeGenericSensor mFakeLightSensor; + private final FakeGenericSensor mFakeLightSensor2; private final FakeGenericSensor mFakeTapSensor; private final FakeGenericSensor[] mSensors; @@ -70,7 +71,8 @@ public class FakeSensorManager extends SensorManager { mSensors = new FakeGenericSensor[]{ mFakeProximitySensor = new FakeProximitySensor(proxSensor), mFakeLightSensor = new FakeGenericSensor(createSensor(Sensor.TYPE_LIGHT, null)), - mFakeTapSensor = new FakeGenericSensor(createSensor(99, TAP_SENSOR_TYPE)) + mFakeTapSensor = new FakeGenericSensor(createSensor(99, TAP_SENSOR_TYPE)), + mFakeLightSensor2 = new FakeGenericSensor(createSensor(Sensor.TYPE_LIGHT, null)) }; } @@ -82,6 +84,10 @@ public class FakeSensorManager extends SensorManager { return mFakeLightSensor; } + public FakeGenericSensor getFakeLightSensor2() { + return mFakeLightSensor2; + } + public FakeGenericSensor getFakeTapSensor() { return mFakeTapSensor; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java index 7bb26748a9d9..e66491e4cbd1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java @@ -123,11 +123,11 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti Uri uri = getUriFor(name); for (ContentObserver observer : mContentObservers.getOrDefault(key, new ArrayList<>())) { - observer.dispatchChange(false, List.of(uri), userHandle); + observer.dispatchChange(false, List.of(uri), 0, userHandle); } for (ContentObserver observer : mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) { - observer.dispatchChange(false, List.of(uri), userHandle); + observer.dispatchChange(false, List.of(uri), 0, userHandle); } return true; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java index 34cae58d30e1..f65caee24e34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -86,7 +87,8 @@ public class FakeSettingsTest extends SysuiTestCase { mFakeSettings.putString("cat", "hat"); - verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt()); + verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt(), + anyInt()); } @Test @@ -96,7 +98,8 @@ public class FakeSettingsTest extends SysuiTestCase { mFakeSettings.putString("cat", "hat"); - verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt()); + verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt(), + anyInt()); } @Test @@ -119,6 +122,18 @@ public class FakeSettingsTest extends SysuiTestCase { mFakeSettings.putString("cat", "hat"); verify(mContentObserver, never()).dispatchChange( - anyBoolean(), any(Collection.class), anyInt()); + anyBoolean(), any(Collection.class), anyInt(), anyInt()); + } + + @Test + public void testContentObserverDispatchCorrectUser() { + int user = 10; + mFakeSettings.registerContentObserverForUser( + mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL + ); + + mFakeSettings.putStringForUser("cat", "hat", user); + verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt(), + eq(user)); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 9f755f7be2c7..1159e0912926 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -179,6 +179,7 @@ public class BubblesTest extends SysuiTestCase { private SysUiState mSysUiState; private boolean mSysUiStateBubblesExpanded; + private boolean mSysUiStateBubblesManageMenuExpanded; @Captor private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor; @@ -295,9 +296,13 @@ public class BubblesTest extends SysuiTestCase { when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); mSysUiState = new SysUiState(); - mSysUiState.addCallback(sysUiFlags -> - mSysUiStateBubblesExpanded = - (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0); + mSysUiState.addCallback(sysUiFlags -> { + mSysUiStateBubblesManageMenuExpanded = + (sysUiFlags + & QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0; + mSysUiStateBubblesExpanded = + (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0; + }); // TODO: Fix mPositioner = new TestableBubblePositioner(mContext, mWindowManager); @@ -372,8 +377,7 @@ public class BubblesTest extends SysuiTestCase { public void testAddBubble() { mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); - - assertFalse(mSysUiStateBubblesExpanded); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -381,7 +385,7 @@ public class BubblesTest extends SysuiTestCase { assertFalse(mBubbleController.hasBubbles()); mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); - assertFalse(mSysUiStateBubblesExpanded); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -396,7 +400,7 @@ public class BubblesTest extends SysuiTestCase { assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); verify(mNotificationEntryManager, times(2)).updateNotifications(anyString()); - assertFalse(mSysUiStateBubblesExpanded); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -459,7 +463,7 @@ public class BubblesTest extends SysuiTestCase { verify(mNotificationEntryManager, never()).performRemoveNotification( eq(mRow.getSbn()), any(), anyInt()); assertFalse(mBubbleController.hasBubbles()); - assertFalse(mSysUiStateBubblesExpanded); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); assertTrue(mRow.isBubble()); } @@ -478,7 +482,7 @@ public class BubblesTest extends SysuiTestCase { assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey())); - assertFalse(mSysUiStateBubblesExpanded); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -498,8 +502,7 @@ public class BubblesTest extends SysuiTestCase { mBubbleData.setExpanded(true); assertStackExpanded(); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); - - assertTrue(mSysUiStateBubblesExpanded); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); // Make sure the notif is suppressed assertBubbleNotificationSuppressedFromShade(mBubbleEntry); @@ -508,8 +511,7 @@ public class BubblesTest extends SysuiTestCase { mBubbleController.collapseStack(); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); assertStackCollapsed(); - - assertFalse(mSysUiStateBubblesExpanded); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -532,8 +534,7 @@ public class BubblesTest extends SysuiTestCase { assertStackExpanded(); verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( true, mRow2.getKey()); - - assertTrue(mSysUiStateBubblesExpanded); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); // Last added is the one that is expanded assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey()); @@ -557,8 +558,7 @@ public class BubblesTest extends SysuiTestCase { // Collapse mBubbleController.collapseStack(); assertStackCollapsed(); - - assertFalse(mSysUiStateBubblesExpanded); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -578,8 +578,7 @@ public class BubblesTest extends SysuiTestCase { mBubbleData.setExpanded(true); assertStackExpanded(); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); - - assertTrue(mSysUiStateBubblesExpanded); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); // Notif is suppressed after expansion assertBubbleNotificationSuppressedFromShade(mBubbleEntry); @@ -604,8 +603,7 @@ public class BubblesTest extends SysuiTestCase { mBubbleData.setExpanded(true); assertStackExpanded(); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); - - assertTrue(mSysUiStateBubblesExpanded); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); // Notif is suppressed after expansion assertBubbleNotificationSuppressedFromShade(mBubbleEntry); @@ -634,7 +632,7 @@ public class BubblesTest extends SysuiTestCase { BubbleStackView stackView = mBubbleController.getStackView(); mBubbleData.setExpanded(true); - assertTrue(mSysUiStateBubblesExpanded); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); assertStackExpanded(); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey()); @@ -666,7 +664,7 @@ public class BubblesTest extends SysuiTestCase { assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY); verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY); assertTrue(mBubbleController.hasBubbles()); - assertTrue(mSysUiStateBubblesExpanded); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -679,7 +677,7 @@ public class BubblesTest extends SysuiTestCase { BubbleStackView stackView = mBubbleController.getStackView(); mBubbleData.setExpanded(true); - assertTrue(mSysUiStateBubblesExpanded); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); assertStackExpanded(); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); @@ -694,7 +692,7 @@ public class BubblesTest extends SysuiTestCase { // We should be collapsed verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); assertFalse(mBubbleController.hasBubbles()); - assertFalse(mSysUiStateBubblesExpanded); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -711,8 +709,7 @@ public class BubblesTest extends SysuiTestCase { verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */, mRow.getKey()); assertStackCollapsed(); - - assertFalse(mSysUiStateBubblesExpanded); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -728,8 +725,7 @@ public class BubblesTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */, mRow.getKey()); assertStackExpanded(); - - assertTrue(mSysUiStateBubblesExpanded); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -746,8 +742,7 @@ public class BubblesTest extends SysuiTestCase { // Dot + flyout is hidden because notif is suppressed assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout()); - - assertFalse(mSysUiStateBubblesExpanded); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -769,8 +764,7 @@ public class BubblesTest extends SysuiTestCase { // Dot + flyout is hidden because notif is suppressed assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout()); - - assertFalse(mSysUiStateBubblesExpanded); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -784,7 +778,7 @@ public class BubblesTest extends SysuiTestCase { mBubbleController.expandStackAndSelectBubble(mBubbleEntry); - assertTrue(mSysUiStateBubblesExpanded); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -1206,6 +1200,63 @@ public class BubblesTest extends SysuiTestCase { assertNotNull(info); } + @Test + public void testShowManageMenuChangesSysuiState() { + mBubbleController.updateBubble(mBubbleEntry); + assertTrue(mBubbleController.hasBubbles()); + + // Expand the stack + BubbleStackView stackView = mBubbleController.getStackView(); + mBubbleData.setExpanded(true); + assertStackExpanded(); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); + + // Show the menu + stackView.showManageMenu(true); + assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */); + } + + @Test + public void testHideManageMenuChangesSysuiState() { + mBubbleController.updateBubble(mBubbleEntry); + assertTrue(mBubbleController.hasBubbles()); + + // Expand the stack + BubbleStackView stackView = mBubbleController.getStackView(); + mBubbleData.setExpanded(true); + assertStackExpanded(); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); + + // Show the menu + stackView.showManageMenu(true); + assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */); + + // Hide the menu + stackView.showManageMenu(false); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); + } + + @Test + public void testCollapseBubbleManageMenuChangesSysuiState() { + mBubbleController.updateBubble(mBubbleEntry); + assertTrue(mBubbleController.hasBubbles()); + + // Expand the stack + BubbleStackView stackView = mBubbleController.getStackView(); + mBubbleData.setExpanded(true); + assertStackExpanded(); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); + + // Show the menu + stackView.showManageMenu(true); + assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */); + + // Collapse the stack + mBubbleData.setExpanded(false); + + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); + } + /** Creates a bubble using the userId and package. */ private Bubble createBubble(int userId, String pkg) { final UserHandle userHandle = new UserHandle(userId); @@ -1303,4 +1354,12 @@ public class BubblesTest extends SysuiTestCase { assertFalse(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade( entry.getKey(), entry.getGroupKey())); } + + /** + * Asserts that the system ui states associated to bubbles are in the correct state. + */ + private void assertSysuiStates(boolean stackExpanded, boolean manageMenuExpanded) { + assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded); + assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index a3bbb26c6b23..05c4822f288e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -68,6 +68,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -160,7 +161,9 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { @Mock private AuthController mAuthController; - private SysUiState mSysUiState = new SysUiState(); + private SysUiState mSysUiState; + private boolean mSysUiStateBubblesExpanded; + private boolean mSysUiStateBubblesManageMenuExpanded; @Captor private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor; @@ -257,6 +260,15 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mZenModeConfig.suppressedVisualEffects = 0; when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); + mSysUiState = new SysUiState(); + mSysUiState.addCallback(sysUiFlags -> { + mSysUiStateBubblesManageMenuExpanded = + (sysUiFlags + & QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0; + mSysUiStateBubblesExpanded = + (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0; + }); + mPositioner = new TestableBubblePositioner(mContext, mWindowManager); mPositioner.setMaxBubbles(5); mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor); @@ -325,6 +337,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { public void testAddBubble() { mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -332,6 +345,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertFalse(mBubbleController.hasBubbles()); mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -345,6 +359,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); verify(mNotifCallback, times(2)).invalidateNotifications(anyString()); + + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -407,6 +423,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { verify(mNotifCallback, times(3)).invalidateNotifications(anyString()); assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey())); assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey())); + + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -425,6 +443,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubbleData.setExpanded(true); assertStackExpanded(); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); // Make sure the notif is suppressed assertBubbleNotificationSuppressedFromShade(mBubbleEntry); @@ -433,6 +452,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubbleController.collapseStack(); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); assertStackCollapsed(); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -455,6 +475,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertStackExpanded(); verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( true, mRow2.getKey()); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); // Last added is the one that is expanded assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey()); @@ -479,6 +500,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { // Collapse mBubbleController.collapseStack(); assertStackCollapsed(); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -498,6 +520,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubbleData.setExpanded(true); assertStackExpanded(); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); // Notif is suppressed after expansion assertBubbleNotificationSuppressedFromShade(mBubbleEntry); @@ -522,6 +545,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { mBubbleData.setExpanded(true); assertStackExpanded(); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); // Notif is suppressed after expansion assertBubbleNotificationSuppressedFromShade(mBubbleEntry); @@ -550,6 +574,8 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { BubbleStackView stackView = mBubbleController.getStackView(); mBubbleData.setExpanded(true); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); + assertStackExpanded(); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey()); @@ -580,6 +606,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY); verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY); assertTrue(mBubbleController.hasBubbles()); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -592,6 +619,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { BubbleStackView stackView = mBubbleController.getStackView(); mBubbleData.setExpanded(true); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); assertStackExpanded(); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey()); @@ -606,6 +634,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { // We should be collapsed verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey()); assertFalse(mBubbleController.hasBubbles()); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @@ -623,6 +652,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */, mRow.getKey()); assertStackCollapsed(); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -638,6 +668,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */, mRow.getKey()); assertStackExpanded(); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -654,6 +685,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { // Dot + flyout is hidden because notif is suppressed assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout()); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -675,6 +707,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { // Dot + flyout is hidden because notif is suppressed assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout()); + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); } @Test @@ -980,6 +1013,63 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { verify(mDataRepository, times(1)).loadBubbles(anyInt(), any()); } + @Test + public void testShowManageMenuChangesSysuiState() { + mBubbleController.updateBubble(mBubbleEntry); + assertTrue(mBubbleController.hasBubbles()); + + // Expand the stack + BubbleStackView stackView = mBubbleController.getStackView(); + mBubbleData.setExpanded(true); + assertStackExpanded(); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); + + // Show the menu + stackView.showManageMenu(true); + assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */); + } + + @Test + public void testHideManageMenuChangesSysuiState() { + mBubbleController.updateBubble(mBubbleEntry); + assertTrue(mBubbleController.hasBubbles()); + + // Expand the stack + BubbleStackView stackView = mBubbleController.getStackView(); + mBubbleData.setExpanded(true); + assertStackExpanded(); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); + + // Show the menu + stackView.showManageMenu(true); + assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */); + + // Hide the menu + stackView.showManageMenu(false); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); + } + + @Test + public void testCollapseBubbleManageMenuChangesSysuiState() { + mBubbleController.updateBubble(mBubbleEntry); + assertTrue(mBubbleController.hasBubbles()); + + // Expand the stack + BubbleStackView stackView = mBubbleController.getStackView(); + mBubbleData.setExpanded(true); + assertStackExpanded(); + assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */); + + // Show the menu + stackView.showManageMenu(true); + assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */); + + // Collapse the stack + mBubbleData.setExpanded(false); + + assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */); + } + /** * Sets the bubble metadata flags for this entry. These flags are normally set by * NotificationManagerService when the notification is sent, however, these tests do not @@ -1034,4 +1124,12 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertFalse(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade( entry.getKey(), entry.getGroupKey())); } + + /** + * Asserts that the system ui states associated to bubbles are in the correct state. + */ + private void assertSysuiStates(boolean stackExpanded, boolean manageMenuExpanded) { + assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded); + assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded); + } } diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index b0893cc360b7..ed37d7e2e6e6 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -19,6 +19,7 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.graphics.Camera; import android.graphics.GraphicBuffer; import android.graphics.Rect; import android.hardware.camera2.CameraAccessException; @@ -135,6 +136,7 @@ public class CameraExtensionsProxyService extends Service { private HashMap<String, CameraCharacteristics> mCharacteristicsHashMap = new HashMap<>(); private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>(); + private CameraManager mCameraManager; private static boolean checkForAdvancedAPI() { if (EXTENSIONS_PRESENT && EXTENSIONS_VERSION.startsWith(ADVANCED_VERSION_PREFIX)) { @@ -460,12 +462,12 @@ public class CameraExtensionsProxyService extends Service { // This will setup the camera vendor tag descriptor in the service process // along with all camera characteristics. try { - CameraManager manager = getSystemService(CameraManager.class); + mCameraManager = getSystemService(CameraManager.class); - String [] cameraIds = manager.getCameraIdListNoLazy(); + String [] cameraIds = mCameraManager.getCameraIdListNoLazy(); if (cameraIds != null) { for (String cameraId : cameraIds) { - CameraCharacteristics chars = manager.getCameraCharacteristics(cameraId); + CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId); mCharacteristicsHashMap.put(cameraId, chars); Object thisClass = CameraCharacteristics.Key.class; Class<CameraCharacteristics.Key<?>> keyClass = @@ -1174,8 +1176,9 @@ public class CameraExtensionsProxyService extends Service { @Override public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) { mCameraId = cameraId; - mPreviewExtender.onInit(cameraId, new CameraCharacteristics(cameraCharacteristics), - CameraExtensionsProxyService.this); + CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics); + mCameraManager.registerDeviceStateListener(chars); + mPreviewExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this); } @Override @@ -1200,13 +1203,16 @@ public class CameraExtensionsProxyService extends Service { @Override public void init(String cameraId, CameraMetadataNative chars) { - mPreviewExtender.init(cameraId, new CameraCharacteristics(chars)); + CameraCharacteristics c = new CameraCharacteristics(chars); + mCameraManager.registerDeviceStateListener(c); + mPreviewExtender.init(cameraId, c); } @Override public boolean isExtensionAvailable(String cameraId, CameraMetadataNative chars) { - return mPreviewExtender.isExtensionAvailable(cameraId, - new CameraCharacteristics(chars)); + CameraCharacteristics c = new CameraCharacteristics(chars); + mCameraManager.registerDeviceStateListener(c); + return mPreviewExtender.isExtensionAvailable(cameraId, c); } @Override @@ -1283,8 +1289,9 @@ public class CameraExtensionsProxyService extends Service { @Override public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) { - mImageExtender.onInit(cameraId, new CameraCharacteristics(cameraCharacteristics), - CameraExtensionsProxyService.this); + CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics); + mCameraManager.registerDeviceStateListener(chars); + mImageExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this); mCameraId = cameraId; } @@ -1310,13 +1317,16 @@ public class CameraExtensionsProxyService extends Service { @Override public void init(String cameraId, CameraMetadataNative chars) { - mImageExtender.init(cameraId, new CameraCharacteristics(chars)); + CameraCharacteristics c = new CameraCharacteristics(chars); + mCameraManager.registerDeviceStateListener(c); + mImageExtender.init(cameraId, c); } @Override public boolean isExtensionAvailable(String cameraId, CameraMetadataNative chars) { - return mImageExtender.isExtensionAvailable(cameraId, - new CameraCharacteristics(chars)); + CameraCharacteristics c = new CameraCharacteristics(chars); + mCameraManager.registerDeviceStateListener(c); + return mImageExtender.isExtensionAvailable(cameraId, c); } @Override diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java index 46bda06d0e04..27d4ea71dfc0 100644 --- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java +++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java @@ -21,6 +21,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; +import android.os.UserManager; import android.util.Log; import android.webkit.PacProcessor; @@ -33,16 +34,44 @@ import java.net.URL; public class PacService extends Service { private static final String TAG = "PacService"; - private Object mLock = new Object(); + private final Object mLock = new Object(); + // Webkit PacProcessor cannot be instantiated before the user is unlocked, so this field is + // initialized lazily. @GuardedBy("mLock") - private final PacProcessor mPacProcessor = PacProcessor.getInstance(); + private PacProcessor mPacProcessor; + + // Stores PAC script when setPacFile is called before mPacProcessor is available. In case the + // script was already fed to the PacProcessor, it should be null. + @GuardedBy("mLock") + private String mPendingScript; private ProxyServiceStub mStub = new ProxyServiceStub(); @Override public void onCreate() { super.onCreate(); + + synchronized (mLock) { + checkPacProcessorLocked(); + } + } + + /** + * Initializes PacProcessor if it hasn't been initialized yet and if the system user is + * unlocked, e.g. after the user has entered their PIN after a reboot. + * Returns whether PacProcessor is available. + */ + private boolean checkPacProcessorLocked() { + if (mPacProcessor != null) { + return true; + } + UserManager um = getSystemService(UserManager.class); + if (um.isUserUnlocked()) { + mPacProcessor = PacProcessor.getInstance(); + return true; + } + return false; } @Override @@ -74,7 +103,20 @@ public class PacService extends Service { } synchronized (mLock) { - return mPacProcessor.findProxyForUrl(url); + if (checkPacProcessorLocked()) { + // Apply pending script in case it was set before processor was ready. + if (mPendingScript != null) { + if (!mPacProcessor.setProxyScript(mPendingScript)) { + Log.e(TAG, "Unable to parse proxy script."); + } + mPendingScript = null; + } + return mPacProcessor.findProxyForUrl(url); + } else { + Log.e(TAG, "PacProcessor isn't ready during early boot," + + " request will be direct"); + return null; + } } } catch (MalformedURLException e) { throw new IllegalArgumentException("Invalid URL was passed"); @@ -88,8 +130,13 @@ public class PacService extends Service { throw new SecurityException(); } synchronized (mLock) { - if (!mPacProcessor.setProxyScript(script)) { - Log.e(TAG, "Unable to parse proxy script."); + if (checkPacProcessorLocked()) { + if (!mPacProcessor.setProxyScript(script)) { + Log.e(TAG, "Unable to parse proxy script."); + } + } else { + Log.d(TAG, "PAC processor isn't ready, saving script for later."); + mPendingScript = script; } } } diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java index 7ba032f683b8..cf4c8a356662 100644 --- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java @@ -114,6 +114,9 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { private int mScreenState; @GuardedBy("this") + private int[] mPerDisplayScreenStates = null; + + @GuardedBy("this") private boolean mUseLatestStates = true; @GuardedBy("this") @@ -291,8 +294,8 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } @Override - public Future<?> scheduleSyncDueToScreenStateChange( - int flags, boolean onBattery, boolean onBatteryScreenOff, int screenState) { + public Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery, + boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) { synchronized (BatteryExternalStatsWorker.this) { if (mCurrentFuture == null || (mUpdateFlags & UPDATE_CPU) == 0) { mOnBattery = onBattery; @@ -301,6 +304,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } // always update screen state mScreenState = screenState; + mPerDisplayScreenStates = perDisplayScreenStates; return scheduleSyncLocked("screen-state", flags); } } @@ -432,6 +436,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { final boolean onBattery; final boolean onBatteryScreenOff; final int screenState; + final int[] displayScreenStates; final boolean useLatestStates; synchronized (BatteryExternalStatsWorker.this) { updateFlags = mUpdateFlags; @@ -440,6 +445,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { onBattery = mOnBattery; onBatteryScreenOff = mOnBatteryScreenOff; screenState = mScreenState; + displayScreenStates = mPerDisplayScreenStates; useLatestStates = mUseLatestStates; mUpdateFlags = 0; mCurrentReason = null; @@ -461,7 +467,8 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } try { updateExternalStatsLocked(reason, updateFlags, onBattery, - onBatteryScreenOff, screenState, useLatestStates); + onBatteryScreenOff, screenState, displayScreenStates, + useLatestStates); } finally { if (DEBUG) { Slog.d(TAG, "end updateExternalStatsSync"); @@ -506,7 +513,8 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { @GuardedBy("mWorkerLock") private void updateExternalStatsLocked(final String reason, int updateFlags, boolean onBattery, - boolean onBatteryScreenOff, int screenState, boolean useLatestStates) { + boolean onBatteryScreenOff, int screenState, int[] displayScreenStates, + boolean useLatestStates) { // We will request data from external processes asynchronously, and wait on a timeout. SynchronousResultReceiver wifiReceiver = null; SynchronousResultReceiver bluetoothReceiver = null; @@ -659,11 +667,12 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { // Inform mStats about each applicable measured energy (unless addressed elsewhere). if (measuredEnergyDeltas != null) { - final long displayChargeUC = measuredEnergyDeltas.displayChargeUC; - if (displayChargeUC != MeasuredEnergySnapshot.UNAVAILABLE) { - // If updating, pass in what BatteryExternalStatsWorker thinks screenState is. - mStats.updateDisplayMeasuredEnergyStatsLocked(displayChargeUC, screenState, - elapsedRealtime); + final long[] displayChargeUC = measuredEnergyDeltas.displayChargeUC; + if (displayChargeUC != null && displayChargeUC.length > 0) { + // If updating, pass in what BatteryExternalStatsWorker thinks + // displayScreenStates is. + mStats.updateDisplayMeasuredEnergyStatsLocked(displayChargeUC, + displayScreenStates, elapsedRealtime); } final long gnssChargeUC = measuredEnergyDeltas.gnssChargeUC; @@ -948,6 +957,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { switch (consumer.type) { case EnergyConsumerType.OTHER: case EnergyConsumerType.CPU_CLUSTER: + case EnergyConsumerType.DISPLAY: break; default: Slog.w(TAG, "EnergyConsumer '" + consumer.name + "' has unexpected ordinal " diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index ae14ca7b66bd..8d10d562520a 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1215,7 +1215,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub mHandler.post(() -> { if (DBG) Slog.d(TAG, "begin noteScreenState"); synchronized (mStats) { - mStats.noteScreenStateLocked(state, elapsedRealtime, uptime, currentTime); + mStats.noteScreenStateLocked(0, state, elapsedRealtime, uptime, currentTime); } if (DBG) Slog.d(TAG, "end noteScreenState"); }); @@ -1230,7 +1230,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub final long uptime = SystemClock.uptimeMillis(); mHandler.post(() -> { synchronized (mStats) { - mStats.noteScreenBrightnessLocked(brightness, elapsedRealtime, uptime); + mStats.noteScreenBrightnessLocked(0, brightness, elapsedRealtime, uptime); } }); } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 503b3a93b31f..94bf62f8b9b7 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -1568,17 +1568,23 @@ public final class BroadcastQueue { perm = PackageManager.PERMISSION_DENIED; } - if (perm == PackageManager.PERMISSION_GRANTED) { - skip = true; - break; - } - int appOp = AppOpsManager.permissionToOpCode(excludedPermission); if (appOp != AppOpsManager.OP_NONE) { - if (mService.getAppOpsManager().checkOpNoThrow(appOp, + // When there is an app op associated with the permission, + // skip when both the permission and the app op are + // granted. + if ((perm == PackageManager.PERMISSION_GRANTED) && ( + mService.getAppOpsManager().checkOpNoThrow(appOp, info.activityInfo.applicationInfo.uid, info.activityInfo.packageName) - == AppOpsManager.MODE_ALLOWED) { + == AppOpsManager.MODE_ALLOWED)) { + skip = true; + break; + } + } else { + // When there is no app op associated with the permission, + // skip when permission is granted. + if (perm == PackageManager.PERMISSION_GRANTED) { skip = true; break; } diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java index a9fca4f24026..0359aa531c64 100644 --- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java +++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java @@ -49,6 +49,9 @@ public class MeasuredEnergySnapshot { /** Number of ordinals for {@link EnergyConsumerType#CPU_CLUSTER}. */ private final int mNumCpuClusterOrdinals; + /** Number of ordinals for {@link EnergyConsumerType#DISPLAY}. */ + private final int mNumDisplayOrdinals; + /** Number of ordinals for {@link EnergyConsumerType#OTHER}. */ private final int mNumOtherOrdinals; @@ -95,6 +98,7 @@ public class MeasuredEnergySnapshot { mNumCpuClusterOrdinals = calculateNumOrdinals(EnergyConsumerType.CPU_CLUSTER, idToConsumerMap); + mNumDisplayOrdinals = calculateNumOrdinals(EnergyConsumerType.DISPLAY, idToConsumerMap); mNumOtherOrdinals = calculateNumOrdinals(EnergyConsumerType.OTHER, idToConsumerMap); mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals); } @@ -108,7 +112,7 @@ public class MeasuredEnergySnapshot { public long[] cpuClusterChargeUC = null; /** The chargeUC for {@link EnergyConsumerType#DISPLAY}. */ - public long displayChargeUC = UNAVAILABLE; + public long[] displayChargeUC = null; /** The chargeUC for {@link EnergyConsumerType#GNSS}. */ public long gnssChargeUC = UNAVAILABLE; @@ -212,7 +216,10 @@ public class MeasuredEnergySnapshot { break; case EnergyConsumerType.DISPLAY: - output.displayChargeUC = deltaChargeUC; + if (output.displayChargeUC == null) { + output.displayChargeUC = new long[mNumDisplayOrdinals]; + } + output.displayChargeUC[ordinal] = deltaChargeUC; break; case EnergyConsumerType.GNSS: diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index e09ba346f8d8..655278657b83 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -96,6 +96,7 @@ import android.media.IRingtonePlayer; import android.media.ISpatializerCallback; import android.media.ISpatializerHeadToSoundStagePoseCallback; import android.media.ISpatializerHeadTrackingModeCallback; +import android.media.ISpatializerOutputCallback; import android.media.IStrategyPreferredDevicesDispatcher; import android.media.IVolumeController; import android.media.MediaMetrics; @@ -8498,6 +8499,26 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.getEffectParameter(key, value); } + /** @see Spatializer#getOutput */ + public int getSpatializerOutput() { + enforceModifyDefaultAudioEffectsPermission(); + return mSpatializerHelper.getOutput(); + } + + /** @see Spatializer#setOnSpatializerOutputChangedListener */ + public void registerSpatializerOutputCallback(ISpatializerOutputCallback cb) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(cb); + mSpatializerHelper.registerSpatializerOutputCallback(cb); + } + + /** @see Spatializer#clearOnSpatializerOutputChangedListener */ + public void unregisterSpatializerOutputCallback(ISpatializerOutputCallback cb) { + enforceModifyDefaultAudioEffectsPermission(); + Objects.requireNonNull(cb); + mSpatializerHelper.unregisterSpatializerOutputCallback(cb); + } + /** * post a message to schedule init/release of head tracking sensors * @param init initialization if true, release if false diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index b2fa86b69cfc..7cd027c7550f 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -31,6 +31,7 @@ import android.media.ISpatializerCallback; import android.media.ISpatializerHeadToSoundStagePoseCallback; import android.media.ISpatializerHeadTrackingCallback; import android.media.ISpatializerHeadTrackingModeCallback; +import android.media.ISpatializerOutputCallback; import android.media.SpatializationLevel; import android.media.Spatializer; import android.media.SpatializerHeadTrackingMode; @@ -76,6 +77,7 @@ public class SpatializerHelper { 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 int mSpatOutput = 0; private @Nullable ISpatializer mSpat; private @Nullable SpatializerCallback mSpatCallback; private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback; @@ -213,6 +215,18 @@ public class SpatializerHelper { postInitSensors(true); } } + + public void onOutputChanged(int output) { + logd("SpatializerCallback.onOutputChanged output:" + output); + int oldOutput; + synchronized (SpatializerHelper.this) { + oldOutput = mSpatOutput; + mSpatOutput = output; + } + if (oldOutput != output) { + dispatchOutputUpdate(output); + } + } }; // spatializer head tracking callback from native @@ -782,6 +796,60 @@ public class SpatializerHelper { } //------------------------------------------------------ + // output + + /** @see Spatializer#getOutput */ + synchronized int getOutput() { + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + throw (new IllegalStateException( + "Can't get output without a spatializer")); + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + if (mSpat == null) { + throw (new IllegalStateException( + "null Spatializer for getOutput")); + } + break; + } + // mSpat != null + try { + return mSpat.getOutput(); + } catch (RemoteException e) { + Log.e(TAG, "Error in getOutput", e); + return 0; + } + } + + final RemoteCallbackList<ISpatializerOutputCallback> mOutputCallbacks = + new RemoteCallbackList<ISpatializerOutputCallback>(); + + synchronized void registerSpatializerOutputCallback( + @NonNull ISpatializerOutputCallback callback) { + mOutputCallbacks.register(callback); + } + + synchronized void unregisterSpatializerOutputCallback( + @NonNull ISpatializerOutputCallback callback) { + mOutputCallbacks.unregister(callback); + } + + private void dispatchOutputUpdate(int output) { + final int nbCallbacks = mOutputCallbacks.beginBroadcast(); + for (int i = 0; i < nbCallbacks; i++) { + try { + mOutputCallbacks.getBroadcastItem(i).dispatchSpatializerOutputChanged(output); + } catch (RemoteException e) { + Log.e(TAG, "Error in dispatchOutputUpdate", e); + } + } + mOutputCallbacks.finishBroadcast(); + } + + //------------------------------------------------------ // sensors private void initSensors(boolean init) { if (mSensorManager == null) { @@ -825,9 +893,18 @@ public class SpatializerHelper { } synchronized void onInitSensors(boolean init) { - final int[] modes = getSupportedHeadTrackingModes(); - if (modes.length == 0) { - Log.i(TAG, "not initializing sensors, no headtracking supported"); + final String action = init ? "initializing" : "releasing"; + if (mSpat == null) { + Log.e(TAG, "not " + action + " sensors, null spatializer"); + return; + } + try { + if (!mSpat.isHeadTrackingSupported()) { + Log.e(TAG, "not " + action + " sensors, spatializer doesn't support headtracking"); + return; + } + } catch (RemoteException e) { + Log.e(TAG, "not " + action + " sensors, error querying headtracking", e); return; } initSensors(init); diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 42b676f0d816..9d2cff9901e2 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -15,6 +15,7 @@ */ package com.android.server.camera; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.os.Build.VERSION_CODES.M; import android.annotation.IntDef; @@ -39,7 +40,9 @@ import android.hardware.CameraSessionStats; import android.hardware.CameraStreamStats; import android.hardware.ICameraService; import android.hardware.ICameraServiceProxy; +import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraMetadata; +import android.hardware.camera2.CaptureRequest; import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManager.FoldStateListener; import android.hardware.display.DisplayManager; @@ -346,13 +349,13 @@ public class CameraServiceProxy extends SystemService private final TaskStateHandler mTaskStackListener = new TaskStateHandler(); - private final class TaskInfo { - private int frontTaskId; - private boolean isResizeable; - private boolean isFixedOrientationLandscape; - private boolean isFixedOrientationPortrait; - private int displayId; - private int userId; + public static final class TaskInfo { + public int frontTaskId; + public boolean isResizeable; + public boolean isFixedOrientationLandscape; + public boolean isFixedOrientationPortrait; + public int displayId; + public int userId; } private final class TaskStateHandler extends TaskStackListener { @@ -367,7 +370,8 @@ public class CameraServiceProxy extends SystemService synchronized (mMapLock) { TaskInfo info = new TaskInfo(); info.frontTaskId = taskInfo.taskId; - info.isResizeable = taskInfo.isResizeable; + info.isResizeable = + (taskInfo.topActivityInfo.resizeMode != RESIZE_MODE_UNRESIZEABLE); info.displayId = taskInfo.displayId; info.userId = taskInfo.userId; info.isFixedOrientationLandscape = ActivityInfo.isFixedOrientationLandscape( @@ -427,97 +431,108 @@ public class CameraServiceProxy extends SystemService } }; - private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() { - private boolean isMOrBelow(Context ctx, String packageName) { - try { - return ctx.getPackageManager().getPackageInfo( - packageName, 0).applicationInfo.targetSdkVersion <= M; - } catch (PackageManager.NameNotFoundException e) { - Slog.e(TAG,"Package name not found!"); - } - return false; + private static boolean isMOrBelow(Context ctx, String packageName) { + try { + return ctx.getPackageManager().getPackageInfo( + packageName, 0).applicationInfo.targetSdkVersion <= M; + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG,"Package name not found!"); } + return false; + } - /** - * Gets whether crop-rotate-scale is needed. - */ - private boolean getNeedCropRotateScale(@NonNull Context ctx, @NonNull String packageName, - @Nullable TaskInfo taskInfo, int sensorOrientation, int lensFacing, - boolean ignoreResizableAndSdkCheck) { - if (taskInfo == null) { - return false; - } + /** + * Estimate the app crop-rotate-scale compensation value. + */ + public static int getCropRotateScale(@NonNull Context ctx, @NonNull String packageName, + @Nullable TaskInfo taskInfo, int displayRotation, int lensFacing, + boolean ignoreResizableAndSdkCheck) { + if (taskInfo == null) { + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } - // External cameras do not need crop-rotate-scale. - if (lensFacing != CameraMetadata.LENS_FACING_FRONT - && lensFacing != CameraMetadata.LENS_FACING_BACK) { - Log.v(TAG, "lensFacing=" + lensFacing + ". Crop-rotate-scale is disabled."); - return false; - } + // External cameras do not need crop-rotate-scale. + if (lensFacing != CameraMetadata.LENS_FACING_FRONT + && lensFacing != CameraMetadata.LENS_FACING_BACK) { + Log.v(TAG, "lensFacing=" + lensFacing + ". Crop-rotate-scale is disabled."); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } - // In case the activity behavior is not explicitly overridden, enable the - // crop-rotate-scale workaround if the app targets M (or below) or is not - // resizeable. - if (!ignoreResizableAndSdkCheck && !isMOrBelow(ctx, packageName) && - taskInfo.isResizeable) { - Slog.v(TAG, - "The activity is N or above and claims to support resizeable-activity. " - + "Crop-rotate-scale is disabled."); - return false; - } + // In case the activity behavior is not explicitly overridden, enable the + // crop-rotate-scale workaround if the app targets M (or below) or is not + // resizeable. + if (!ignoreResizableAndSdkCheck && !isMOrBelow(ctx, packageName) && + taskInfo.isResizeable) { + Slog.v(TAG, + "The activity is N or above and claims to support resizeable-activity. " + + "Crop-rotate-scale is disabled."); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } - DisplayManager displayManager = ctx.getSystemService(DisplayManager.class); - int rotationDegree = 0; - if (displayManager != null) { - Display display = displayManager.getDisplay(taskInfo.displayId); - if (display == null) { - Slog.e(TAG, "Invalid display id: " + taskInfo.displayId); - return false; - } + if (!taskInfo.isFixedOrientationPortrait && !taskInfo.isFixedOrientationLandscape) { + Log.v(TAG, "Non-fixed orientation activity. Crop-rotate-scale is disabled."); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } - int rotation = display.getRotation(); - switch (rotation) { - case Surface.ROTATION_0: - rotationDegree = 0; - break; - case Surface.ROTATION_90: - rotationDegree = 90; - break; - case Surface.ROTATION_180: - rotationDegree = 180; - break; - case Surface.ROTATION_270: - rotationDegree = 270; - break; - } - } else { - Slog.e(TAG, "Failed to query display manager!"); - return false; - } + int rotationDegree; + switch (displayRotation) { + case Surface.ROTATION_0: + rotationDegree = 0; + break; + case Surface.ROTATION_90: + rotationDegree = 90; + break; + case Surface.ROTATION_180: + rotationDegree = 180; + break; + case Surface.ROTATION_270: + rotationDegree = 270; + break; + default: + Log.e(TAG, "Unsupported display rotation: " + displayRotation); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } - // Here we only need to know whether the camera is landscape or portrait. Therefore we - // don't need to consider whether it is a front or back camera. The formula works for - // both. - boolean landscapeCamera = ((rotationDegree + sensorOrientation) % 180 == 0); - Slog.v(TAG, - "Display.getRotation()=" + rotationDegree - + " CameraCharacteristics.SENSOR_ORIENTATION=" + sensorOrientation - + " isFixedOrientationPortrait=" + taskInfo.isFixedOrientationPortrait - + " isFixedOrientationLandscape=" + - taskInfo.isFixedOrientationLandscape); - // We need to do crop-rotate-scale when camera is landscape and activity is portrait or - // vice versa. - return (taskInfo.isFixedOrientationPortrait && landscapeCamera) - || (taskInfo.isFixedOrientationLandscape && !landscapeCamera); + Slog.v(TAG, + "Display.getRotation()=" + rotationDegree + + " isFixedOrientationPortrait=" + taskInfo.isFixedOrientationPortrait + + " isFixedOrientationLandscape=" + + taskInfo.isFixedOrientationLandscape); + // We are trying to estimate the necessary rotation compensation for clients that + // don't handle various display orientations. + // The logic that is missing on client side is similar to the reference code + // in {@link android.hardware.Camera#setDisplayOrientation} where "info.orientation" + // is already applied in "CameraUtils::getRotationTransform". + // Care should be taken to reverse the rotation direction depending on the camera + // lens facing. + if (rotationDegree == 0) { + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + if (lensFacing == CameraCharacteristics.LENS_FACING_FRONT) { + // Switch direction for front facing cameras + rotationDegree = 360 - rotationDegree; } + switch (rotationDegree) { + case 90: + return CaptureRequest.SCALER_ROTATE_AND_CROP_90; + case 270: + return CaptureRequest.SCALER_ROTATE_AND_CROP_270; + case 180: + return CaptureRequest.SCALER_ROTATE_AND_CROP_180; + case 0: + default: + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + } + + private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() { @Override - public boolean isRotateAndCropOverrideNeeded(String packageName, int sensorOrientation, - int lensFacing) { + public int getRotateAndCropOverride(String packageName, int lensFacing) { if (Binder.getCallingUid() != Process.CAMERASERVER_UID) { Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " + " camera service UID!"); - return false; + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; } // TODO: Modify the sensor orientation in camera characteristics along with any 3A @@ -531,10 +546,10 @@ public class CameraServiceProxy extends SystemService if (CompatChanges.isChangeEnabled(OVERRIDE_CAMERA_ROTATE_AND_CROP, packageName, UserHandle.getUserHandleForUid(taskInfo.userId))) { Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP enabled!"); - return true; + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; } else { Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP disabled!"); - return false; + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; } } boolean ignoreResizableAndSdkCheck = false; @@ -544,7 +559,23 @@ public class CameraServiceProxy extends SystemService Slog.v(TAG, "OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK enabled!"); ignoreResizableAndSdkCheck = true; } - return getNeedCropRotateScale(mContext, packageName, taskInfo, sensorOrientation, + + DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); + int displayRotation; + if (displayManager != null) { + Display display = displayManager.getDisplay(taskInfo.displayId); + if (display == null) { + Slog.e(TAG, "Invalid display id: " + taskInfo.displayId); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + + displayRotation = display.getRotation(); + } else { + Slog.e(TAG, "Failed to query display manager!"); + return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE; + } + + return getCropRotateScale(mContext, packageName, taskInfo, displayRotation, lensFacing, ignoreResizableAndSdkCheck); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 63d32c8da2b2..768587a6a2b8 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1052,11 +1052,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } assert(state != Display.STATE_UNKNOWN); - // Initialize things the first time the power state is changed. - if (mustInitialize) { - initialize(state); - } - // Apply the proximity sensor. if (mProximitySensor != null) { if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) { @@ -1107,6 +1102,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call state = Display.STATE_OFF; } + // Initialize things the first time the power state is changed. + if (mustInitialize) { + initialize(state); + } + // Animate the screen state change unless already animating. // The transition may be deferred, so after this point we will use the // actual state instead of the desired one. diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java index fa33338a61e7..03e421bfed67 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java @@ -209,6 +209,12 @@ public class ContextHubClientBroker extends IContextHubClient.Stub */ private AtomicBoolean mIsPendingIntentCancelled = new AtomicBoolean(false); + /** + * True if a permissions query has been issued and is being processed. Used to prevent too many + * queries from being issued by a single client at once. + */ + private AtomicBoolean mIsPermQueryIssued = new AtomicBoolean(false); + /* * True if the application creating the client has the ACCESS_CONTEXT_HUB permission. */ @@ -240,11 +246,11 @@ public class ContextHubClientBroker extends IContextHubClient.Stub private final IContextHubTransactionCallback mQueryPermsCallback = new IContextHubTransactionCallback.Stub() { @Override - public void onTransactionComplete(int result) { - } + public void onTransactionComplete(int result) {} @Override public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) { + mIsPermQueryIssued.set(false); if (result != ContextHubTransaction.RESULT_SUCCESS && nanoAppStateList != null) { Log.e(TAG, "Permissions query failed, but still received nanoapp state"); } else if (nanoAppStateList != null) { @@ -656,9 +662,11 @@ public class ContextHubClientBroker extends IContextHubClient.Stub * communicated with in the past. */ private void checkNanoappPermsAsync() { - ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction( - mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage); - mTransactionManager.addTransaction(transaction); + if (!mIsPermQueryIssued.getAndSet(true)) { + ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction( + mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage); + mTransactionManager.addTransaction(transaction); + } } private int updateNanoAppAuthState( diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java index cc51cea05160..22a675ad39ab 100644 --- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java @@ -105,15 +105,20 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation synchronized (mLock) { mDeviceIdleHelper.addListener(this); - onDeviceIdleChanged(mDeviceIdleHelper.isDeviceIdle()); + mDeviceIdle = mDeviceIdleHelper.isDeviceIdle(); + mDeviceStationaryHelper.addListener(this); + mDeviceStationary = false; + mDeviceStationaryRealtimeMs = Long.MIN_VALUE; + + onThrottlingChangedLocked(false); } } @Override protected void onStop() { synchronized (mLock) { + mDeviceStationaryHelper.removeListener(this); mDeviceIdleHelper.removeListener(this); - onDeviceIdleChanged(false); mIncomingRequest = ProviderRequest.EMPTY_REQUEST; mOutgoingRequest = ProviderRequest.EMPTY_REQUEST; @@ -146,27 +151,13 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation } mDeviceIdle = deviceIdle; - - if (deviceIdle) { - // device stationary helper will deliver an immediate listener update - mDeviceStationaryHelper.addListener(this); - } else { - mDeviceStationaryHelper.removeListener(this); - mDeviceStationary = false; - mDeviceStationaryRealtimeMs = Long.MIN_VALUE; - onThrottlingChangedLocked(false); - } + onThrottlingChangedLocked(false); } } @Override public void onDeviceStationaryChanged(boolean deviceStationary) { synchronized (mLock) { - if (!mDeviceIdle) { - // stationary detection is only registered while idle - ignore late notifications - return; - } - if (mDeviceStationary == deviceStationary) { return; } diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 9f02c3caa388..9be618cb7add 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -338,6 +338,25 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override + public void setBluetoothA2dpOn(IMediaRouterClient client, boolean on) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + mAudioService.setBluetoothA2dpOn(on); + } + } catch (RemoteException ex) { + Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn. on=" + on); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + @Override public void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan) { if (client == null) { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 84be7f5809e6..20687c6764db 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -4073,7 +4073,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (hasRestrictedModeAccess(uid)) { uidBlockedState.allowedReasons |= ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS; } else { - uidBlockedState.allowedReasons &= ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS; + uidBlockedState.allowedReasons &= ~ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS; } uidBlockedState.updateEffectiveBlockedReasons(); if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 7ba0f04a435f..2f550c6e9338 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -7076,7 +7076,6 @@ public class NotificationManagerService extends SystemService { r.isUpdate = true; final boolean isInterruptive = isVisuallyInterruptive(old, r); r.setTextChanged(isInterruptive); - r.setInterruptive(isInterruptive); } mNotificationsByKey.put(n.getKey(), r); @@ -9424,7 +9423,7 @@ public class NotificationManagerService extends SystemService { record.getSystemGeneratedSmartActions(), record.getSmartReplies(), record.canBubble(), - record.isInterruptive(), + record.isTextChanged(), record.isConversation(), record.getShortcutInfo(), record.getRankingScore() == 0 diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index b4ca5118e10f..b6b54fc19011 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -1143,6 +1143,10 @@ public final class NotificationRecord { return mIsInterruptive; } + public boolean isTextChanged() { + return mTextChanged; + } + /** Returns the time the notification audibly alerted the user. */ public long getLastAudiblyAlertedMs() { return mLastAudiblyAlertedMs; diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java index ed1f5f567d95..3fc416931a06 100644 --- a/services/core/java/com/android/server/os/NativeTombstoneManager.java +++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java @@ -356,7 +356,7 @@ public final class NativeTombstoneManager { return false; } - if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 1000) { + if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 5000) { return false; } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 301914615562..7096f6f419b7 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -913,6 +913,11 @@ final class DefaultPermissionGrantPolicy { } grantPermissionsToSystemPackage(pm, dialerPackage, userId, CONTACTS_PERMISSIONS, SMS_PERMISSIONS, MICROPHONE_PERMISSIONS, CAMERA_PERMISSIONS); + boolean isAndroidAutomotive = + mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0); + if (isAndroidAutomotive) { + grantPermissionsToSystemPackage(pm, dialerPackage, userId, NEARBY_DEVICES_PERMISSIONS); + } } private void grantDefaultPermissionsToDefaultSystemSmsApp(PackageManagerWrapper pm, diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 5acff2b6c743..cda7407dc98c 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -47,7 +47,6 @@ import static android.view.KeyEvent.KEYCODE_VOLUME_UP; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; @@ -405,7 +404,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { private AccessibilityShortcutController mAccessibilityShortcutController; boolean mSafeMode; - private WindowState mKeyguardCandidate = null; // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key. // This is for car dock and this is updated from resource. @@ -1814,18 +1812,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() { @Override - public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration, - long statusBarAnimationStartTime, long statusBarAnimationDuration) { + public int onAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration, long statusBarAnimationStartTime, + long statusBarAnimationDuration) { // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't // need to call IKeyguardService#keyguardGoingAway here. return handleStartTransitionForKeyguardLw(keyguardGoingAway - && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation, duration); + && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation, + keyguardOccluding, duration); } @Override public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) { - handleStartTransitionForKeyguardLw(keyguardGoingAway, 0 /* duration */); + handleStartTransitionForKeyguardLw( + keyguardGoingAway, false /* keyguardOccludingStarted */, + 0 /* duration */); } }); @@ -3048,26 +3050,28 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPendingKeyguardOccluded = occluded; mKeyguardOccludedChanged = true; } else { - setKeyguardOccludedLw(occluded, false /* force */); + setKeyguardOccludedLw(occluded, false /* force */, + false /* transitionStarted */); } } @Override - public int applyKeyguardOcclusionChange() { + public int applyKeyguardOcclusionChange(boolean transitionStarted) { if (mKeyguardOccludedChanged) { if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded=" + mPendingKeyguardOccluded); - mKeyguardOccludedChanged = false; - if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */)) { + if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */, + transitionStarted)) { return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER; } } return 0; } - private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway, long duration) { - final int res = applyKeyguardOcclusionChange(); - if (res != 0) return res; + private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration) { + final int redoLayout = applyKeyguardOcclusionChange(keyguardOccluding); + if (redoLayout != 0) return redoLayout; if (keyguardGoingAway) { if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation"); startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration); @@ -3265,42 +3269,32 @@ public class PhoneWindowManager implements WindowManagerPolicy { mNavBarVirtualKeyHapticFeedbackEnabled = enabled; } - /** {@inheritDoc} */ - @Override - public void setKeyguardCandidateLw(WindowState win) { - mKeyguardCandidate = win; - setKeyguardOccludedLw(isKeyguardOccluded(), true /* force */); - } - /** * Updates the occluded state of the Keyguard. * * @param isOccluded Whether the Keyguard is occluded by another window. * @param force notify the occluded status to KeyguardService and update flags even though * occlude status doesn't change. + * @param transitionStarted {@code true} if keyguard (un)occluded transition started. * @return Whether the flags have changed and we have to redo the layout. */ - private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force) { + private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force, + boolean transitionStarted) { if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded); + mKeyguardOccludedChanged = false; if (isKeyguardOccluded() == isOccluded && !force) { return false; } final boolean showing = mKeyguardDelegate.isShowing(); final boolean animate = showing && !isOccluded; - mKeyguardDelegate.setOccluded(isOccluded, animate); - - if (!showing) { - return false; - } - if (mKeyguardCandidate != null) { - if (isOccluded) { - mKeyguardCandidate.getAttrs().flags &= ~FLAG_SHOW_WALLPAPER; - } else if (!mKeyguardDelegate.hasLockscreenWallpaper()) { - mKeyguardCandidate.getAttrs().flags |= FLAG_SHOW_WALLPAPER; - } - } - return true; + // When remote animation is enabled for keyguard (un)occlude transition, KeyguardService + // uses remote animation start as a signal to update its occlusion status ,so we don't need + // to notify here. + final boolean notify = !WindowManagerService.sEnableRemoteKeyguardOccludeAnimation + || !transitionStarted; + mKeyguardDelegate.setOccluded(isOccluded, animate, notify); + return showing; } /** {@inheritDoc} */ diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 78b03b2b88e7..87465a4b2ffe 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -173,8 +173,11 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { */ void onKeyguardOccludedChangedLw(boolean occluded); - /** Applies a keyguard occlusion change if one happened. */ - int applyKeyguardOcclusionChange(); + /** + * Applies a keyguard occlusion change if one happened. + * @param transitionStarted Whether keyguard (un)occlude transition is starting or not. + */ + int applyKeyguardOcclusionChange(boolean transitionStarted); /** * Interface to the Window Manager state associated with a particular @@ -719,13 +722,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId); /** - * Set or clear a window which can behave as the keyguard. - * - * @param win The window which can behave as the keyguard. - */ - void setKeyguardCandidateLw(@Nullable WindowState win); - - /** * Create and return an animation to re-display a window that was force hidden by Keyguard. */ public Animation createHiddenByKeyguardExit(boolean onWallpaper, diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index 86ff33e8cc42..0080ec6cc989 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -29,7 +29,6 @@ import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardService; import com.android.server.UiThread; import com.android.server.policy.WindowManagerPolicy.OnKeyguardExitResult; -import com.android.server.wm.WindowManagerService; import java.io.PrintWriter; @@ -235,13 +234,6 @@ public class KeyguardServiceDelegate { return false; } - public boolean hasLockscreenWallpaper() { - if (mKeyguardService != null) { - return mKeyguardService.hasLockscreenWallpaper(); - } - return false; - } - public boolean hasKeyguard() { return mKeyguardState.deviceHasKeyguard; } @@ -259,13 +251,8 @@ public class KeyguardServiceDelegate { } } - /** - * @deprecated Notify occlude status change via remote animation. - */ - @Deprecated - public void setOccluded(boolean isOccluded, boolean animate) { - if (!WindowManagerService.sEnableRemoteKeyguardOccludeAnimation - && mKeyguardService != null) { + public void setOccluded(boolean isOccluded, boolean animate, boolean notify) { + if (mKeyguardService != null && notify) { if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate); mKeyguardService.setOccluded(isOccluded, animate); } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java index 051f555fab95..ac650ec0f564 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -261,10 +261,6 @@ public class KeyguardServiceWrapper implements IKeyguardService { return mKeyguardStateMonitor.isTrusted(); } - public boolean hasLockscreenWallpaper() { - return mKeyguardStateMonitor.hasLockscreenWallpaper(); - } - public boolean isSecure(int userId) { return mKeyguardStateMonitor.isSecure(userId); } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java index add0b01f1879..e6511372d62c 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java @@ -44,7 +44,6 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { private volatile boolean mSimSecure = true; private volatile boolean mInputRestricted = true; private volatile boolean mTrusted = false; - private volatile boolean mHasLockscreenWallpaper = false; private int mCurrentUserId; @@ -79,10 +78,6 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { return mTrusted; } - public boolean hasLockscreenWallpaper() { - return mHasLockscreenWallpaper; - } - @Override // Binder interface public void onShowingStateChanged(boolean showing) { mIsShowing = showing; @@ -110,11 +105,6 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { mCallback.onTrustedChanged(); } - @Override // Binder interface - public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) { - mHasLockscreenWallpaper = hasLockscreenWallpaper; - } - public interface StateCallback { void onTrustedChanged(); void onShowingChanged(); diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java index 6366280e1762..7ffff935128f 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java @@ -429,15 +429,7 @@ class ConversionUtil { private static @NonNull HidlMemory parcelFileDescriptorToHidlMemory(@Nullable ParcelFileDescriptor data, int dataSize) { if (dataSize > 0) { - // Extract a dup of the underlying FileDescriptor out of data. - FileDescriptor fd = new FileDescriptor(); - try { - ParcelFileDescriptor dup = data.dup(); - fd.setInt$(dup.detachFd()); - return HidlMemoryUtil.fileDescriptorToHidlMemory(fd, dataSize); - } catch (IOException e) { - throw new RuntimeException(e); - } + return HidlMemoryUtil.fileDescriptorToHidlMemory(data.getFileDescriptor(), dataSize); } else { return HidlMemoryUtil.fileDescriptorToHidlMemory(null, 0); } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 2a47512bb147..730766275f4a 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -1319,24 +1319,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return IExternalVibratorService.SCALE_MUTE; } - int mode = checkAppOpModeLocked(vib.getUid(), vib.getPackage(), - vib.getVibrationAttributes()); - if (mode != AppOpsManager.MODE_ALLOWED) { - ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib); - vibHolder.scale = IExternalVibratorService.SCALE_MUTE; - if (mode == AppOpsManager.MODE_ERRORED) { - Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid()); - endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS); - } else { - endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS); - } - return vibHolder.scale; - } - ExternalVibrationHolder cancelingExternalVibration = null; VibrationThread cancelingVibration = null; int scale; synchronized (mLock) { + Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked( + vib.getUid(), vib.getPackage(), vib.getVibrationAttributes()); + if (ignoreStatus != null) { + ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib); + vibHolder.scale = IExternalVibratorService.SCALE_MUTE; + endVibrationLocked(vibHolder, ignoreStatus); + return vibHolder.scale; + } if (mCurrentExternalVibration != null && mCurrentExternalVibration.externalVibration.equals(vib)) { // We are already playing this external vibration, so we can return the same diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index e59c82cfb6d0..cf9783fb9241 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -50,6 +50,7 @@ import android.accessibilityservice.AccessibilityTrace; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Application; import android.content.Context; import android.content.pm.PackageManagerInternal; @@ -134,6 +135,8 @@ final class AccessibilityController { private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>(); private SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver = new SparseArray<>(); + private SparseArray<IBinder> mFocusedWindow = new SparseArray<>(); + private int mFocusedDisplay = -1; // Set to true if initializing window population complete. private boolean mAllObserversInitialized = true; @@ -603,6 +606,29 @@ final class AccessibilityController { return display.getType() == Display.TYPE_VIRTUAL && dc.getParentWindow() != null; } + void onFocusChanged(InputTarget lastTarget, InputTarget newTarget) { + if (lastTarget != null) { + mFocusedWindow.remove(lastTarget.getDisplayId()); + } + if (newTarget != null) { + int displayId = newTarget.getDisplayId(); + IBinder clientBinder = newTarget.getIWindow().asBinder(); + mFocusedWindow.put(displayId, clientBinder); + } + } + + public void onDisplayRemoved(int displayId) { + mFocusedWindow.remove(displayId); + } + + public void setFocusedDisplay(int focusedDisplayId) { + mFocusedDisplay = focusedDisplayId; + } + + @Nullable IBinder getFocusedWindowToken() { + return mFocusedWindow.get(mFocusedDisplay); + } + /** * This class encapsulates the functionality related to display magnification. */ diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 6d144e1da234..7365f8844040 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -46,8 +46,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.isSplitScreenWindowingMode; -import static android.app.servertransaction.TransferSplashScreenViewStateItem.ATTACH_TO; -import static android.app.servertransaction.TransferSplashScreenViewStateItem.HANDOVER_TO; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_HOME; import static android.content.Intent.CATEGORY_LAUNCHER; @@ -401,10 +399,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private static final int STARTING_WINDOW_TYPE_SNAPSHOT = 1; private static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2; - /** - * Value to increment the z-layer when boosting a layer during animations. BOOST in l33tsp34k. - */ - @VisibleForTesting static final int Z_BOOST_BASE = 800570000; static final int INVALID_PID = -1; // How long we wait until giving up on the last activity to pause. This @@ -2326,7 +2320,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // unable to copy from shell, maybe it's not a splash screen. or something went wrong. // either way, abort and reset the sequence. if (parcelable == null - || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING) { + || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING + || mStartingWindow == null) { if (parcelable != null) { parcelable.clearIfNeeded(); } @@ -2335,13 +2330,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } // schedule attach splashScreen to client + final SurfaceControl windowAnimationLeash = TaskOrganizerController + .applyStartingWindowAnimation(mStartingWindow); try { mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT; mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, - TransferSplashScreenViewStateItem.obtain(ATTACH_TO, parcelable)); + TransferSplashScreenViewStateItem.obtain(parcelable, + windowAnimationLeash)); scheduleTransferSplashScreenTimeout(); } catch (Exception e) { Slog.w(TAG, "onCopySplashScreenComplete fail: " + this); + mStartingWindow.cancelAnimation(); parcelable.clearIfNeeded(); mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH; } @@ -2351,14 +2350,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A removeTransferSplashScreenTimeout(); // Client has draw the splash screen, so we can remove the starting window. if (mStartingWindow != null) { + mStartingWindow.cancelAnimation(); mStartingWindow.hide(false, false); } - try { - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, - TransferSplashScreenViewStateItem.obtain(HANDOVER_TO, null)); - } catch (Exception e) { - Slog.w(TAG, "onSplashScreenAttachComplete fail: " + this); - } // no matter what, remove the starting window. mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH; removeStartingWindowAnimation(false /* prepareAnimation */); @@ -4244,20 +4238,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return callback.test(this) ? this : null; } - @Override - protected void setLayer(Transaction t, int layer) { - if (!mSurfaceAnimator.hasLeash()) { - t.setLayer(mSurfaceControl, layer); - } - } - - @Override - protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) { - if (!mSurfaceAnimator.hasLeash()) { - t.setRelativeLayer(mSurfaceControl, relativeTo, layer); - } - } - void logStartActivity(int tag, Task task) { final Uri data = intent.getData(); final String strData = data != null ? data.toSafeString() : null; @@ -4714,6 +4694,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (app != null) { mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */); } + logAppCompatState(); } /** @@ -4741,7 +4722,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED); mTaskSupervisor.getActivityMetricsLogger().notifyVisibilityChanged(this); mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = true; - logAppCompatState(); } @VisibleForTesting @@ -4908,8 +4888,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * @param visible {@code true} if this {@link ActivityRecord} should become visible, otherwise * this should become invisible. * @param performLayout if {@code true}, perform surface placement after committing visibility. + * @param fromTransition {@code true} if this is part of finishing a transition. */ - void commitVisibility(boolean visible, boolean performLayout) { + void commitVisibility(boolean visible, boolean performLayout, boolean fromTransition) { // Reset the state of mVisibleSetFromTransferredStartingWindow since visibility is actually // been set by the app now. mVisibleSetFromTransferredStartingWindow = false; @@ -4959,7 +4940,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/); mUseTransferredAnimation = false; - postApplyAnimation(visible); + postApplyAnimation(visible, fromTransition); + } + + void commitVisibility(boolean visible, boolean performLayout) { + commitVisibility(visible, performLayout, false /* fromTransition */); } /** @@ -4970,10 +4955,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * * @param visible {@code true} if this {@link ActivityRecord} has become visible, otherwise * this has become invisible. + * @param fromTransition {@code true} if this call is part of finishing a transition. This is + * needed because the shell transition is no-longer active by the time + * commitVisibility is called. */ - private void postApplyAnimation(boolean visible) { + private void postApplyAnimation(boolean visible, boolean fromTransition) { final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled(); - final boolean delayed = isAnimating(TRANSITION | PARENTS | CHILDREN, + final boolean delayed = isAnimating(PARENTS | CHILDREN, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION | ANIMATION_TYPE_RECENTS); if (!delayed && !usingShellTransitions) { @@ -5012,7 +5000,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final DisplayContent displayContent = getDisplayContent(); if (!displayContent.mClosingApps.contains(this) - && !displayContent.mOpeningApps.contains(this)) { + && !displayContent.mOpeningApps.contains(this) + && !fromTransition) { // Take the screenshot before possibly hiding the WSA, otherwise the screenshot // will not be taken. mWmService.mTaskSnapshotController.notifyAppVisibilityChanged(this, visible); @@ -6787,12 +6776,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return candidate; } - SurfaceControl getAppAnimationLayer() { - return getAppAnimationLayer(isActivityTypeHome() ? ANIMATION_LAYER_HOME - : needsZBoost() ? ANIMATION_LAYER_BOOSTED - : ANIMATION_LAYER_STANDARD); - } - @Override boolean needsZBoost() { return mNeedsZBoost || super.needsZBoost(); @@ -6853,29 +6836,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A || mDisplayContent.isNextTransitionForward(); } - private int getAnimationLayer() { - // The leash is parented to the animation layer. We need to preserve the z-order by using - // the prefix order index, but we boost if necessary. - int layer; - if (!inPinnedWindowingMode()) { - layer = getPrefixOrderIndex(); - } else { - // Root pinned tasks have animations take place within themselves rather than an - // animation layer so we need to preserve the order relative to the root task (e.g. - // the order of our task/parent). - layer = getParent().getPrefixOrderIndex(); - } - - if (mNeedsZBoost) { - layer += Z_BOOST_BASE; - } - return layer; - } - @Override - public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { - t.setLayer(leash, getAnimationLayer()); - getDisplayContent().assignRootTaskOrdering(); + void resetSurfacePositionForAnimationLeash(SurfaceControl.Transaction t) { + // Noop as Activity may be offset for letterbox } @Override @@ -6904,7 +6867,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Crop to root task bounds. t.setLayer(leash, 0); - t.setLayer(mAnimationBoundsLayer, getAnimationLayer()); + t.setLayer(mAnimationBoundsLayer, getLastLayer()); // Reparent leash to animation bounds layer. t.reparent(leash, mAnimationBoundsLayer); @@ -7548,7 +7511,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** * Adjusts horizontal position of resolved bounds if they doesn't fill the parent using gravity * requested in the config or via an ADB command. For more context see {@link - * WindowManagerService#getLetterboxHorizontalPositionMultiplier}. + * LetterboxUiController#getHorizontalPositionMultiplier(Configuration)}. */ private void updateResolvedBoundsHorizontalPosition(Configuration newParentConfiguration) { final Configuration resolvedConfig = getResolvedOverrideConfiguration(); @@ -7885,7 +7848,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Below figure is an example that puts an activity which was launched in a larger container // into a smaller container. // The outermost rectangle is the real display bounds. - // "@" is the container app bounds (parent bounds or fixed orientation bouds) + // "@" is the container app bounds (parent bounds or fixed orientation bounds) // "#" is the {@code resolvedBounds} that applies to application. // "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled. // ------------------------------ @@ -7928,12 +7891,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mSizeCompatBounds = null; } - // Align to top of parent (bounds) - this is a UX choice and exclude the horizontal decor - // if needed. Horizontal position is adjusted in updateResolvedBoundsHorizontalPosition. + // Vertically center within parent (bounds) - this is a UX choice and exclude the horizontal + // decor if needed. Horizontal position is adjusted in + // updateResolvedBoundsHorizontalPosition. // Above coordinates are in "@" space, now place "*" and "#" to screen space. final boolean fillContainer = resolvedBounds.equals(containingBounds); final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left; - final int screenPosY = containerBounds.top; + final int screenPosY = mSizeCompatBounds == null + ? (containerBounds.height() - resolvedBounds.height()) / 2 + : (containerBounds.height() - mSizeCompatBounds.height()) / 2; if (screenPosX != 0 || screenPosY != 0) { if (mSizeCompatBounds != null) { mSizeCompatBounds.offset(screenPosX, screenPosY); diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index b71ad2ea7102..6ad2f7cad3c2 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -501,6 +501,8 @@ public class ActivityStartController { int startActivityInTaskFragment(@NonNull TaskFragment taskFragment, @NonNull Intent activityIntent, @Nullable Bundle activityOptions, @Nullable IBinder resultTo) { + final ActivityRecord caller = + resultTo != null ? ActivityRecord.forTokenLocked(resultTo) : null; return obtainStarter(activityIntent, "startActivityInTaskFragment") .setActivityOptions(activityOptions) .setInTaskFragment(taskFragment) @@ -508,6 +510,7 @@ public class ActivityStartController { .setRequestCode(-1) .setCallingUid(Binder.getCallingUid()) .setCallingPid(Binder.getCallingPid()) + .setUserId(caller != null ? caller.mUserId : mService.getCurrentUserId()) .execute(); } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 170789569b3f..dc5126dbf916 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1565,7 +1565,7 @@ class ActivityStarter { // transition based on a sub-action. // Only do the create here (and defer requestStart) since startActivityInner might abort. final TransitionController transitionController = r.mTransitionController; - final Transition newTransition = (!transitionController.isCollecting() + Transition newTransition = (!transitionController.isCollecting() && transitionController.getTransitionPlayer() != null) ? transitionController.createTransition(TRANSIT_OPEN) : null; RemoteTransition remoteTransition = r.takeRemoteTransition(); @@ -1620,6 +1620,10 @@ class ActivityStarter { // The activity is started new rather than just brought forward, so record // it as an existence change. transitionController.collectExistenceChange(r); + } else if (result == START_DELIVERED_TO_TOP && newTransition != null) { + // We just delivered to top, so there isn't an actual transition here + newTransition.abort(); + newTransition = null; } if (isTransient) { // `r` isn't guaranteed to be the actual relevant activity, so we must wait @@ -1979,7 +1983,7 @@ class ActivityStarter { // Allowing the embedding if the task is owned by system. final int hostUid = hostTask.effectiveUid; - if (hostUid == Process.SYSTEM_UID) { + if (UserHandle.getAppId(hostUid) == Process.SYSTEM_UID) { return true; } @@ -2737,9 +2741,11 @@ class ActivityStarter { // 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.getActivityType(), null /** options */, + mSourceRootTask, 0 /** launchFlags */); + // If target root task is created by organizer, let organizer handle reparent itself. + if (!mTargetRootTask.mCreatedByOrganizer && launchRootTask != null + && launchRootTask != mTargetRootTask) { mTargetRootTask.reparent(launchRootTask, POSITION_TOP); mTargetRootTask = launchRootTask; } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 7f3f1f86103f..60a514e4d612 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -991,6 +991,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { synchronized (mGlobalLock) { mWindowManager = wm; mRootWindowContainer = wm.mRoot; + mWindowOrganizerController.setWindowManager(wm); mTempConfig.setToDefaults(); mTempConfig.setLocales(LocaleList.getDefault()); mConfigurationSeq = mTempConfig.seq = 1; @@ -3423,9 +3424,15 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final List<RemoteAction> actions = r.pictureInPictureArgs.getActions(); mRootWindowContainer.moveActivityToPinnedRootTask( r, "enterPictureInPictureMode"); - final Task rootTask = r.getRootTask(); - rootTask.setPictureInPictureAspectRatio(aspectRatio); - rootTask.setPictureInPictureActions(actions); + final Task task = r.getTask(); + task.setPictureInPictureAspectRatio(aspectRatio); + task.setPictureInPictureActions(actions); + + // Continue the pausing process after entering pip. + if (task.getPausingActivity() == r) { + task.schedulePauseActivity(r, false /* userLeaving */, + false /* pauseImmediately */, "auto-pip"); + } } }; diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java index 529c4f608743..5899a4e89ca7 100644 --- a/services/core/java/com/android/server/wm/AnimationAdapter.java +++ b/services/core/java/com/android/server/wm/AnimationAdapter.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import android.annotation.NonNull; import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -52,7 +53,7 @@ interface AnimationAdapter { * @param finishCallback The callback to be invoked when the animation has finished. */ void startAnimation(SurfaceControl animationLeash, Transaction t, @AnimationType int type, - OnAnimationFinishedCallback finishCallback); + @NonNull OnAnimationFinishedCallback finishCallback); /** * Called when the animation that was started with {@link #startAnimation} was cancelled by the diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java index 892db9c33dbd..c881864dff25 100644 --- a/services/core/java/com/android/server/wm/AnrController.java +++ b/services/core/java/com/android/server/wm/AnrController.java @@ -31,7 +31,6 @@ import android.util.SparseArray; import android.view.InputApplicationHandle; import com.android.server.am.ActivityManagerService; -import com.android.server.wm.EmbeddedWindowController.EmbeddedWindow; import java.io.File; import java.util.ArrayList; @@ -81,21 +80,19 @@ class AnrController { final boolean aboveSystem; final ActivityRecord activity; synchronized (mService.mGlobalLock) { - WindowState windowState = mService.mInputToWindowMap.get(inputToken); - if (windowState != null) { - pid = windowState.mSession.mPid; - activity = windowState.mActivityRecord; - Slog.i(TAG_WM, "ANR in " + windowState.mAttrs.getTitle() + ". Reason:" + reason); - } else { - EmbeddedWindow embeddedWindow = mService.mEmbeddedWindowController.get(inputToken); - if (embeddedWindow == null) { - Slog.e(TAG_WM, "Unknown token, dropping notifyConnectionUnresponsive request"); - return; - } - pid = embeddedWindow.mOwnerPid; - windowState = embeddedWindow.mHostWindowState; - activity = null; // Don't blame the host process, instead blame the embedded pid. + InputTarget target = mService.getInputTargetFromToken(inputToken); + if (target == null) { + Slog.e(TAG_WM, "Unknown token, dropping notifyConnectionUnresponsive request"); + return; } + + WindowState windowState = target.getWindowState(); + pid = target.getPid(); + // Blame the activity if the input token belongs to the window. If the target is + // embedded, then we will blame the pid instead. + activity = (windowState.mInputChannelToken == inputToken) + ? windowState.mActivityRecord : null; + Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + reason); aboveSystem = isWindowAboveSystem(windowState); dumpAnrStateLocked(activity, windowState, reason); } @@ -109,19 +106,12 @@ class AnrController { void notifyWindowResponsive(IBinder inputToken) { final int pid; synchronized (mService.mGlobalLock) { - WindowState windowState = mService.mInputToWindowMap.get(inputToken); - if (windowState != null) { - pid = windowState.mSession.mPid; - } else { - // Check if the token belongs to an embedded window. - EmbeddedWindow embeddedWindow = mService.mEmbeddedWindowController.get(inputToken); - if (embeddedWindow == null) { - Slog.e(TAG_WM, - "Unknown token, dropping notifyWindowConnectionResponsive request"); - return; - } - pid = embeddedWindow.mOwnerPid; + InputTarget target = mService.getInputTargetFromToken(inputToken); + if (target == null) { + Slog.e(TAG_WM, "Unknown token, dropping notifyWindowConnectionResponsive request"); + return; } + pid = target.getPid(); } mService.mAmInternal.inputDispatchingResumed(pid); } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index e21a00b4aec6..bd08d01768e3 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -443,6 +443,7 @@ public class AppTransition implements Dump { int redoLayout = notifyAppTransitionStartingLocked( AppTransition.isKeyguardGoingAwayTransitOld(transit), + AppTransition.isKeyguardOccludeTransitOld(transit), topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0, topOpeningAnim != null ? topOpeningAnim.getStatusBarTransitionsStartTime() @@ -557,12 +558,14 @@ public class AppTransition implements Dump { } } - private int notifyAppTransitionStartingLocked(boolean keyguardGoingAway, long duration, - long statusBarAnimationStartTime, long statusBarAnimationDuration) { + private int notifyAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOcclude, long duration, long statusBarAnimationStartTime, + long statusBarAnimationDuration) { int redoLayout = 0; for (int i = 0; i < mListeners.size(); i++) { redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway, - duration, statusBarAnimationStartTime, statusBarAnimationDuration); + keyguardOcclude, duration, statusBarAnimationStartTime, + statusBarAnimationDuration); } return redoLayout; } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index ffaf710523ee..535a061ee4ab 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -124,6 +124,7 @@ public class AppTransitionController { @interface TransitContainerType {} private final ArrayMap<WindowContainer, Integer> mTempTransitionReasons = new ArrayMap<>(); + private final ArrayList<WindowContainer> mTempTransitionWindows = new ArrayList<>(); AppTransitionController(WindowManagerService service, DisplayContent displayContent) { mService = service; @@ -523,26 +524,44 @@ public class AppTransitionController { } } + private boolean transitionMayContainNonAppWindows(@TransitionOldType int transit) { + // We don't want to have the client to animate any non-app windows. + // Having {@code transit} of those types doesn't mean it will contain non-app windows, but + // non-app windows will only be included with those transition types. And we don't currently + // have any use case of those for TaskFragment transition. + // @see NonAppWindowAnimationAdapter#startNonAppWindowAnimations + if (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY + || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER + || transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT + || transit == TRANSIT_OLD_WALLPAPER_CLOSE) { + return true; + } + + // Check if the wallpaper is going to participate in the transition. We don't want to have + // the client to animate the wallpaper windows. + // @see WallpaperAnimationAdapter#startWallpaperAnimations + return mDisplayContent.mWallpaperController.isWallpaperVisible(); + } + /** - * Overrides the pending transition with the remote animation defined by the - * {@link ITaskFragmentOrganizer} if all windows in the transition are children of - * {@link TaskFragment} that are organized by the same organizer. - * - * @return {@code true} if the transition is overridden. + * Finds the common {@link android.window.TaskFragmentOrganizer} that organizes all app windows + * in the current transition. + * @return {@code null} if there is no such organizer, or if there are more than one. */ - private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit, - ArraySet<Integer> activityTypes) { - final ArrayList<WindowContainer> allWindows = new ArrayList<>(); - allWindows.addAll(mDisplayContent.mClosingApps); - allWindows.addAll(mDisplayContent.mOpeningApps); - allWindows.addAll(mDisplayContent.mChangingContainers); + @Nullable + private ITaskFragmentOrganizer findTaskFragmentOrganizerForAllWindows() { + mTempTransitionWindows.clear(); + mTempTransitionWindows.addAll(mDisplayContent.mClosingApps); + mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps); + mTempTransitionWindows.addAll(mDisplayContent.mChangingContainers); // It should only animated by the organizer if all windows are below the same leaf Task. Task leafTask = null; - for (int i = allWindows.size() - 1; i >= 0; i--) { - final ActivityRecord r = getAppFromContainer(allWindows.get(i)); + for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) { + final ActivityRecord r = getAppFromContainer(mTempTransitionWindows.get(i)); if (r == null) { - return false; + leafTask = null; + break; } // The activity may be a child of embedded Task, but we want to find the owner Task. // As a result, find the organized TaskFragment first. @@ -561,26 +580,31 @@ public class AppTransitionController { ? organizedTaskFragment.getTask() : r.getTask(); if (task == null) { - return false; + leafTask = null; + break; } // We don't want the organizer to handle transition of other non-embedded Task. if (leafTask != null && leafTask != task) { - return false; + leafTask = null; + break; } final ActivityRecord rootActivity = task.getRootActivity(); // We don't want the organizer to handle transition when the whole app is closing. if (rootActivity == null) { - return false; + leafTask = null; + break; } // We don't want the organizer to handle transition of non-embedded activity of other // app. if (r.getUid() != rootActivity.getUid() && !r.isEmbedded()) { - return false; + leafTask = null; + break; } leafTask = task; } + mTempTransitionWindows.clear(); if (leafTask == null) { - return false; + return null; } // We don't support remote animation for Task with multiple TaskFragmentOrganizers. @@ -599,12 +623,28 @@ public class AppTransitionController { if (hasMultipleOrganizers) { ProtoLog.e(WM_DEBUG_APP_TRANSITIONS, "We don't support remote animation for" + " Task with multiple TaskFragmentOrganizers."); + return null; + } + return organizer[0]; + } + + /** + * Overrides the pending transition with the remote animation defined by the + * {@link ITaskFragmentOrganizer} if all windows in the transition are children of + * {@link TaskFragment} that are organized by the same organizer. + * + * @return {@code true} if the transition is overridden. + */ + private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit, + ArraySet<Integer> activityTypes) { + if (transitionMayContainNonAppWindows(transit)) { return false; } - final RemoteAnimationDefinition definition = organizer[0] != null + final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizerForAllWindows(); + final RemoteAnimationDefinition definition = organizer != null ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController - .getRemoteAnimationDefinition(organizer[0]) + .getRemoteAnimationDefinition(organizer) : null; final RemoteAnimationAdapter adapter = definition != null ? definition.getAdapter(transit, activityTypes) diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index eeb85c585876..5a2cf17ffd18 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -28,6 +28,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; +import static android.app.WindowConfigurationProto.WINDOWING_MODE; +import static android.content.ConfigurationProto.WINDOW_CONFIGURATION; import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION; import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION; @@ -695,22 +697,40 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { @CallSuper protected void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) { - // Critical log level logs only visible elements to mitigate performance overheard - if (logLevel != WindowTraceLogLevel.ALL && !mHasOverrideConfiguration) { - return; + final long token = proto.start(fieldId); + + if (logLevel == WindowTraceLogLevel.ALL || mHasOverrideConfiguration) { + mRequestedOverrideConfiguration.dumpDebug(proto, OVERRIDE_CONFIGURATION, + logLevel == WindowTraceLogLevel.CRITICAL); } - final long token = proto.start(fieldId); - mRequestedOverrideConfiguration.dumpDebug(proto, OVERRIDE_CONFIGURATION, - logLevel == WindowTraceLogLevel.CRITICAL); + // Unless trace level is set to `WindowTraceLogLevel.ALL` don't dump anything that isn't + // required to mitigate performance overhead if (logLevel == WindowTraceLogLevel.ALL) { mFullConfiguration.dumpDebug(proto, FULL_CONFIGURATION, false /* critical */); mMergedOverrideConfiguration.dumpDebug(proto, MERGED_OVERRIDE_CONFIGURATION, false /* critical */); } + + if (logLevel == WindowTraceLogLevel.TRIM) { + // Required for Fass to automatically detect pip transitions in Winscope traces + dumpDebugWindowingMode(proto); + } + proto.end(token); } + private void dumpDebugWindowingMode(ProtoOutputStream proto) { + final long fullConfigToken = proto.start(FULL_CONFIGURATION); + final long windowConfigToken = proto.start(WINDOW_CONFIGURATION); + + int windowingMode = mFullConfiguration.windowConfiguration.getWindowingMode(); + proto.write(WINDOWING_MODE, windowingMode); + + proto.end(windowConfigToken); + proto.end(fullConfigToken); + } + /** * Dumps the names of this container children in the input print writer indenting each * level with the input prefix. diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 46a4f8a26673..0ecee6641509 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -91,7 +91,9 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LAYER_MIRRORING; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; +import static com.android.internal.util.LatencyTracker.ACTION_ROTATE_SCREEN; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; @@ -127,7 +129,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -297,7 +298,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * The direct child layer of the display to put all non-overlay windows. This is also used for * screen rotation animation so that there is a parent layer to put the animation leash. */ - private final SurfaceControl mWindowingLayer; + private SurfaceControl mWindowingLayer; /** * The window token of the layer of the hierarchy to mirror, or null if this DisplayContent @@ -329,7 +330,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp private final ImeContainer mImeWindowsContainer = new ImeContainer(mWmService); @VisibleForTesting - final DisplayAreaPolicy mDisplayAreaPolicy; + DisplayAreaPolicy mDisplayAreaPolicy; private WindowState mTmpWindow; private boolean mUpdateImeTarget; @@ -588,7 +589,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Caches the value whether told display manager that we have content. */ private boolean mLastHasContent; - private DisplayRotationUtil mRotationUtil = new DisplayRotationUtil(); + private static DisplayRotationUtil sRotationUtil = new DisplayRotationUtil(); /** * The input method window for this display. @@ -1003,8 +1004,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final boolean committed = winAnimator.commitFinishDrawingLocked(); if (isDefaultDisplay && committed) { if (w.hasWallpaper()) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "First draw done in potential wallpaper target " + w); + ProtoLog.v(WM_DEBUG_WALLPAPER, + "First draw done in potential wallpaper target %s", w); mWallpaperMayChange = true; pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; if (DEBUG_LAYOUT_REPEATS) { @@ -1104,52 +1105,91 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mDividerControllerLocked = new DockedTaskDividerController(this); mPinnedTaskController = new PinnedTaskController(mWmService, this); + final Transaction pendingTransaction = getPendingTransaction(); + configureSurfaces(pendingTransaction); + pendingTransaction.apply(); + + // Sets the display content for the children. + onDisplayChanged(this); + updateDisplayAreaOrganizers(); + + mInputMonitor = new InputMonitor(mWmService, this); + mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this); + + if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display); + + mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this); + } + + @Override + void migrateToNewSurfaceControl(Transaction t) { + t.remove(mSurfaceControl); + + mLastSurfacePosition.set(0, 0); + + configureSurfaces(t); + + for (int i = 0; i < mChildren.size(); i++) { + SurfaceControl sc = mChildren.get(i).getSurfaceControl(); + if (sc != null) { + t.reparent(sc, mSurfaceControl); + } + } + + scheduleAnimation(); + } + + /** + * Configures the surfaces hierarchy for DisplayContent + * This method always recreates the main surface control but reparents the children + * if they are already created. + * @param transaction as part of which to perform the configuration + */ + private void configureSurfaces(Transaction transaction) { final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession) .setOpaque(true) .setContainerLayer() .setCallsite("DisplayContent"); - mSurfaceControl = b.setName("Root").setContainerLayer().build(); + mSurfaceControl = b.setName(getName()).setContainerLayer().build(); - // Setup the policy and build the display area hierarchy. - mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate( - mWmService, this /* content */, this /* root */, mImeWindowsContainer); + if (mDisplayAreaPolicy == null) { + // Setup the policy and build the display area hierarchy. + // Build the hierarchy only after creating the surface so it is reparented correctly + mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate( + mWmService, this /* content */, this /* root */, + mImeWindowsContainer); + } final List<DisplayArea<? extends WindowContainer>> areas = mDisplayAreaPolicy.getDisplayAreas(FEATURE_WINDOWED_MAGNIFICATION); final DisplayArea<?> area = areas.size() == 1 ? areas.get(0) : null; + if (area != null && area.getParent() == this) { // The windowed magnification area should contain all non-overlay windows, so just use // it as the windowing layer. mWindowingLayer = area.mSurfaceControl; + transaction.reparent(mWindowingLayer, mSurfaceControl); } else { // Need an additional layer for screen level animation, so move the layer containing // the windows to the new root. mWindowingLayer = mSurfaceControl; mSurfaceControl = b.setName("RootWrapper").build(); - getPendingTransaction().reparent(mWindowingLayer, mSurfaceControl) + transaction.reparent(mWindowingLayer, mSurfaceControl) .show(mWindowingLayer); } - mOverlayLayer = b.setName("Display Overlays").setParent(mSurfaceControl).build(); + if (mOverlayLayer == null) { + mOverlayLayer = b.setName("Display Overlays").setParent(mSurfaceControl).build(); + } else { + transaction.reparent(mOverlayLayer, mSurfaceControl); + } - getPendingTransaction() + transaction .setLayer(mSurfaceControl, 0) .setLayerStack(mSurfaceControl, mDisplayId) .show(mSurfaceControl) .setLayer(mOverlayLayer, Integer.MAX_VALUE) .show(mOverlayLayer); - getPendingTransaction().apply(); - - // Sets the display content for the children. - onDisplayChanged(this); - updateDisplayAreaOrganizers(); - - mInputMonitor = new InputMonitor(mWmService, this); - mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this); - - if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display); - - mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this); } boolean isReady() { @@ -2053,29 +2093,35 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mDisplayCutoutCache.getOrCompute(mInitialDisplayCutout, rotation); } - private WmDisplayCutout calculateDisplayCutoutForRotationUncached( - DisplayCutout cutout, int rotation) { + static WmDisplayCutout calculateDisplayCutoutForRotationAndDisplaySizeUncached( + DisplayCutout cutout, int rotation, int displayWidth, int displayHeight) { if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) { return WmDisplayCutout.NO_CUTOUT; } if (rotation == ROTATION_0) { return WmDisplayCutout.computeSafeInsets( - cutout, mInitialDisplayWidth, mInitialDisplayHeight); + cutout, displayWidth, displayHeight); } final Insets waterfallInsets = RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation); final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); - final Rect[] newBounds = mRotationUtil.getRotatedBounds( + final Rect[] newBounds = sRotationUtil.getRotatedBounds( cutout.getBoundingRectsAll(), - rotation, mInitialDisplayWidth, mInitialDisplayHeight); + rotation, displayWidth, displayHeight); final CutoutPathParserInfo info = cutout.getCutoutPathParserInfo(); final CutoutPathParserInfo newInfo = new CutoutPathParserInfo( info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(), info.getCutoutSpec(), rotation, info.getScale()); return WmDisplayCutout.computeSafeInsets( DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo), - rotated ? mInitialDisplayHeight : mInitialDisplayWidth, - rotated ? mInitialDisplayWidth : mInitialDisplayHeight); + rotated ? displayHeight : displayWidth, + rotated ? displayWidth : displayHeight); + } + + private WmDisplayCutout calculateDisplayCutoutForRotationUncached( + DisplayCutout cutout, int rotation) { + return calculateDisplayCutoutForRotationAndDisplaySizeUncached(cutout, rotation, + mInitialDisplayWidth, mInitialDisplayHeight); } RoundedCorners calculateRoundedCornersForRotation(int rotation) { @@ -3099,6 +3145,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mOverlayLayer.release(); mInputMonitor.onDisplayRemoved(); mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this); + mWmService.mAccessibilityController.onDisplayRemoved(mDisplayId); } finally { mDisplayReady = false; } @@ -3184,6 +3231,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } final Transition t = controller.requestTransitionIfNeeded(TRANSIT_CHANGE, this); if (t != null) { + if (getRotation() != getWindowConfiguration().getRotation()) { + mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN); + controller.mTransitionMetricsReporter.associate(t, + startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN)); + } t.setKnownConfigChanges(this, changes); } } @@ -5155,9 +5207,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp onAppTransitionDone(); changes |= FINISH_LAYOUT_REDO_LAYOUT; - if (DEBUG_WALLPAPER_LIGHT) { - Slog.v(TAG_WM, "Wallpaper layer changed: assigning layers + relayout"); - } + ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper layer changed: assigning layers + relayout"); computeImeTarget(true /* updateImeTarget */); mWallpaperMayChange = true; // Since the window list has been rebuilt, focus might have to be recomputed since the diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index ccfb174e3d16..d1357c0899b8 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -618,7 +618,8 @@ public class DisplayPolicy { } @Override - public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration, + public int onAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration, long statusBarAnimationStartTime, long statusBarAnimationDuration) { mHandler.post(() -> { StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); @@ -898,15 +899,6 @@ public class DisplayPolicy { // letterboxed. Hence always let them extend under the cutout. attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; break; - case TYPE_NOTIFICATION_SHADE: - // If the Keyguard is in a hidden state (occluded by another window), we force to - // remove the wallpaper and keyguard flag so that any change in-flight after setting - // the keyguard as occluded wouldn't set these flags again. - // See {@link #processKeyguardSetHiddenResultLw}. - if (mService.mPolicy.isKeyguardOccluded()) { - attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; - } - break; case TYPE_TOAST: // While apps should use the dedicated toast APIs to add such windows @@ -1102,9 +1094,6 @@ public class DisplayPolicy { switch (attrs.type) { case TYPE_NOTIFICATION_SHADE: mNotificationShade = win; - if (mDisplayContent.isDefaultDisplay) { - mService.mPolicy.setKeyguardCandidateLw(win); - } break; case TYPE_STATUS_BAR: mStatusBar = win; @@ -1300,9 +1289,6 @@ public class DisplayPolicy { mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, null, null); } else if (mNotificationShade == win) { mNotificationShade = null; - if (mDisplayContent.isDefaultDisplay) { - mService.mPolicy.setKeyguardCandidateLw(null); - } } else if (mClimateBarAlt == win) { mClimateBarAlt = null; mDisplayContent.setInsetProvider(ITYPE_CLIMATE_BAR, null, null); @@ -1530,30 +1516,50 @@ public class DisplayPolicy { * some temporal states, but doesn't change the window frames used to show on screen. */ void simulateLayoutDisplay(DisplayFrames displayFrames, SparseArray<Rect> barContentFrames) { - if (mNavigationBar != null) { - final WindowFrames simulatedWindowFrames = new WindowFrames(); - if (INSETS_LAYOUT_GENERALIZATION) { - simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames, + if (INSETS_LAYOUT_GENERALIZATION) { + final InsetsStateController insetsStateController = + mDisplayContent.getInsetsStateController(); + for (int type = 0; type < InsetsState.SIZE; type++) { + final InsetsSourceProvider provider = + insetsStateController.peekSourceProvider(type); + if (provider == null || !provider.hasWindow() + || provider.mWin.getControllableInsetProvider() != provider) { + continue; + } + final WindowFrames simulatedWindowFrames = new WindowFrames(); + simulateLayoutDecorWindow(provider.mWin, displayFrames, simulatedWindowFrames, barContentFrames, contentFrame -> simulateLayoutForContentFrame(displayFrames, - mNavigationBar, contentFrame)); - } else { + provider.mWin, contentFrame)); + } + } else { + if (mNavigationBar != null) { + final WindowFrames simulatedWindowFrames = new WindowFrames(); simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames, barContentFrames, contentFrame -> layoutNavigationBar(displayFrames, contentFrame)); } - } - if (mStatusBar != null) { - final WindowFrames simulatedWindowFrames = new WindowFrames(); - if (INSETS_LAYOUT_GENERALIZATION) { + if (mStatusBar != null) { + final WindowFrames simulatedWindowFrames = new WindowFrames(); simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames, barContentFrames, + contentFrame -> layoutStatusBar(displayFrames, contentFrame)); + } + if (mExtraNavBarAlt != null) { + // There's no pre-defined behavior for the extra navigation bar, we need to use the + // new flexible insets logic anyway. + final WindowFrames simulatedWindowFrames = new WindowFrames(); + simulateLayoutDecorWindow(mExtraNavBarAlt, displayFrames, simulatedWindowFrames, + barContentFrames, contentFrame -> simulateLayoutForContentFrame(displayFrames, - mStatusBar, contentFrame)); - } else { - simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames, + mExtraNavBarAlt, contentFrame)); + } + if (mClimateBarAlt != null) { + final WindowFrames simulatedWindowFrames = new WindowFrames(); + simulateLayoutDecorWindow(mClimateBarAlt, displayFrames, simulatedWindowFrames, barContentFrames, - contentFrame -> layoutStatusBar(displayFrames, contentFrame)); + contentFrame -> simulateLayoutForContentFrame(displayFrames, + mClimateBarAlt, contentFrame)); } } } diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index b08d6e1dff9e..fc317a1212d5 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -127,7 +127,7 @@ class EmbeddedWindowController { } } - static class EmbeddedWindow { + static class EmbeddedWindow implements InputTarget { final IWindow mClient; @Nullable final WindowState mHostWindowState; @Nullable final ActivityRecord mHostActivityRecord; @@ -166,7 +166,8 @@ class EmbeddedWindowController { mDisplayId = displayId; } - String getName() { + @Override + public String toString() { final String hostWindowName = (mHostWindowState != null) ? mHostWindowState.getWindowTag().toString() : "Internal"; return "EmbeddedWindow{ u" + UserHandle.getUserId(mOwnerUid) + " " + hostWindowName @@ -183,7 +184,7 @@ class EmbeddedWindowController { } InputChannel openInputChannel() { - final String name = getName(); + final String name = toString(); mInputChannel = mWmService.mInputManager.createInputChannel(name); return mInputChannel; } @@ -195,5 +196,25 @@ class EmbeddedWindowController { mInputChannel = null; } } + + @Override + public WindowState getWindowState() { + return mHostWindowState; + } + + @Override + public int getDisplayId() { + return mDisplayId; + } + + @Override + public IWindow getIWindow() { + return mClient; + } + + @Override + public int getPid() { + return mOwnerPid; + } } } diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java new file mode 100644 index 000000000000..c7d328a2b18a --- /dev/null +++ b/services/core/java/com/android/server/wm/InputTarget.java @@ -0,0 +1,40 @@ +/* + * 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.view.IWindow; + +/** + * Common interface between focusable objects. + * + * Both WindowState and EmbeddedWindows can receive input. This consolidates some common properties + * of both targets. + */ +interface InputTarget { + /* Get the WindowState associated with the target. */ + WindowState getWindowState(); + + /* Display id of the target. */ + int getDisplayId(); + + /* Client IWindow for the target. */ + IWindow getIWindow(); + + /* Owning pid of the target. */ + int getPid(); +} + diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 403df9185126..d202587bd306 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -535,7 +535,7 @@ class InsetsSourceProvider { @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, - @AnimationType int type, OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) { // TODO(b/166736352): Check if we still need to control the IME visibility here. if (mSource.getType() == ITYPE_IME) { // TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed. diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index c630e91ee306..bd41de3a9509 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -194,8 +194,7 @@ class KeyguardController { if (keyguardChanged) { // Irrelevant to AOD. - dismissMultiWindowModeForTaskIfNeeded(null /* currentTaskControllsingOcclusion */, - false /* turningScreenOn */); + dismissMultiWindowModeForTaskIfNeeded(null /* currentTaskControllsingOcclusion */); mKeyguardGoingAway = false; if (keyguardShowing) { mDismissalRequested = false; @@ -396,6 +395,8 @@ class KeyguardController { mService.continueWindowLayout(); } } + dismissMultiWindowModeForTaskIfNeeded(topActivity != null + ? topActivity.getRootTask() : null); } /** @@ -421,21 +422,6 @@ class KeyguardController { } } - /** - * Called when somebody wants to turn screen on. - */ - private void handleTurnScreenOn(int displayId) { - if (displayId != DEFAULT_DISPLAY) { - return; - } - - mTaskSupervisor.wakeUp("handleTurnScreenOn"); - if (mKeyguardShowing && canDismissKeyguard()) { - mWindowManager.dismissKeyguard(null /* callback */, null /* message */); - mDismissalRequested = true; - } - } - boolean isDisplayOccluded(int displayId) { return getDisplayState(displayId).mOccluded; } @@ -449,11 +435,9 @@ class KeyguardController { } private void dismissMultiWindowModeForTaskIfNeeded( - @Nullable Task currentTaskControllingOcclusion, boolean turningScreenOn) { - // If turningScreenOn is true, it means that the visibility state has changed from - // currentTaskControllingOcclusion and we should update windowing mode. + @Nullable Task currentTaskControllingOcclusion) { // TODO(b/113840485): Handle docked stack for individual display. - if (!turningScreenOn && (!mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY))) { + if (!mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY)) { return; } @@ -592,26 +576,17 @@ class KeyguardController { && controller.mWindowManager.isKeyguardSecure( controller.mService.getCurrentUserId()); - boolean occludingChange = false; - boolean turningScreenOn = false; if (mTopTurnScreenOnActivity != lastTurnScreenOnActivity && mTopTurnScreenOnActivity != null && !mService.mWindowManager.mPowerManager.isInteractive() - && (mRequestDismissKeyguard || occludedByActivity - || controller.canDismissKeyguard())) { - turningScreenOn = true; - controller.handleTurnScreenOn(mDisplayId); + && (mRequestDismissKeyguard || occludedByActivity)) { + controller.mTaskSupervisor.wakeUp("handleTurnScreenOn"); mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false); } if (lastOccluded != mOccluded) { - occludingChange = true; controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity); } - - if (occludingChange || turningScreenOn) { - controller.dismissMultiWindowModeForTaskIfNeeded(task, turningScreenOn); - } } /** diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java index 520bd8b2108e..a3eb980992c7 100644 --- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static com.android.server.wm.AnimationAdapterProto.LOCAL; import static com.android.server.wm.LocalAnimationAdapterProto.ANIMATION_SPEC; +import android.annotation.NonNull; import android.os.SystemClock; import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl; @@ -51,7 +52,7 @@ class LocalAnimationAdapter implements AnimationAdapter { @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, - @AnimationType int type, OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) { mAnimator.startAnimation(mSpec, animationLeash, t, () -> finishCallback.onAnimationFinished(type, this)); } diff --git a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java index e50dc51c402d..7abf3b820c18 100644 --- a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java +++ b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; +import android.annotation.NonNull; import android.view.SurfaceControl; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; @@ -144,7 +145,7 @@ public class NavBarFadeAnimationController extends FadeAnimationController{ @Override public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, - int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { super.startAnimation(animationLeash, t, type, finishCallback); if (mParent != null && mParent.isValid()) { t.reparent(animationLeash, mParent); diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java index 88941eb4d369..9f28509c56bd 100644 --- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java @@ -27,6 +27,7 @@ import static com.android.server.wm.AnimationAdapterProto.REMOTE; import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; +import android.annotation.NonNull; import android.graphics.Rect; import android.os.SystemClock; import android.util.proto.ProtoOutputStream; @@ -145,7 +146,7 @@ class NonAppWindowAnimationAdapter implements AnimationAdapter { @Override public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, - int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation"); mCapturedLeash = animationLeash; mCapturedLeashFinishCallback = finishCallback; diff --git a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java index ef8dee401b05..11a27c593d9e 100644 --- a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java +++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java @@ -120,8 +120,15 @@ public class PossibleDisplayInfoMapper { @Surface.Rotation int rotation) { DisplayInfo updatedDisplayInfo = new DisplayInfo(); updatedDisplayInfo.copyFrom(displayInfo); - updatedDisplayInfo.rotation = rotation; + // Apply rotations before updating width and height + updatedDisplayInfo.roundedCorners = updatedDisplayInfo.roundedCorners.rotate(rotation, + updatedDisplayInfo.logicalWidth, updatedDisplayInfo.logicalHeight); + updatedDisplayInfo.displayCutout = + DisplayContent.calculateDisplayCutoutForRotationAndDisplaySizeUncached( + updatedDisplayInfo.displayCutout, rotation, updatedDisplayInfo.logicalWidth, + updatedDisplayInfo.logicalHeight).getDisplayCutout(); + updatedDisplayInfo.rotation = rotation; final int naturalWidth = updatedDisplayInfo.getNaturalWidth(); final int naturalHeight = updatedDisplayInfo.getNaturalHeight(); updatedDisplayInfo.logicalWidth = naturalWidth; diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index ba1cf8ae06df..ee05523b3f2a 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -170,6 +170,13 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "startRecentsActivity(): intent=%s", mTargetIntent); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RecentsAnimation#startRecentsActivity"); + // Cancel any existing recents animation running synchronously (do not hold the + // WM lock) before starting the newly requested recents animation as they can not coexist + if (mWindowManager.getRecentsAnimationController() != null) { + mWindowManager.getRecentsAnimationController().forceCancelAnimation( + REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity"); + } + // If the activity is associated with the root recents task, then try and get that first Task targetRootTask = mDefaultTaskDisplayArea.getRootTask(WINDOWING_MODE_UNDEFINED, mTargetActivityType); @@ -243,12 +250,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan targetActivity.intent.replaceExtras(mTargetIntent); // Fetch all the surface controls and pass them to the client to get the animation - // started. Cancel any existing recents animation running synchronously (do not hold the - // WM lock) - if (mWindowManager.getRecentsAnimationController() != null) { - mWindowManager.getRecentsAnimationController().forceCancelAnimation( - REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity"); - } + // started mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner, this, mDefaultTaskDisplayArea.getDisplayId(), mTaskSupervisor.mRecentTasks.getRecentTaskIds(), targetActivity); diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index a663c62b40e5..67b3ec8c2b90 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -169,8 +169,9 @@ public class RecentsAnimationController implements DeathRecipient { */ final AppTransitionListener mAppTransitionListener = new AppTransitionListener() { @Override - public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration, - long statusBarAnimationStartTime, long statusBarAnimationDuration) { + public int onAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration, long statusBarAnimationStartTime, + long statusBarAnimationDuration) { continueDeferredCancel(); return 0; } @@ -793,7 +794,7 @@ public class RecentsAnimationController implements DeathRecipient { private RemoteAnimationTarget[] createWallpaperAnimations() { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "createWallpaperAnimations()"); - return WallpaperAnimationAdapter.startWallpaperAnimations(mService, 0L, 0L, + return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent, 0L, 0L, adapter -> { synchronized (mService.mGlobalLock) { // If the wallpaper animation is canceled, continue with the recents @@ -1320,7 +1321,7 @@ public class RecentsAnimationController implements DeathRecipient { @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, - @AnimationType int type, OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) { // Restore position and root task crop until client has a chance to modify it. t.setPosition(animationLeash, mLocalBounds.left, mLocalBounds.top); mTmpRect.set(mLocalBounds); diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 16a45fe7cec7..eeac230489f9 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -22,6 +22,7 @@ import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import android.annotation.NonNull; import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; @@ -207,7 +208,7 @@ class RemoteAnimationController implements DeathRecipient { if (wrappers.mThumbnailAdapter != null && wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) { wrappers.mThumbnailAdapter.mCapturedFinishCallback - .onAnimationFinished(wrappers.mAdapter.mAnimationType, + .onAnimationFinished(wrappers.mThumbnailAdapter.mAnimationType, wrappers.mThumbnailAdapter); } mPendingAnimations.remove(i); @@ -218,7 +219,7 @@ class RemoteAnimationController implements DeathRecipient { private RemoteAnimationTarget[] createWallpaperAnimations() { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createWallpaperAnimations()"); - return WallpaperAnimationAdapter.startWallpaperAnimations(mService, + return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent, mRemoteAnimationAdapter.getDuration(), mRemoteAnimationAdapter.getStatusBarTransitionDelay(), adapter -> { @@ -260,7 +261,7 @@ class RemoteAnimationController implements DeathRecipient { } if (adapters.mThumbnailAdapter != null) { adapters.mThumbnailAdapter.mCapturedFinishCallback - .onAnimationFinished(adapters.mAdapter.mAnimationType, + .onAnimationFinished(adapters.mThumbnailAdapter.mAnimationType, adapters.mThumbnailAdapter); } mPendingAnimations.remove(i); @@ -477,7 +478,7 @@ class RemoteAnimationController implements DeathRecipient { @Override public void startAnimation(SurfaceControl animationLeash, Transaction t, - @AnimationType int type, OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation"); if (mStartBounds.isEmpty()) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 97ea41c2f228..e3600e656307 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -48,6 +48,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_O import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST; @@ -72,6 +73,7 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList; import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity; import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG; +import static com.android.server.wm.RootWindowContainerProto.DEFAULT_MIN_SIZE_RESIZABLE_TASK; import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT; import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER; import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER; @@ -79,7 +81,6 @@ import static com.android.server.wm.Task.REPARENT_LEAVE_ROOT_TASK_IN_PLACE; import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT; import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -504,6 +505,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mTopFocusedDisplayId = topFocusedDisplayId; mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId); mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId); + mWmService.mAccessibilityController.setFocusedDisplay(topFocusedDisplayId); ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "New topFocusedDisplayId=%d", topFocusedDisplayId); } return changed; @@ -893,7 +895,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) { final DisplayContent displayContent = mChildren.get(displayNdx); if (displayContent.mWallpaperMayChange) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Wallpaper may change! Adjusting"); + ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper may change! Adjusting"); displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; if (DEBUG_LAYOUT_REPEATS) { surfacePlacer.debugLayoutRepeats("WallpaperMayChange", @@ -1253,6 +1255,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent> pw.println(mTopFocusedDisplayId); } + void dumpDefaultMinSizeOfResizableTask(PrintWriter pw) { + pw.print(" mDefaultMinSizeOfResizeableTaskDp="); + pw.println(mDefaultMinSizeOfResizeableTaskDp); + } + void dumpLayoutNeededDisplayIds(PrintWriter pw) { if (!isLayoutNeeded()) { return; @@ -1299,7 +1306,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mTaskSupervisor.getKeyguardController().dumpDebug(proto, KEYGUARD_CONTROLLER); proto.write(IS_HOME_RECENTS_COMPONENT, mTaskSupervisor.mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser)); - + proto.write(DEFAULT_MIN_SIZE_RESIZABLE_TASK, mDefaultMinSizeOfResizeableTaskDp); proto.end(token); } @@ -2150,6 +2157,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> final Task rootTask; if (singleActivity) { rootTask = task; + + // Apply the last recents animation leash transform to the task entering PIP + rootTask.maybeApplyLastRecentsAnimationTransaction(); } else { // In the case of multiple activities, we will create a new task for it and then // move the PIP activity into the task. Note that we explicitly defer the task diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index c7bf8ecfe949..d712bbf0fdef 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -57,8 +57,11 @@ class SurfaceAnimator { @VisibleForTesting SurfaceControl mLeash; @VisibleForTesting + SurfaceFreezer.Snapshot mSnapshot; + @VisibleForTesting final Animatable mAnimatable; - private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback; + @VisibleForTesting + final OnAnimationFinishedCallback mInnerAnimationFinishedCallback; /** * Static callback to run on all animations started through this SurfaceAnimator @@ -151,12 +154,14 @@ class SurfaceAnimator { * @param animationFinishedCallback The callback being triggered when the animation finishes. * @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a * cancel call to the underlying AnimationAdapter. + * @param snapshotAnim The animation to run for the snapshot. {@code null} if there is no + * snapshot. */ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, @AnimationType int type, @Nullable OnAnimationFinishedCallback animationFinishedCallback, @Nullable Runnable animationCancelledCallback, - @Nullable SurfaceFreezer freezer) { + @Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) { cancelAnimation(t, true /* restarting */, true /* forwardCancel */); mAnimation = anim; mAnimationType = type; @@ -181,12 +186,16 @@ class SurfaceAnimator { return; } mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback); + if (snapshotAnim != null) { + mSnapshot = freezer.takeSnapshotForAnimation(); + mSnapshot.startAnimation(t, snapshotAnim, type); + } } void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, @AnimationType int type) { startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */, - null /* animationCancelledCallback */, null /* freezer */); + null /* animationCancelledCallback */, null /* snapshotAnim */, null /* freezer */); } /** @@ -328,6 +337,7 @@ class SurfaceAnimator { final OnAnimationFinishedCallback animationFinishedCallback = mSurfaceAnimationFinishedCallback; final Runnable animationCancelledCallback = mAnimationCancelledCallback; + final SurfaceFreezer.Snapshot snapshot = mSnapshot; reset(t, false); if (animation != null) { if (!mAnimationStartDelayed && forwardCancel) { @@ -346,9 +356,14 @@ class SurfaceAnimator { } } - if (forwardCancel && leash != null) { - t.remove(leash); - mService.scheduleAnimationLocked(); + if (forwardCancel) { + if (snapshot != null) { + snapshot.cancelAnimation(t, false /* restarting */); + } + if (leash != null) { + t.remove(leash); + mService.scheduleAnimationLocked(); + } } if (!restarting) { @@ -361,6 +376,12 @@ class SurfaceAnimator { mAnimation = null; mSurfaceAnimationFinishedCallback = null; mAnimationType = ANIMATION_TYPE_NONE; + final SurfaceFreezer.Snapshot snapshot = mSnapshot; + mSnapshot = null; + if (snapshot != null) { + // Reset the mSnapshot reference before calling the callback to prevent circular reset. + snapshot.cancelAnimation(t, !destroyLeash); + } if (mLeash == null) { return; } @@ -377,11 +398,15 @@ class SurfaceAnimator { boolean scheduleAnim = false; final SurfaceControl surface = animatable.getSurfaceControl(); final SurfaceControl parent = animatable.getParentSurfaceControl(); + final SurfaceControl curAnimationLeash = animatable.getAnimationLeash(); // If the surface was destroyed or the leash is invalid, we don't care to reparent it back. // Note that we also set this variable to true even if the parent isn't valid anymore, in // order to ensure onAnimationLeashLost still gets called in this case. - final boolean reparent = surface != null; + // If the animation leash is set, and it is different from the removing leash, it means the + // surface now has a new animation surface. We don't want to reparent for that. + final boolean reparent = surface != null && (curAnimationLeash == null + || curAnimationLeash.equals(leash)); if (reparent) { if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent: " + parent); // We shouldn't really need these isValid checks but we do @@ -608,6 +633,14 @@ class SurfaceAnimator { void onAnimationLeashLost(Transaction t); /** + * Gets the last created animation leash that has not lost yet. + */ + @Nullable + default SurfaceControl getAnimationLeash() { + return null; + } + + /** * @return A new surface to be used for the animation leash, inserted at the correct * position in the hierarchy. */ diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java index 89986cefb207..c667db86e410 100644 --- a/services/core/java/com/android/server/wm/SurfaceFreezer.java +++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; -import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION; import android.annotation.NonNull; @@ -27,13 +26,11 @@ import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; -import android.view.Surface; import android.view.SurfaceControl; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; -import java.util.function.Supplier; - /** * This class handles "freezing" of an Animatable. The Animatable in question should implement * Freezable. @@ -54,7 +51,8 @@ class SurfaceFreezer { private final Freezable mAnimatable; private final WindowManagerService mWmService; - private SurfaceControl mLeash; + @VisibleForTesting + SurfaceControl mLeash; Snapshot mSnapshot = null; final Rect mFreezeBounds = new Rect(); @@ -94,7 +92,7 @@ class SurfaceFreezer { if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { return; } - mSnapshot = new Snapshot(mWmService.mSurfaceFactory, t, screenshotBuffer, mLeash); + mSnapshot = new Snapshot(t, screenshotBuffer, mLeash); } } @@ -109,12 +107,25 @@ class SurfaceFreezer { } /** + * Used by {@link SurfaceAnimator}. This "transfers" the snapshot leash to be used for + * animation. By transferring the leash, this will no longer try to clean-up the leash when + * finished. + */ + @Nullable + Snapshot takeSnapshotForAnimation() { + final Snapshot out = mSnapshot; + mSnapshot = null; + return out; + } + + /** * Clean-up the snapshot and remove leash. If the leash was taken, this just cleans-up the * snapshot. */ void unfreeze(SurfaceControl.Transaction t) { if (mSnapshot != null) { mSnapshot.cancelAnimation(t, false /* restarting */); + mSnapshot = null; } if (mLeash == null) { return; @@ -163,13 +174,12 @@ class SurfaceFreezer { class Snapshot { private SurfaceControl mSurfaceControl; private AnimationAdapter mAnimation; - private SurfaceAnimator.OnAnimationFinishedCallback mFinishedCallback; /** * @param t Transaction to create the thumbnail in. * @param screenshotBuffer A thumbnail or placeholder for thumbnail to initialize with. */ - Snapshot(Supplier<Surface> surfaceFactory, SurfaceControl.Transaction t, + Snapshot(SurfaceControl.Transaction t, SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer, SurfaceControl parent) { // We can't use a delegating constructor since we need to // reference this::onAnimationFinished @@ -211,19 +221,15 @@ class SurfaceFreezer { * component responsible for running the animation. It runs the animation with * {@link AnimationAdapter#startAnimation} once the hierarchy with * the Leash has been set up. - * @param animationFinishedCallback The callback being triggered when the animation - * finishes. */ - void startAnimation(SurfaceControl.Transaction t, AnimationAdapter anim, int type, - @Nullable SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback) { + void startAnimation(SurfaceControl.Transaction t, AnimationAdapter anim, int type) { cancelAnimation(t, true /* restarting */); mAnimation = anim; - mFinishedCallback = animationFinishedCallback; if (mSurfaceControl == null) { cancelAnimation(t, false /* restarting */); return; } - mAnimation.startAnimation(mSurfaceControl, t, type, animationFinishedCallback); + mAnimation.startAnimation(mSurfaceControl, t, type, (typ, ani) -> { }); } /** @@ -235,18 +241,9 @@ class SurfaceFreezer { void cancelAnimation(SurfaceControl.Transaction t, boolean restarting) { final SurfaceControl leash = mSurfaceControl; final AnimationAdapter animation = mAnimation; - final SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback = - mFinishedCallback; mAnimation = null; - mFinishedCallback = null; if (animation != null) { animation.onAnimationCancelled(leash); - if (!restarting) { - if (animationFinishedCallback != null) { - animationFinishedCallback.onAnimationFinished( - ANIMATION_TYPE_APP_TRANSITION, animation); - } - } } if (!restarting) { destroy(t); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 594b6be489d6..ba2da06efab4 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1001,7 +1001,6 @@ class Task extends TaskFragment { mCallingPackage = r.launchedFromPackage; mCallingFeatureId = r.launchedFromFeatureId; setIntent(intent != null ? intent : r.intent, info != null ? info : r.info); - return; } setLockTaskAuth(r); } @@ -1926,6 +1925,7 @@ class Task extends TaskFragment { final ActivityRecord r = topRunningActivity(); if (r != null && mDisplayContent.isFixedRotationLaunchingApp(r)) { getSyncTransaction().setWindowCrop(mSurfaceControl, null) + .setCornerRadius(mSurfaceControl, 0f) .setMatrix(mSurfaceControl, Matrix.IDENTITY_MATRIX, new float[9]); } } @@ -2972,11 +2972,6 @@ class Task extends TaskFragment { return super.makeAnimationLeash().setMetadata(METADATA_TASK_ID, mTaskId); } - @Override - void resetSurfacePositionForAnimationLeash(SurfaceControl.Transaction t) { - super.resetSurfacePositionForAnimationLeash(t); - } - boolean shouldAnimate() { /** * Animations are handled by the TaskOrganizer implementation. diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 66f2dbc8ec09..275ed0ee28a9 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -99,13 +99,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { private int mColorLayerCounter = 0; /** - * A control placed at the appropriate level for transitions to occur. - */ - private SurfaceControl mAppAnimationLayer; - private SurfaceControl mBoostedAppAnimationLayer; - private SurfaceControl mHomeAppAnimationLayer; - - /** * Given that the split-screen divider does not have an AppWindowToken, it * will have to live inside of a "NonAppWindowContainer". However, in visual Z order * it will need to be interleaved with some of our children, appearing on top of @@ -132,7 +125,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { private final ArrayList<WindowContainer> mTmpNormalChildren = new ArrayList<>(); private final ArrayList<WindowContainer> mTmpHomeChildren = new ArrayList<>(); private final IntArray mTmpNeedsZBoostIndexes = new IntArray(); - private int mTmpLayerForAnimationLayer; private ArrayList<Task> mTmpTasks = new ArrayList<>(); @@ -871,33 +863,13 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { int layer = 0; // Place root home tasks to the bottom. - layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer, false /* normalRootTasks */); - // The home animation layer is between the root home tasks and the normal root tasks. - final int layerForHomeAnimationLayer = layer++; - mTmpLayerForAnimationLayer = layer++; - layer = adjustRootTaskLayer(t, mTmpNormalChildren, layer, true /* normalRootTasks */); - - // The boosted animation layer is between the normal root tasks and the always on top - // root tasks. - final int layerForBoostedAnimationLayer = layer++; - // Always on top tasks layer should higher than split divider layer so set it as start. - layer = SPLIT_DIVIDER_LAYER + 1; - adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer, false /* normalRootTasks */); + layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer); + adjustRootTaskLayer(t, mTmpNormalChildren, layer); - t.setLayer(mHomeAppAnimationLayer, layerForHomeAnimationLayer); - t.setLayer(mAppAnimationLayer, mTmpLayerForAnimationLayer); + // Always on top tasks layer should higher than split divider layer so set it as start. t.setLayer(mSplitScreenDividerAnchor, SPLIT_DIVIDER_LAYER); - t.setLayer(mBoostedAppAnimationLayer, layerForBoostedAnimationLayer); - } - - private int adjustNormalRootTaskLayer(WindowContainer child, int layer) { - if ((child.asTask() != null && child.asTask().isAnimatingByRecents()) - || child.isAppTransitioning()) { - // The animation layer is located above the highest animating root task and no - // higher. - mTmpLayerForAnimationLayer = layer++; - } - return layer; + layer = SPLIT_DIVIDER_LAYER + 1; + adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer); } /** @@ -906,11 +878,10 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { * normal rootTasks. * * @param startLayer The beginning layer of this group of rootTasks. - * @param normalRootTasks Set {@code true} if this group is neither home nor always on top. * @return The adjusted layer value. */ private int adjustRootTaskLayer(SurfaceControl.Transaction t, - ArrayList<WindowContainer> children, int startLayer, boolean normalRootTasks) { + ArrayList<WindowContainer> children, int startLayer) { mTmpNeedsZBoostIndexes.clear(); final int childCount = children.size(); for (int i = 0; i < childCount; i++) { @@ -923,9 +894,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { if (!childNeedsZBoost) { child.assignLayer(t, startLayer++); - if (normalRootTasks) { - startLayer = adjustNormalRootTaskLayer(child, startLayer); - } } else { mTmpNeedsZBoostIndexes.add(i); } @@ -935,9 +903,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { for (int i = 0; i < zBoostSize; i++) { final WindowContainer child = children.get(mTmpNeedsZBoostIndexes.get(i)); child.assignLayer(t, startLayer++); - if (normalRootTasks) { - startLayer = adjustNormalRootTaskLayer(child, startLayer); - } } return startLayer; } @@ -951,19 +916,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } @Override - SurfaceControl getAppAnimationLayer(@AnimationLayer int animationLayer) { - switch (animationLayer) { - case ANIMATION_LAYER_BOOSTED: - return mBoostedAppAnimationLayer; - case ANIMATION_LAYER_HOME: - return mHomeAppAnimationLayer; - case ANIMATION_LAYER_STANDARD: - default: - return mAppAnimationLayer; - } - } - - @Override RemoteAnimationTarget createRemoteAnimationTarget( RemoteAnimationController.RemoteAnimationRecord record) { final ActivityRecord activity = getTopMostActivity(); @@ -983,42 +935,21 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { .setName("colorBackgroundLayer") .setCallsite("TaskDisplayArea.onParentChanged") .build(); - mAppAnimationLayer = makeChildSurface(null) - .setName("animationLayer") - .setCallsite("TaskDisplayArea.onParentChanged") - .build(); - mBoostedAppAnimationLayer = makeChildSurface(null) - .setName("boostedAnimationLayer") - .setCallsite("TaskDisplayArea.onParentChanged") - .build(); - mHomeAppAnimationLayer = makeChildSurface(null) - .setName("homeAnimationLayer") - .setCallsite("TaskDisplayArea.onParentChanged") - .build(); mSplitScreenDividerAnchor = makeChildSurface(null) .setName("splitScreenDividerAnchor") .setCallsite("TaskDisplayArea.onParentChanged") .build(); getSyncTransaction() - .show(mAppAnimationLayer) - .show(mBoostedAppAnimationLayer) - .show(mHomeAppAnimationLayer) .show(mSplitScreenDividerAnchor); }); } else { super.onParentChanged(newParent, oldParent); mWmService.mTransactionFactory.get() .remove(mColorBackgroundLayer) - .remove(mAppAnimationLayer) - .remove(mBoostedAppAnimationLayer) - .remove(mHomeAppAnimationLayer) .remove(mSplitScreenDividerAnchor) .apply(); mColorBackgroundLayer = null; - mAppAnimationLayer = null; - mBoostedAppAnimationLayer = null; - mHomeAppAnimationLayer = null; mSplitScreenDividerAnchor = null; } } @@ -1059,15 +990,12 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { @Override void migrateToNewSurfaceControl(SurfaceControl.Transaction t) { super.migrateToNewSurfaceControl(t); - if (mAppAnimationLayer == null) { + if (mColorBackgroundLayer == null) { return; } // As TaskDisplayArea is getting a new surface, reparent and reorder the child surfaces. t.reparent(mColorBackgroundLayer, mSurfaceControl); - t.reparent(mAppAnimationLayer, mSurfaceControl); - t.reparent(mBoostedAppAnimationLayer, mSurfaceControl); - t.reparent(mHomeAppAnimationLayer, mSurfaceControl); t.reparent(mSplitScreenDividerAnchor, mSurfaceControl); reassignLayer(t); scheduleAnimation(); @@ -1347,6 +1275,11 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } } } + // For better split UX, If task launch by the source task which root task is created by + // organizer, it should also launch in that root too. + if (sourceTask != null && sourceTask.getRootTask().mCreatedByOrganizer) { + return sourceTask.getRootTask(); + } return null; } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 44b22c643347..99f6f341e977 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -1428,23 +1428,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { + "directly: %s", prev); didAutoPip = mAtmService.enterPictureInPictureMode(prev, prev.pictureInPictureArgs); - mPausingActivity = null; } else { - ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev); - try { - EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev), - prev.shortComponentName, "userLeaving=" + userLeaving, reason); - - mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(), - prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving, - prev.configChangeFlags, pauseImmediately)); - } catch (Exception e) { - // Ignore exception, if process died other code will cleanup. - Slog.w(TAG, "Exception thrown during pause", e); - mPausingActivity = null; - mLastPausedActivity = null; - mTaskSupervisor.mNoHistoryActivities.remove(prev); - } + schedulePauseActivity(prev, userLeaving, pauseImmediately, reason); } } else { mPausingActivity = null; @@ -1459,7 +1444,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { } // If already entered PIP mode, no need to keep pausing. - if (mPausingActivity != null && !didAutoPip) { + if (mPausingActivity != null) { // Have the window manager pause its key dispatching until the new // activity has started. If we're pausing the activity just because // the screen is being turned off and the UI is sleeping, don't interrupt @@ -1494,6 +1479,25 @@ class TaskFragment extends WindowContainer<WindowContainer> { } } + void schedulePauseActivity(ActivityRecord prev, boolean userLeaving, + boolean pauseImmediately, String reason) { + ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev); + try { + EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev), + prev.shortComponentName, "userLeaving=" + userLeaving, reason); + + mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(), + prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving, + prev.configChangeFlags, pauseImmediately)); + } catch (Exception e) { + // Ignore exception, if process died other code will cleanup. + Slog.w(TAG, "Exception thrown during pause", e); + mPausingActivity = null; + mLastPausedActivity = null; + mTaskSupervisor.mNoHistoryActivities.remove(prev); + } + } + @VisibleForTesting void completePause(boolean resumeNext, ActivityRecord resuming) { // Complete the pausing process of a pausing activity, so it doesn't make sense to diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index e31a6620187a..d543c1f756c9 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -717,7 +717,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } // First we get the default size we want. - getDefaultFreeformSize(displayArea, layout, orientation, mTmpBounds); + getDefaultFreeformSize(root.info, displayArea, layout, orientation, mTmpBounds); if (hasInitialBounds || sizeMatches(inOutBounds, mTmpBounds)) { // We're here because either input parameters specified initial bounds, or the suggested // bounds have the same size of the default freeform size. We should use the suggested @@ -785,7 +785,8 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { return orientation; } - private void getDefaultFreeformSize(@NonNull TaskDisplayArea displayArea, + private void getDefaultFreeformSize(@NonNull ActivityInfo info, + @NonNull TaskDisplayArea displayArea, @NonNull ActivityInfo.WindowLayout layout, int orientation, @NonNull Rect bounds) { // Default size, which is letterboxing/pillarboxing in displayArea. That's to say the large // dimension of default size is the small dimension of displayArea size, and the small @@ -816,11 +817,38 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { final int layoutMinWidth = (layout == null) ? -1 : layout.minWidth; final int layoutMinHeight = (layout == null) ? -1 : layout.minHeight; - // Final result. + // Aspect ratio requirements. + final float minAspectRatio = info.getMinAspectRatio(); + final float maxAspectRatio = info.getMaxAspectRatio(); + final int width = Math.min(defaultWidth, Math.max(phoneWidth, layoutMinWidth)); final int height = Math.min(defaultHeight, Math.max(phoneHeight, layoutMinHeight)); + final float aspectRatio = (float) Math.max(width, height) / (float) Math.min(width, height); + + // Adjust the width and height to the aspect ratio requirements. + int adjWidth = width; + int adjHeight = height; + if (minAspectRatio >= 1 && aspectRatio < minAspectRatio) { + // The aspect ratio is below the minimum, adjust it to the minimum. + if (orientation == SCREEN_ORIENTATION_LANDSCAPE) { + // Fix the width, scale the height. + adjHeight = (int) (adjWidth / minAspectRatio + 0.5f); + } else { + // Fix the height, scale the width. + adjWidth = (int) (adjHeight / minAspectRatio + 0.5f); + } + } else if (maxAspectRatio >= 1 && aspectRatio > maxAspectRatio) { + // The aspect ratio exceeds the maximum, adjust it to the maximum. + if (orientation == SCREEN_ORIENTATION_LANDSCAPE) { + // Fix the width, scale the height. + adjHeight = (int) (adjWidth / maxAspectRatio + 0.5f); + } else { + // Fix the height, scale the width. + adjWidth = (int) (adjHeight / maxAspectRatio + 0.5f); + } + } - bounds.set(0, 0, width, height); + bounds.set(0, 0, adjWidth, adjHeight); bounds.offset(stableBounds.left, stableBounds.top); } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index f1345e8ee61e..3d5f9881e044 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -22,6 +22,7 @@ import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL; import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; @@ -422,8 +423,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } // Capture the animation surface control for activity's main window - private static class StartingWindowAnimationAdaptor implements AnimationAdapter { - private SurfaceControl mAnimationLeash; + static class StartingWindowAnimationAdaptor implements AnimationAdapter { + SurfaceControl mAnimationLeash; @Override public boolean getShowWallpaper() { return false; @@ -431,7 +432,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { @Override public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, - int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { mAnimationLeash = animationLeash; } @@ -464,6 +465,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } } + static SurfaceControl applyStartingWindowAnimation(WindowContainer window) { + final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor(); + window.startAnimation(window.getPendingTransaction(), adaptor, false, + ANIMATION_TYPE_STARTING_REVEAL); + return adaptor.mAnimationLeash; + } + boolean addStartingWindow(Task task, ActivityRecord activity, int launchTheme, TaskSnapshot taskSnapshot) { final Task rootTask = task.getRootTask(); @@ -510,12 +518,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { final WindowState mainWindow = topActivity.findMainWindow(false/* includeStartingApp */); if (mainWindow != null) { - final StartingWindowAnimationAdaptor adaptor = - new StartingWindowAnimationAdaptor(); final SurfaceControl.Transaction t = mainWindow.getPendingTransaction(); - mainWindow.startAnimation(t, adaptor, false, - ANIMATION_TYPE_STARTING_REVEAL); - removalInfo.windowAnimationLeash = adaptor.mAnimationLeash; + removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow); removalInfo.mainFrame = mainWindow.getRelativeFrame(); t.setPosition(removalInfo.windowAnimationLeash, removalInfo.mainFrame.left, removalInfo.mainFrame.top); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 5ce9938ff71d..ce93f2495c22 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -178,46 +178,49 @@ class TaskSnapshotController { snapshotTasks(tasks, false /* allowSnapshotHome */); } - private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) { - for (int i = tasks.size() - 1; i >= 0; i--) { - final Task task = tasks.valueAt(i); - final TaskSnapshot snapshot; - final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome(); - if (snapshotHome) { - snapshot = snapshotTask(task); - } else { - switch (getSnapshotMode(task)) { - case SNAPSHOT_MODE_NONE: - continue; - case SNAPSHOT_MODE_APP_THEME: - snapshot = drawAppThemeSnapshot(task); - break; - case SNAPSHOT_MODE_REAL: - snapshot = snapshotTask(task); - break; - default: - snapshot = null; - break; - } + void recordTaskSnapshot(Task task, boolean allowSnapshotHome) { + final TaskSnapshot snapshot; + final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome(); + if (snapshotHome) { + snapshot = snapshotTask(task); + } else { + switch (getSnapshotMode(task)) { + case SNAPSHOT_MODE_NONE: + return; + case SNAPSHOT_MODE_APP_THEME: + snapshot = drawAppThemeSnapshot(task); + break; + case SNAPSHOT_MODE_REAL: + snapshot = snapshotTask(task); + break; + default: + snapshot = null; + break; } - if (snapshot != null) { - final HardwareBuffer buffer = snapshot.getHardwareBuffer(); - if (buffer.getWidth() == 0 || buffer.getHeight() == 0) { - buffer.close(); - Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x" - + buffer.getHeight()); - } else { - mCache.putSnapshot(task, snapshot); - // Don't persist or notify the change for the temporal snapshot. - if (!snapshotHome) { - mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); - task.onSnapshotChanged(snapshot); - } + } + if (snapshot != null) { + final HardwareBuffer buffer = snapshot.getHardwareBuffer(); + if (buffer.getWidth() == 0 || buffer.getHeight() == 0) { + buffer.close(); + Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x" + + buffer.getHeight()); + } else { + mCache.putSnapshot(task, snapshot); + // Don't persist or notify the change for the temporal snapshot. + if (!snapshotHome) { + mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot); + task.onSnapshotChanged(snapshot); } } } } + private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) { + for (int i = tasks.size() - 1; i >= 0; i--) { + recordTaskSnapshot(tasks.valueAt(i), allowSnapshotHome); + } + } + /** * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW * MANAGER LOCK WHEN CALLING THIS METHOD! diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index e50e8ef56778..9fc45b972e91 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -415,7 +415,16 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (commitVisibility) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Commit activity becoming invisible: %s", ar); - ar.commitVisibility(false /* visible */, false /* performLayout */); + final Task task = ar.getTask(); + if (task != null && !task.isVisibleRequested() + && mTransientLaunches != null) { + // If transition is transient, then snapshots are taken at end of + // transition. + mController.mTaskSnapshotController.recordTaskSnapshot( + task, false /* allowSnapshotHome */); + } + ar.commitVisibility(false /* visible */, false /* performLayout */, + true /* fromTransition */); activitiesWentInvisible = true; } } @@ -460,6 +469,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (mState != STATE_COLLECTING) { throw new IllegalStateException("Too late to abort."); } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId); mController.dispatchLegacyAppTransitionCancelled(); mState = STATE_ABORT; // Syncengine abort will call through to onTransactionReady() @@ -558,6 +568,19 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mVisibleAtTransitionEndTokens.add(wc.asWindowToken()); } + // Take task snapshots before the animation so that we can capture IME before it gets + // transferred. If transition is transient, IME won't be moved during the transition and + // the tasks are still live, so we take the snapshot at the end of the transition instead. + if (mTransientLaunches == null) { + for (int i = mParticipants.size() - 1; i >= 0; --i) { + final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); + if (ar == null || ar.isVisibleRequested() || ar.getTask() == null + || ar.getTask().isVisibleRequested()) continue; + mController.mTaskSnapshotController.recordTaskSnapshot( + ar.getTask(), false /* allowSnapshotHome */); + } + } + mStartTransaction = transaction; mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get(); buildFinishTransaction(mFinishTransaction, info.getRootLeash()); @@ -767,7 +790,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } } if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { - mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange(); + mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange( + true /* keyguardOccludingStarted */); } } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index fc5423942dc3..bf4cf5f75e36 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -30,10 +30,12 @@ import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.SystemClock; +import android.util.ArrayMap; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.WindowManager; import android.window.IRemoteTransition; +import android.window.ITransitionMetricsReporter; import android.window.ITransitionPlayer; import android.window.RemoteTransition; import android.window.TransitionInfo; @@ -45,6 +47,7 @@ import com.android.server.LocalServices; import com.android.server.statusbar.StatusBarManagerInternal; import java.util.ArrayList; +import java.util.function.LongConsumer; /** * Handles all the aspects of recording and synchronizing transitions. @@ -58,7 +61,10 @@ class TransitionController { private static final int LEGACY_STATE_RUNNING = 2; private ITransitionPlayer mTransitionPlayer; + final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter(); + final ActivityTaskManagerService mAtm; + final TaskSnapshotController mTaskSnapshotController; private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners = new ArrayList<>(); @@ -79,9 +85,11 @@ class TransitionController { // TODO(b/188595497): remove when not needed. final StatusBarManagerInternal mStatusBar; - TransitionController(ActivityTaskManagerService atm) { + TransitionController(ActivityTaskManagerService atm, + TaskSnapshotController taskSnapshotController) { mAtm = atm; mStatusBar = LocalServices.getService(StatusBarManagerInternal.class); + mTaskSnapshotController = taskSnapshotController; mTransitionPlayerDeath = () -> { synchronized (mAtm.mGlobalLock) { // Clean-up/finish any playing transitions. @@ -321,6 +329,8 @@ class TransitionController { /** @see Transition#finishTransition */ void finishTransition(@NonNull IBinder token) { + // It is usually a no-op but make sure that the metric consumer is removed. + mTransitionMetricsReporter.reportAnimationStart(token, 0 /* startTime */); final Transition record = Transition.fromBinder(token); if (record == null || !mPlayingTransitions.contains(record)) { Slog.e(TAG, "Trying to finish a non-playing transition " + token); @@ -385,9 +395,10 @@ class TransitionController { void dispatchLegacyAppTransitionStarting(TransitionInfo info) { final boolean keyguardGoingAway = info.isKeyguardGoingAway(); for (int i = 0; i < mLegacyListeners.size(); ++i) { + // TODO(shell-transitions): handle (un)occlude transition. mLegacyListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway, - 0 /* durationHint */, SystemClock.uptimeMillis(), - AnimationAdapter.STATUS_BAR_TRANSITION_DURATION); + false /* keyguardOcclude */, 0 /* durationHint */, + SystemClock.uptimeMillis(), AnimationAdapter.STATUS_BAR_TRANSITION_DURATION); } } @@ -416,6 +427,28 @@ class TransitionController { proto.end(token); } + static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub { + private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>(); + + void associate(IBinder transitionToken, LongConsumer consumer) { + synchronized (mMetricConsumers) { + mMetricConsumers.put(transitionToken, consumer); + } + } + + @Override + public void reportAnimationStart(IBinder transitionToken, long startTime) { + final LongConsumer c; + synchronized (mMetricConsumers) { + if (mMetricConsumers.isEmpty()) return; + c = mMetricConsumers.remove(transitionToken); + } + if (c != null) { + c.accept(startTime); + } + } + } + class Lock { private int mTransitionWaiters = 0; void runWhenIdle(long timeout, Runnable r) { diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java index 25f7269effe8..26527233aef0 100644 --- a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java @@ -20,6 +20,7 @@ import static com.android.server.wm.AnimationAdapterProto.REMOTE; import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; +import android.annotation.NonNull; import android.graphics.Point; import android.os.SystemClock; import android.util.proto.ProtoOutputStream; @@ -64,18 +65,17 @@ class WallpaperAnimationAdapter implements AnimationAdapter { * * @return RemoteAnimationTarget[] targets for all the visible wallpaper windows */ - public static RemoteAnimationTarget[] startWallpaperAnimations(WindowManagerService service, + public static RemoteAnimationTarget[] startWallpaperAnimations(DisplayContent displayContent, long durationHint, long statusBarTransitionDelay, Consumer<WallpaperAnimationAdapter> animationCanceledRunnable, ArrayList<WallpaperAnimationAdapter> adaptersOut) { + if (!displayContent.mWallpaperController.isWallpaperVisible()) { + ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, + "\tWallpaper of display=%s is not visible", displayContent); + return new RemoteAnimationTarget[0]; + } final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); - service.mRoot.forAllWallpaperWindows(wallpaperWindow -> { - if (!wallpaperWindow.getDisplayContent().mWallpaperController.isWallpaperVisible()) { - ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tNot visible=%s", wallpaperWindow); - return; - } - - ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tvisible=%s", wallpaperWindow); + displayContent.forAllWallpaperWindows(wallpaperWindow -> { final WallpaperAnimationAdapter wallpaperAdapter = new WallpaperAnimationAdapter( wallpaperWindow, durationHint, statusBarTransitionDelay, animationCanceledRunnable); @@ -134,7 +134,8 @@ class WallpaperAnimationAdapter implements AnimationAdapter { @Override public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, - @AnimationType int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { + @AnimationType int type, + @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation"); // Restore z-layering until client has a chance to modify it. diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 0909462585af..d93b649b390a 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -24,12 +24,12 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT; @@ -49,6 +49,8 @@ import android.view.WindowManager; import android.view.animation.Animation; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.ProtoLogImpl; +import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ToBooleanFunction; import java.io.PrintWriter; @@ -291,10 +293,11 @@ class WallpaperController { for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) { final WallpaperWindowToken token = mWallpaperTokens.get(i); token.setVisibility(false); - if (DEBUG_WALLPAPER_LIGHT && token.isVisible()) { - Slog.d(TAG, "Hiding wallpaper " + token - + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev=" - + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, " ")); + if (ProtoLogImpl.isEnabled(WM_DEBUG_WALLPAPER) && token.isVisible()) { + ProtoLog.d(WM_DEBUG_WALLPAPER, + "Hiding wallpaper %s from %s target=%s prev=%s callers=%s", + token, winGoingAway, mWallpaperTarget, mPrevWallpaperTarget, + Debug.getCallers(5)); } } } @@ -544,15 +547,15 @@ class WallpaperController { // Is it time to stop animating? if (!mPrevWallpaperTarget.isAnimatingLw()) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "No longer animating wallpaper targets!"); + ProtoLog.v(WM_DEBUG_WALLPAPER, "No longer animating wallpaper targets!"); mPrevWallpaperTarget = null; mWallpaperTarget = wallpaperTarget; } return; } - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "New wallpaper target: " + wallpaperTarget + " prevTarget: " + mWallpaperTarget); + ProtoLog.v(WM_DEBUG_WALLPAPER, "New wallpaper target: %s prevTarget: %s caller=%s", + wallpaperTarget, mWallpaperTarget, Debug.getCallers(5)); mPrevWallpaperTarget = null; @@ -570,8 +573,8 @@ class WallpaperController { // then we are in our super special mode! boolean oldAnim = prevWallpaperTarget.isAnimatingLw(); boolean foundAnim = wallpaperTarget.isAnimatingLw(); - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "New animation: " + foundAnim + " old animation: " + oldAnim); + ProtoLog.v(WM_DEBUG_WALLPAPER, "New animation: %s old animation: %s", + foundAnim, oldAnim); if (!foundAnim || !oldAnim) { return; @@ -586,14 +589,14 @@ class WallpaperController { final boolean oldTargetHidden = prevWallpaperTarget.mActivityRecord != null && !prevWallpaperTarget.mActivityRecord.mVisibleRequested; - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Animating wallpapers:" + " old: " - + prevWallpaperTarget + " hidden=" + oldTargetHidden + " new: " + wallpaperTarget - + " hidden=" + newTargetHidden); + ProtoLog.v(WM_DEBUG_WALLPAPER, "Animating wallpapers: " + + "old: %s hidden=%b new: %s hidden=%b", + prevWallpaperTarget, oldTargetHidden, wallpaperTarget, newTargetHidden); mPrevWallpaperTarget = prevWallpaperTarget; if (newTargetHidden && !oldTargetHidden) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Old wallpaper still the target."); + ProtoLog.v(WM_DEBUG_WALLPAPER, "Old wallpaper still the target."); // Use the old target if new target is hidden but old target // is not. If they're both hidden, still use the new target. mWallpaperTarget = prevWallpaperTarget; @@ -661,8 +664,8 @@ class WallpaperController { /* x= */ 0, /* y= */ 0, /* z= */ 0, /* extras= */ null, /* sync= */ false); } - if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, "New wallpaper: target=" + mWallpaperTarget - + " prev=" + mPrevWallpaperTarget); + ProtoLog.d(WM_DEBUG_WALLPAPER, "New wallpaper: target=%s prev=%s", + mWallpaperTarget, mPrevWallpaperTarget); } boolean processWallpaperDrawPendingTimeout() { @@ -798,6 +801,18 @@ class WallpaperController { wallpaperBuffer.getHardwareBuffer(), wallpaperBuffer.getColorSpace()); } + /** + * Mirrors the visible wallpaper if it's available. + * + * @return A SurfaceControl for the parent of the mirrored wallpaper. + */ + SurfaceControl mirrorWallpaperSurface() { + final WindowState wallpaperWindowState = getTopVisibleWallpaper(); + return wallpaperWindowState != null + ? SurfaceControl.mirrorSurface(wallpaperWindowState.getSurfaceControl()) + : null; + } + WindowState getTopVisibleWallpaper() { mTmpTopWallpaper = null; diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 75c84c44c48e..3a639f50603f 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -20,7 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -28,7 +28,6 @@ import android.annotation.Nullable; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; -import android.util.Slog; import android.view.DisplayInfo; import android.view.ViewGroup; import android.view.WindowManager; @@ -107,8 +106,8 @@ class WallpaperWindowToken extends WindowToken { void updateWallpaperWindows(boolean visible) { if (isVisible() != visible) { - if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, - "Wallpaper token " + token + " visible=" + visible); + ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b", + token, visible); setVisibility(visible); } final WallpaperController wallpaperController = mDisplayContent.mWallpaperController; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 841783d6b8cd..51ecce0ec9ec 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -57,6 +57,7 @@ import static com.android.server.wm.WindowContainerProto.CONFIGURATION_CONTAINER import static com.android.server.wm.WindowContainerProto.IDENTIFIER; import static com.android.server.wm.WindowContainerProto.ORIENTATION; import static com.android.server.wm.WindowContainerProto.SURFACE_ANIMATOR; +import static com.android.server.wm.WindowContainerProto.SURFACE_CONTROL; import static com.android.server.wm.WindowContainerProto.VISIBLE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -70,7 +71,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; -import android.app.WindowConfiguration; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -111,6 +111,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -127,26 +128,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM; - /** Animation layer that happens above all animating {@link Task}s. */ - static final int ANIMATION_LAYER_STANDARD = 0; - - /** Animation layer that happens above all {@link Task}s. */ - static final int ANIMATION_LAYER_BOOSTED = 1; - - /** - * Animation layer that is reserved for {@link WindowConfiguration#ACTIVITY_TYPE_HOME} - * activities and all activities that are being controlled by the recents animation. This - * layer is generally below all {@link Task}s. - */ - static final int ANIMATION_LAYER_HOME = 2; - - @IntDef(prefix = { "ANIMATION_LAYER_" }, value = { - ANIMATION_LAYER_STANDARD, - ANIMATION_LAYER_BOOSTED, - ANIMATION_LAYER_HOME, - }) - @interface AnimationLayer {} - static final int POSITION_TOP = Integer.MAX_VALUE; static final int POSITION_BOTTOM = Integer.MIN_VALUE; @@ -198,6 +179,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< */ protected final SurfaceAnimator mSurfaceAnimator; + /** The parent leash added for animation. */ + @Nullable + private SurfaceControl mAnimationLeash; + final SurfaceFreezer mSurfaceFreezer; protected final WindowManagerService mWmService; final TransitionController mTransitionController; @@ -2429,6 +2414,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< if (mSurfaceAnimator.isAnimating()) { mSurfaceAnimator.dumpDebug(proto, SURFACE_ANIMATOR); } + if (mSurfaceControl != null) { + mSurfaceControl.dumpDebug(proto, SURFACE_CONTROL); + } // add children to proto for (int i = 0; i < getChildCount(); i++) { @@ -2578,11 +2566,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @param animationFinishedCallback The callback being triggered when the animation finishes. * @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a * cancel call to the underlying AnimationAdapter. + * @param snapshotAnim The animation to run for the snapshot. {@code null} if there is no + * snapshot. */ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, @AnimationType int type, @Nullable OnAnimationFinishedCallback animationFinishedCallback, - @Nullable Runnable animationCancelledCallback) { + @Nullable Runnable animationCancelledCallback, + @Nullable AnimationAdapter snapshotAnim) { if (DEBUG_ANIM) { Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + anim); } @@ -2590,14 +2581,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // TODO: This should use isVisible() but because isVisible has a really weird meaning at // the moment this doesn't work for all animatable window containers. mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback, - animationCancelledCallback, mSurfaceFreezer); + animationCancelledCallback, snapshotAnim, mSurfaceFreezer); } void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, @AnimationType int type, @Nullable OnAnimationFinishedCallback animationFinishedCallback) { startAnimation(t, anim, hidden, type, animationFinishedCallback, - null /* adapterAnimationCancelledCallback */); + null /* adapterAnimationCancelledCallback */, null /* snapshotAnim */); } void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden, @@ -2668,17 +2659,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return getParentSurfaceControl(); } - /** - * @return The layer on which all app animations are happening. - */ - SurfaceControl getAppAnimationLayer(@AnimationLayer int animationLayer) { - final WindowContainer parent = getParent(); - if (parent != null) { - return parent.getAppAnimationLayer(animationLayer); - } - return null; - } - // TODO: Remove this and use #getBounds() instead once we set an app transition animation // on TaskStack. Rect getAnimationBounds(int appRootTaskClipMode) { @@ -2856,21 +2836,26 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< taskDisplayArea.setBackgroundColor(backgroundColor); } + // Atomic counter to make sure the clearColor callback is only called one. + // It will be called twice in the case we cancel the animation without restart + // (in that case it will run as the cancel and finished callbacks). + final AtomicInteger callbackCounter = new AtomicInteger(0); + final Runnable clearBackgroundColorHandler = () -> { + if (callbackCounter.getAndIncrement() == 0) { + taskDisplayArea.clearBackgroundColor(); + } + }; + final Runnable cleanUpCallback = isSettingBackgroundColor - ? taskDisplayArea::clearBackgroundColor : () -> {}; + ? clearBackgroundColorHandler : () -> {}; startAnimation(getPendingTransaction(), adapter, !isVisible(), - ANIMATION_TYPE_APP_TRANSITION, - (type, anim) -> cleanUpCallback.run(), - cleanUpCallback); + ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> cleanUpCallback.run(), + cleanUpCallback, thumbnailAdapter); if (adapter.getShowWallpaper()) { getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } - if (thumbnailAdapter != null) { - mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(), - thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> { }); - } } } @@ -3000,6 +2985,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Override public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { mLastLayer = -1; + mAnimationLeash = leash; reassignLayer(t); // Leash is now responsible for position, so set our position to 0. @@ -3009,11 +2995,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Override public void onAnimationLeashLost(Transaction t) { mLastLayer = -1; - mSurfaceFreezer.unfreeze(t); + mAnimationLeash = null; reassignLayer(t); updateSurfacePosition(t); } + @Override + public SurfaceControl getAnimationLeash() { + return mAnimationLeash; + } + private void doAnimationFinished(@AnimationType int type, AnimationAdapter anim) { for (int i = 0; i < mSurfaceAnimationSources.size(); ++i) { mSurfaceAnimationSources.valueAt(i).onAnimationFinished(type, anim); diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java index 08404411c02b..c95470071e2d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java +++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java @@ -43,7 +43,6 @@ public class WindowManagerDebugConfig { static final boolean DEBUG_CONFIGURATION = false; static final boolean DEBUG_STARTING_WINDOW_VERBOSE = false; static final boolean DEBUG_WALLPAPER = false; - static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER; static final boolean DEBUG_DRAG = true; static final boolean DEBUG_SCREENSHOT = false; static final boolean DEBUG_LAYOUT_REPEATS = false; diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index ba0266a575bf..4e51a17e03e0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -194,7 +194,7 @@ public abstract class WindowManagerInternal { /** * Called when a pending app transition gets cancelled. * - * @param keyguardGoingAway true if keyguard going away transition transition got cancelled. + * @param keyguardGoingAway true if keyguard going away transition got cancelled. */ public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {} @@ -207,6 +207,7 @@ public abstract class WindowManagerInternal { * Called when an app transition gets started * * @param keyguardGoingAway true if keyguard going away transition is started. + * @param keyguardOccluding true if keyguard (un)occlude transition is started. * @param duration the total duration of the transition * @param statusBarAnimationStartTime the desired start time for all visual animations in * the status bar caused by this app transition in uptime millis @@ -218,8 +219,9 @@ public abstract class WindowManagerInternal { * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER}, * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}. */ - public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration, - long statusBarAnimationStartTime, long statusBarAnimationDuration) { + public int onAppTransitionStartingLocked(boolean keyguardGoingAway, + boolean keyguardOccluding, long duration, long statusBarAnimationStartTime, + long statusBarAnimationDuration) { return 0; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4cf8c97eb132..fd0f4636ec1f 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -433,7 +433,7 @@ public class WindowManagerService extends IWindowManager.Stub "persist.wm.enable_remote_keyguard_animation"; private static final int sEnableRemoteKeyguardAnimation = - SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 0); + SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1); /** * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY @@ -2472,7 +2472,8 @@ public class WindowManagerService extends IWindowManager.Stub if (isPrimaryDisplay) { transformHint = (transformHint + mPrimaryDisplayOrientation) % 4; } - outSurfaceControl.setTransformHint(transformHint); + outSurfaceControl.setTransformHint( + SurfaceControl.rotationToBufferTransform(transformHint)); ProtoLog.v(WM_DEBUG_ORIENTATION, "Passing transform hint %d for window %s%s", transformHint, win, @@ -2582,13 +2583,17 @@ public class WindowManagerService extends IWindowManager.Stub // an exit. win.mAnimatingExit = true; } else if (win.mDisplayContent.okToAnimate() - && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)) { - // If the wallpaper is currently behind this - // window, we need to change both of them inside - // of a transaction to avoid artifacts. + && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win) + && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) { + // If the wallpaper is currently behind this app window, we need to change both of them + // inside of a transaction to avoid artifacts. + // For NotificationShade, sysui is in charge of running window animation and it updates + // the client view visibility only after both NotificationShade and the wallpaper are + // hidden. So we don't need to care about exit animation, but can destroy its surface + // immediately. win.mAnimatingExit = true; } else { - boolean stopped = win.mActivityRecord != null ? win.mActivityRecord.mAppStopped : true; + boolean stopped = win.mActivityRecord == null || win.mActivityRecord.mAppStopped; // We set mDestroying=true so ActivityRecord#notifyAppStopped in-to destroy surfaces // will later actually destroy the surface if we do not do so here. Normally we leave // this to the exit animation. @@ -3787,6 +3792,14 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override + public SurfaceControl mirrorWallpaperSurface(int displayId) { + synchronized (mGlobalLock) { + final DisplayContent dc = mRoot.getDisplayContent(displayId); + return dc.mWallpaperController.mirrorWallpaperSurface(); + } + } + /** * Takes a snapshot of the screen. In landscape mode this grabs the whole screen. * In portrait mode, it grabs the upper region of the screen based on the vertical dimension @@ -4981,23 +4994,49 @@ public class WindowManagerService extends IWindowManager.Stub return Surface.ROTATION_0; } + // Returns an input target which is mapped to the given input token. This can be a WindowState + // or an embedded window. + @Nullable InputTarget getInputTargetFromToken(IBinder inputToken) { + WindowState windowState = mInputToWindowMap.get(inputToken); + if (windowState != null) { + return windowState; + } + + EmbeddedWindowController.EmbeddedWindow embeddedWindow = + mEmbeddedWindowController.get(inputToken); + if (embeddedWindow != null) { + return embeddedWindow; + } + + return null; + } + void reportFocusChanged(IBinder oldToken, IBinder newToken) { - WindowState lastFocus; - WindowState newFocus; + InputTarget lastTarget; + InputTarget newTarget; synchronized (mGlobalLock) { - lastFocus = mInputToWindowMap.get(oldToken); - newFocus = mInputToWindowMap.get(newToken); - ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus changing: %s -> %s", lastFocus, newFocus); + lastTarget = getInputTargetFromToken(oldToken); + newTarget = getInputTargetFromToken(newToken); + if (newTarget == null && lastTarget == null) { + Slog.v(TAG_WM, "Unknown focus tokens, dropping reportFocusChanged"); + return; + } + + mAccessibilityController.onFocusChanged(lastTarget, newTarget); + ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus changing: %s -> %s", lastTarget, newTarget); } - if (newFocus != null) { - mAnrController.onFocusChanged(newFocus); - newFocus.reportFocusChangedSerialized(true); + // Call WindowState focus change observers + WindowState newFocusedWindow = newTarget != null ? newTarget.getWindowState() : null; + if (newFocusedWindow != null && newFocusedWindow.mInputChannelToken == newToken) { + mAnrController.onFocusChanged(newFocusedWindow); + newFocusedWindow.reportFocusChangedSerialized(true); notifyFocusChanged(); } - if (lastFocus != null) { - lastFocus.reportFocusChangedSerialized(false); + WindowState lastFocusedWindow = lastTarget != null ? lastTarget.getWindowState() : null; + if (lastFocusedWindow != null && lastFocusedWindow.mInputChannelToken == oldToken) { + lastFocusedWindow.reportFocusChangedSerialized(false); } } @@ -5797,7 +5836,9 @@ public class WindowManagerService extends IWindowManager.Stub return; } - if (!displayContent.isReady() || !mPolicy.isScreenOn() || !displayContent.okToAnimate()) { + if (!displayContent.isReady() || !displayContent.getDisplayPolicy().isScreenOnFully() + || displayContent.getDisplayInfo().state == Display.STATE_OFF + || !displayContent.okToAnimate()) { // No need to freeze the screen before the display is ready, if the screen is off, // or we can't currently animate. return; @@ -6391,6 +6432,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mGlobalConfiguration="); pw.println(mRoot.getConfiguration()); pw.print(" mHasPermanentDpad="); pw.println(mHasPermanentDpad); mRoot.dumpTopFocusedDisplayId(pw); + mRoot.dumpDefaultMinSizeOfResizableTask(pw); mRoot.forAllDisplays(dc -> { final int displayId = dc.getDisplayId(); final InsetsControlTarget imeLayeringTarget = dc.getImeTarget(IME_TARGET_LAYERING); @@ -7522,11 +7564,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public IBinder getFocusedWindowToken() { synchronized (mGlobalLock) { - WindowState windowState = getFocusedWindowLocked(); - if (windowState != null) { - return windowState.mClient.asBinder(); - } - return null; + return mAccessibilityController.getFocusedWindowToken(); } } @@ -8267,7 +8305,7 @@ public class WindowManagerService extends IWindowManager.Stub clientChannel = win.openInputChannel(); mEmbeddedWindowController.add(clientChannel.getToken(), win); applicationHandle = win.getApplicationHandle(); - name = win.getName(); + name = win.toString(); } updateInputChannel(clientChannel.getToken(), callingUid, callingPid, displayId, surface, @@ -8333,7 +8371,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.e(TAG, "Couldn't find window for provided channelToken."); return; } - name = win.getName(); + name = win.toString(); applicationHandle = win.getApplicationHandle(); } @@ -8542,10 +8580,9 @@ public class WindowManagerService extends IWindowManager.Stub SurfaceControl.Transaction t = mTransactionFactory.get(); final int displayId = embeddedWindow.mDisplayId; if (grantFocus) { - t.setFocusedWindow(inputToken, embeddedWindow.getName(), displayId).apply(); + t.setFocusedWindow(inputToken, embeddedWindow.toString(), displayId).apply(); EventLog.writeEvent(LOGTAG_INPUT_FOCUS, - "Focus request " + embeddedWindow.getName(), - "reason=grantEmbeddedWindowFocus(true)"); + "Focus request " + embeddedWindow, "reason=grantEmbeddedWindowFocus(true)"); } else { // Search for a new focus target DisplayContent displayContent = mRoot.getDisplayContent(displayId); @@ -8554,18 +8591,18 @@ public class WindowManagerService extends IWindowManager.Stub if (newFocusTarget == null) { ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus remove request for " + "win=%s dropped since no candidate was found", - embeddedWindow.getName()); + embeddedWindow); return; } t.requestFocusTransfer(newFocusTarget.mInputChannelToken, newFocusTarget.getName(), - inputToken, embeddedWindow.getName(), + inputToken, embeddedWindow.toString(), displayId).apply(); EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Transfer focus request " + newFocusTarget, "reason=grantEmbeddedWindowFocus(false)"); } ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s", - embeddedWindow.getName(), grantFocus); + embeddedWindow, grantFocus); } } @@ -8594,24 +8631,24 @@ public class WindowManagerService extends IWindowManager.Stub } SurfaceControl.Transaction t = mTransactionFactory.get(); if (grantFocus) { - t.requestFocusTransfer(targetInputToken, embeddedWindow.getName(), + t.requestFocusTransfer(targetInputToken, embeddedWindow.toString(), hostWindow.mInputChannel.getToken(), hostWindow.getName(), hostWindow.getDisplayId()).apply(); EventLog.writeEvent(LOGTAG_INPUT_FOCUS, - "Transfer focus request " + embeddedWindow.getName(), + "Transfer focus request " + embeddedWindow, "reason=grantEmbeddedWindowFocus(true)"); } else { t.requestFocusTransfer(hostWindow.mInputChannel.getToken(), hostWindow.getName(), targetInputToken, - embeddedWindow.getName(), + embeddedWindow.toString(), hostWindow.getDisplayId()).apply(); EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Transfer focus request " + hostWindow, "reason=grantEmbeddedWindowFocus(false)"); } ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s", - embeddedWindow.getName(), grantFocus); + embeddedWindow, grantFocus); } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 781b53df998e..6e706e9df003 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -66,6 +66,7 @@ import android.window.IDisplayAreaOrganizerController; import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; import android.window.ITaskOrganizerController; +import android.window.ITransitionMetricsReporter; import android.window.ITransitionPlayer; import android.window.IWindowContainerTransactionCallback; import android.window.IWindowOrganizerController; @@ -83,7 +84,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.function.Consumer; +import java.util.function.Function; /** * Server side implementation for the interface for organizing windows @@ -117,7 +118,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final DisplayAreaOrganizerController mDisplayAreaOrganizerController; final TaskFragmentOrganizerController mTaskFragmentOrganizerController; - final TransitionController mTransitionController; + TransitionController mTransitionController; /** * A Map which manages the relationship between * {@link TaskFragmentCreationParams#getFragmentToken()} and {@link TaskFragment} @@ -131,7 +132,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mTaskOrganizerController = new TaskOrganizerController(mService); mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService); mTaskFragmentOrganizerController = new TaskFragmentOrganizerController(atm); - mTransitionController = new TransitionController(atm); + } + + void setWindowManager(WindowManagerService wms) { + mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController); } TransitionController getTransitionController() { @@ -890,24 +894,31 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // We want to collect the tasks first before re-parenting to avoid array shifting on us. final ArrayList<Task> tasksToReparent = new ArrayList<>(); - currentParent.forAllTasks((Consumer<Task>) (task) -> { + currentParent.forAllTasks((Function<Task, Boolean>) task -> { Slog.i(TAG, " Processing task=" + task); - if (task.mCreatedByOrganizer - || task.getParent() != finalCurrentParent) { + final boolean reparent; + if (task.mCreatedByOrganizer || task.getParent() != finalCurrentParent) { // We only care about non-organized task that are direct children of the thing we // are reparenting from. - return; + return false; } if (newParentInMultiWindow && !task.supportsMultiWindowInDisplayArea(newParentTda)) { Slog.e(TAG, "reparentChildrenTasksHierarchyOp non-resizeable task to multi window," + " task=" + task); - return; + return false; + } + if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType()) + || !ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) { + return false; } - if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())) return; - if (!ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) return; - tasksToReparent.add(task); - }, !hop.getToTop()); + if (hop.getToTop()) { + tasksToReparent.add(0, task); + } else { + tasksToReparent.add(task); + } + return hop.getReparentTopOnly() && tasksToReparent.size() == 1; + }); final int count = tasksToReparent.size(); for (int i = 0; i < count; ++i) { @@ -1030,6 +1041,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } + @Override + public ITransitionMetricsReporter getTransitionMetricsReporter() { + return mTransitionController.mTransitionMetricsReporter; + } + /** Whether the configuration changes are important to report back to an organizer. */ static boolean configurationsAreEqualForOrganizer( Configuration newConfig, @Nullable Configuration oldConfig) { diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 719d52c89209..af386419d455 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -952,7 +952,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio final int displayId = r.getDisplayId(); final Context c = root.getDisplayUiContext(displayId); - if (r.mVisibleRequested && !displayContexts.contains(c)) { + if (c != null && r.mVisibleRequested && !displayContexts.contains(c)) { displayContexts.add(c); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index ad4734f0526c..2410d7ec13c2 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -273,7 +273,7 @@ import java.util.function.Predicate; /** A window in the window manager. */ class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState, - InsetsControlTarget { + InsetsControlTarget, InputTarget { static final String TAG = TAG_WITH_CLASS_NAME ? "WindowState" : TAG_WM; // The minimal size of a window within the usable area of the freeform root task. @@ -1727,7 +1727,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return state; } - int getDisplayId() { + @Override + public int getDisplayId() { final DisplayContent displayContent = getDisplayContent(); if (displayContent == null) { return Display.INVALID_DISPLAY; @@ -1735,6 +1736,21 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return displayContent.getDisplayId(); } + @Override + public WindowState getWindowState() { + return this; + } + + @Override + public IWindow getIWindow() { + return mClient; + } + + @Override + public int getPid() { + return mSession.mPid; + } + Task getTask() { return mActivityRecord != null ? mActivityRecord.getTask() : null; } @@ -6034,7 +6050,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override boolean isSyncFinished() { - if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mViewVisibility == View.GONE) { + if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mViewVisibility == View.GONE + && !isVisibleRequested()) { // Don't wait for GONE windows. However, we don't alter the state in case the window // becomes un-gone while the syncset is still active. return true; diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java index 4a67ec71fcaa..6faa7e7c89e7 100644 --- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java @@ -74,15 +74,20 @@ public class BatteryExternalStatsWorkerTest { @Test public void testTargetedEnergyConsumerQuerying() { final int numCpuClusters = 4; + final int numDisplays = 5; final int numOther = 3; // Add some energy consumers used by BatteryExternalStatsWorker. final IntArray tempAllIds = new IntArray(); - final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0, - "display"); - tempAllIds.add(displayId); - mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345); + final int[] displayIds = new int[numDisplays]; + for (int i = 0; i < numDisplays; i++) { + displayIds[i] = mPowerStatsInternal.addEnergyConsumer( + EnergyConsumerType.DISPLAY, i, "display" + i); + tempAllIds.add(displayIds[i]); + mPowerStatsInternal.incrementEnergyConsumption(displayIds[i], 12345 + i); + } + Arrays.sort(displayIds); final int wifiId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.WIFI, 0, "wifi"); @@ -130,9 +135,13 @@ public class BatteryExternalStatsWorkerTest { final EnergyConsumerResult[] displayResults = mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_DISPLAY).getNow(null); - // Results should only have the display energy consumer - assertEquals(1, displayResults.length); - assertEquals(displayId, displayResults[0].id); + // Results should only have the cpu cluster energy consumers + final int[] receivedDisplayIds = new int[displayResults.length]; + for (int i = 0; i < displayResults.length; i++) { + receivedDisplayIds[i] = displayResults[i].id; + } + Arrays.sort(receivedDisplayIds); + assertArrayEquals(displayIds, receivedDisplayIds); final EnergyConsumerResult[] wifiResults = mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_WIFI).getNow(null); @@ -193,6 +202,7 @@ public class BatteryExternalStatsWorkerTest { public class TestBatteryStatsImpl extends BatteryStatsImpl { public TestBatteryStatsImpl(Context context) { mPowerProfile = new PowerProfile(context, true /* forTest */); + initTimersAndCounters(); } } diff --git a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java index 8c87506295f3..a0cbcadee844 100644 --- a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java +++ b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java @@ -16,8 +16,6 @@ package com.android.server.am; -import static com.android.server.am.MeasuredEnergySnapshot.UNAVAILABLE; - import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; @@ -120,7 +118,7 @@ public final class MeasuredEnergySnapshotTest { // results0 MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0); if (delta != null) { // null is fine here. If non-null, it better be uninteresting though. - assertEquals(UNAVAILABLE, delta.displayChargeUC); + assertNull(delta.displayChargeUC); assertNull(delta.otherTotalChargeUC); assertNull(delta.otherUidChargesUC); } @@ -130,7 +128,7 @@ public final class MeasuredEnergySnapshotTest { assertNotNull(delta); long expectedChargeUC; expectedChargeUC = calculateChargeConsumedUC(14_000, VOLTAGE_0, 24_000, VOLTAGE_1); - assertEquals(expectedChargeUC, delta.displayChargeUC); + assertEquals(expectedChargeUC, delta.displayChargeUC[0]); assertNotNull(delta.otherTotalChargeUC); @@ -149,14 +147,14 @@ public final class MeasuredEnergySnapshotTest { delta = snapshot.updateAndGetDelta(RESULTS_2, VOLTAGE_2); assertNotNull(delta); expectedChargeUC = calculateChargeConsumedUC(24_000, VOLTAGE_1, 36_000, VOLTAGE_2); - assertEquals(expectedChargeUC, delta.displayChargeUC); + assertEquals(expectedChargeUC, delta.displayChargeUC[0]); assertNull(delta.otherUidChargesUC); assertNull(delta.otherTotalChargeUC); // results3 delta = snapshot.updateAndGetDelta(RESULTS_3, VOLTAGE_3); assertNotNull(delta); - assertEquals(UNAVAILABLE, delta.displayChargeUC); + assertNull(delta.displayChargeUC); assertNotNull(delta.otherTotalChargeUC); @@ -183,7 +181,7 @@ public final class MeasuredEnergySnapshotTest { delta = snapshot.updateAndGetDelta(RESULTS_4, VOLTAGE_4); assertNotNull(delta); expectedChargeUC = calculateChargeConsumedUC(36_000, VOLTAGE_2, 43_000, VOLTAGE_4); - assertEquals(expectedChargeUC, delta.displayChargeUC); + assertEquals(expectedChargeUC, delta.displayChargeUC[0]); assertNotNull(delta.otherTotalChargeUC); expectedChargeUC = calculateChargeConsumedUC(190_000, VOLTAGE_3, 290_000, VOLTAGE_4); @@ -210,7 +208,7 @@ public final class MeasuredEnergySnapshotTest { // results0 MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0); if (delta != null) { // null is fine here. If non-null, it better be uninteresting though. - assertEquals(UNAVAILABLE, delta.displayChargeUC); + assertNull(delta.displayChargeUC); assertNull(delta.otherTotalChargeUC); assertNull(delta.otherUidChargesUC); } @@ -220,7 +218,7 @@ public final class MeasuredEnergySnapshotTest { assertNotNull(delta); final long expectedChargeUC = calculateChargeConsumedUC(14_000, VOLTAGE_0, 24_000, VOLTAGE_1); - assertEquals(expectedChargeUC, delta.displayChargeUC); + assertEquals(expectedChargeUC, delta.displayChargeUC[0]); assertNull(delta.otherTotalChargeUC); // Although in the results, they're not in the idMap assertNull(delta.otherUidChargesUC); } diff --git a/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java new file mode 100644 index 000000000000..ea746d1f4fd3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java @@ -0,0 +1,102 @@ +/* + * 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.camera; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.InstrumentationRegistry; + +import android.content.Context; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraMetadata; +import android.view.Display; +import android.view.Surface; + +import java.util.HashMap; + +@RunWith(JUnit4.class) +public class CameraServiceProxyTest { + + @Test + public void testGetCropRotateScale() { + + Context ctx = InstrumentationRegistry.getContext(); + + // Check resizeability and SDK + CameraServiceProxy.TaskInfo taskInfo = new CameraServiceProxy.TaskInfo(); + taskInfo.isResizeable = true; + taskInfo.displayId = Display.DEFAULT_DISPLAY; + taskInfo.isFixedOrientationLandscape = false; + taskInfo.isFixedOrientationPortrait = true; + // Resizeable apps should be ignored + assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo, + Surface.ROTATION_90 , CameraCharacteristics.LENS_FACING_BACK, + /*ignoreResizableAndSdkCheck*/false)).isEqualTo( + CameraMetadata.SCALER_ROTATE_AND_CROP_NONE); + // Resizeable apps will be considered in case the ignore flag is set + assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo, + Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK, + /*ignoreResizableAndSdkCheck*/true)).isEqualTo( + CameraMetadata.SCALER_ROTATE_AND_CROP_90); + taskInfo.isResizeable = false; + // Non-resizeable apps should be considered + assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo, + Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK, + /*ignoreResizableAndSdkCheck*/false)).isEqualTo( + CameraMetadata.SCALER_ROTATE_AND_CROP_90); + // The ignore flag for non-resizeable should have no effect + assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo, + Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK, + /*ignoreResizableAndSdkCheck*/true)).isEqualTo( + CameraMetadata.SCALER_ROTATE_AND_CROP_90); + // Non-fixed orientation should be ignored + taskInfo.isFixedOrientationLandscape = false; + taskInfo.isFixedOrientationPortrait = false; + assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo, + Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK, + /*ignoreResizableAndSdkCheck*/true)).isEqualTo( + CameraMetadata.SCALER_ROTATE_AND_CROP_NONE); + // Check rotation and lens facing combinations + HashMap<Integer, Integer> backFacingMap = new HashMap<Integer, Integer>() {{ + put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE); + put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_90); + put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_270); + put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180); + }}; + taskInfo.isFixedOrientationPortrait = true; + backFacingMap.forEach((key, value) -> { + assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo, + key, CameraCharacteristics.LENS_FACING_BACK, + /*ignoreResizableAndSdkCheck*/true)).isEqualTo(value); + }); + HashMap<Integer, Integer> frontFacingMap = new HashMap<Integer, Integer>() {{ + put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE); + put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_270); + put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_90); + put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180); + }}; + frontFacingMap.forEach((key, value) -> { + assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo, + key, CameraCharacteristics.LENS_FACING_FRONT, + /*ignoreResizableAndSdkCheck*/true)).isEqualTo(value); + }); + } +} diff --git a/services/tests/servicestests/src/com/android/server/camera/OWNERS b/services/tests/servicestests/src/com/android/server/camera/OWNERS new file mode 100644 index 000000000000..f48a95c5b3a3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/camera/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/av:/camera/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 5a00e0d6530d..62e0a19abd7f 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -998,6 +998,8 @@ public class VibratorManagerServiceTest { throws Exception { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); + setRingerMode(AudioManager.RINGER_MODE_NORMAL); + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); createSystemReadyService(); IExternalVibrationController firstController = mock(IExternalVibrationController.class); @@ -1006,8 +1008,11 @@ public class VibratorManagerServiceTest { firstController); int firstScale = mExternalVibratorService.onExternalVibrationStart(firstVibration); - ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS, - secondController); + AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) + .build(); + ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME, + ringtoneAudioAttrs, secondController); int secondScale = mExternalVibratorService.onExternalVibrationStart(secondVibration); assertEquals(IExternalVibratorService.SCALE_NONE, firstScale); @@ -1040,6 +1045,37 @@ public class VibratorManagerServiceTest { assertEquals(Arrays.asList(true), mVibratorProviders.get(1).getExternalControlStates()); } + @Test + public void onExternalVibration_withRingtone_usesRingerModeSettings() { + mockVibrators(1); + mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); + mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM); + AudioAttributes audioAttrs = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) + .build(); + ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs, + mock(IExternalVibrationController.class)); + + setRingerMode(AudioManager.RINGER_MODE_NORMAL); + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); + setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + createSystemReadyService(); + int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); + assertEquals(IExternalVibratorService.SCALE_MUTE, scale); + + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); + setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1); + createSystemReadyService(); + scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); + assertEquals(IExternalVibratorService.SCALE_NONE, scale); + + setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); + setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + createSystemReadyService(); + scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); + assertEquals(IExternalVibratorService.SCALE_NONE, scale); + } + private VibrationEffectSegment expectedPrebaked(int effectId) { return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index 577e36c7d5db..a834e2b6cc5a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -118,7 +118,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions()); assertEquals(getSmartReplies(key, i), ranking.getSmartReplies()); assertEquals(canBubble(i), ranking.canBubble()); - assertEquals(visuallyInterruptive(i), ranking.visuallyInterruptive()); + assertEquals(isTextChanged(i), ranking.isTextChanged()); assertEquals(isConversation(i), ranking.isConversation()); assertEquals(getShortcutInfo(i).getId(), ranking.getConversationShortcutInfo().getId()); assertEquals(getRankingAdjustment(i), ranking.getRankingAdjustment()); @@ -189,7 +189,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { (ArrayList) tweak.getSmartActions(), (ArrayList) tweak.getSmartReplies(), tweak.canBubble(), - tweak.visuallyInterruptive(), + tweak.isTextChanged(), tweak.isConversation(), tweak.getConversationShortcutInfo(), tweak.getRankingAdjustment(), @@ -270,7 +270,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { getSmartActions(key, i), getSmartReplies(key, i), canBubble(i), - visuallyInterruptive(i), + isTextChanged(i), isConversation(i), getShortcutInfo(i), getRankingAdjustment(i), @@ -379,7 +379,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { return index % 4 == 0; } - private boolean visuallyInterruptive(int index) { + private boolean isTextChanged(int index) { return index % 4 == 0; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 1ae219db7726..c493639fc6b1 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -3872,6 +3872,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testVisuallyInterruptive_notSeen() throws Exception { + NotificationRecord original = generateNotificationRecord(mTestNotificationChannel); + mService.addNotification(original); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, original.getSbn().getId(), + original.getSbn().getTag(), mUid, 0, + new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setContentTitle("new title").build(), + UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord update = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + mService.addEnqueuedNotification(update); + + NotificationManagerService.PostNotificationRunnable runnable = + mService.new PostNotificationRunnable(update.getKey()); + runnable.run(); + waitForIdle(); + + assertFalse(update.isInterruptive()); + } + + @Test public void testApplyAdjustmentMultiUser() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addNotification(r); 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 65733d7a4129..44cff33d6146 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -77,6 +77,7 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityRecord.State.STARTED; import static com.android.server.wm.ActivityRecord.State.STOPPED; import static com.android.server.wm.ActivityRecord.State.STOPPING; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE; import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE; import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; @@ -1751,6 +1752,11 @@ public class ActivityRecordTests extends WindowTestsBase { anyInt() /* orientation */, anyInt() /* lastRotation */); // Set to visible so the activity can freeze the screen. activity.setVisibility(true); + // Update the display policy to make the screen fully turned on so the freeze is allowed + display.getDisplayPolicy().screenTurnedOn(null); + display.getDisplayPolicy().finishKeyguardDrawn(); + display.getDisplayPolicy().finishWindowsDrawn(); + display.getDisplayPolicy().finishScreenTurningOn(); display.rotateInDifferentOrientationIfNeeded(activity); display.setFixedRotationLaunchingAppUnchecked(activity); @@ -3026,6 +3032,10 @@ public class ActivityRecordTests extends WindowTestsBase { // Because the app is waiting for transition, it should not hide the surface. assertTrue(app.mActivityRecord.isSurfaceShowing()); + + // Ensure onAnimationFinished will callback when the closing animation is finished. + verify(app.mActivityRecord).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), + eq(null)); } private void assertHasStartingWindow(ActivityRecord atoken) { 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 82140f4d965c..5d0e34a80f3f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -43,6 +43,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -893,6 +894,33 @@ public class AppTransitionControllerTest extends WindowTestsBase { } @Test + public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter( + new TestRemoteAnimationRunner(), 10, 1); + setupTaskFragmentRemoteAnimation(organizer, adapter); + + // Create a TaskFragment with embedded activity. + final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity( + createTask(mDisplayContent), organizer); + final ActivityRecord activity = taskFragment.getTopMostActivity(); + activity.allDrawn = true; + // Set wallpaper as visible. + final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, + mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */); + spyOn(mDisplayContent.mWallpaperController); + doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible(); + spyOn(mDisplayContent.mAppTransition); + + // Prepare a transition. + prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment); + + // Should not be overridden when there is wallpaper in the transition. + verify(mDisplayContent.mAppTransition, never()) + .overridePendingAppTransitionRemote(adapter, false /* sync */); + } + + @Test public void testTransitionGoodToGoForTaskFragments() { final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); final Task task = createTask(mDisplayContent); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java index fd562c3f90b2..ebefeaff7f26 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java @@ -75,13 +75,19 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase { @Test public void clipAfterAnim_boundsLayerZBoosted() { + final Task task = mActivity.getTask(); + final ActivityRecord topActivity = createActivityRecord(task); + task.assignChildLayers(mTransaction); + + assertThat(topActivity.getLastLayer()).isGreaterThan(mActivity.getLastLayer()); + mActivity.mNeedsAnimationBoundsLayer = true; mActivity.mNeedsZBoost = true; - mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */, ANIMATION_TYPE_APP_TRANSITION); + verify(mTransaction).setLayer(eq(mActivity.mAnimationBoundsLayer), - intThat(layer -> layer >= ActivityRecord.Z_BOOST_BASE)); + intThat(layer -> layer > topActivity.getLastLayer())); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index a680cba8b266..9d2a69197013 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -257,7 +257,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { verify(mMockRunner).onAnimationCanceled(null /* taskSnapshot */); // Simulate the app transition finishing - mController.mAppTransitionListener.onAppTransitionStartingLocked(false, 0, 0, 0); + mController.mAppTransitionListener.onAppTransitionStartingLocked(false, false, 0, 0, 0); verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false); } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 6407c92ee2aa..7266631161b5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -174,7 +174,9 @@ public class SizeCompatTests extends WindowTestsBase { // The activity should be able to accept negative x position [-150, 100 - 150, 600]. final int dx = bounds.left + bounds.width() / 2; - mTask.setBounds(bounds.left - dx, bounds.top, bounds.right - dx, bounds.bottom); + final int dy = bounds.top + bounds.height() / 2; + mTask.setBounds(bounds.left - dx, bounds.top - dy, bounds.right - dx, bounds.bottom - dy); + // expected:<Rect(-150, 100 - 150, 600)> but was:<Rect(-150, 0 - 150, 500)> assertEquals(mTask.getBounds(), mActivity.getBounds()); final int density = mActivity.getConfiguration().densityDpi; @@ -1850,7 +1852,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(0, 0, 700, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(0, 0, 700, 1400), + /* sizeCompatUnscaled */ new Rect(0, 700, 700, 2100), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(0, 0, 350, 700)); } @@ -1863,7 +1865,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), + /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); } @@ -1878,7 +1880,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), + /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); @@ -1888,7 +1890,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), + /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); } @@ -1901,7 +1903,7 @@ public class SizeCompatTests extends WindowTestsBase { // At launch. /* fixedOrientationLetterbox */ new Rect(2100, 0, 2800, 1400), // After 90 degree rotation. - /* sizeCompatUnscaled */ new Rect(700, 0, 1400, 1400), + /* sizeCompatUnscaled */ new Rect(700, 700, 1400, 2100), // After the display is resized to (700, 1400). /* sizeCompatScaled */ new Rect(1050, 0, 1400, 700)); } @@ -2093,7 +2095,7 @@ public class SizeCompatTests extends WindowTestsBase { assertTrue(mActivity.inSizeCompatMode()); // Activity is in size compat mode but not scaled. - assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds()); + assertEquals(new Rect(0, 1050, 1400, 1750), mActivity.getBounds()); } private static WindowState addWindowToActivity(ActivityRecord activity) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java index e528a4a23e45..168c250a8c93 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java @@ -1321,6 +1321,50 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { } @Test + public void testDefaultFreeformSizeRespectsMinAspectRatio() { + final TestDisplayContent freeformDisplay = createNewDisplayContent( + WINDOWING_MODE_FREEFORM); + + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(freeformDisplay.mDisplayId); + + mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP; + mActivity.info.setMinAspectRatio(5f); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder() + .setOptions(options).calculate()); + + final float aspectRatio = + (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height()) + / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height()); + assertTrue("Bounds aspect ratio should be at least 5.0, but was " + aspectRatio, + aspectRatio >= 5f); + } + + @Test + public void testDefaultFreeformSizeRespectsMaxAspectRatio() { + final TestDisplayContent freeformDisplay = createNewDisplayContent( + WINDOWING_MODE_FREEFORM); + + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(freeformDisplay.mDisplayId); + + mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP; + mActivity.info.setMaxAspectRatio(1.5f); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder() + .setOptions(options).calculate()); + + final float aspectRatio = + (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height()) + / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height()); + assertTrue("Bounds aspect ratio should be at most 1.5, but was " + aspectRatio, + aspectRatio <= 1.5f); + } + + @Test public void testCascadesToSourceSizeForFreeform() { final TestDisplayContent freeformDisplay = createNewDisplayContent( WINDOWING_MODE_FREEFORM); @@ -1348,6 +1392,72 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase { } @Test + public void testCascadesToSourceSizeForFreeformRespectingMinAspectRatio() { + final TestDisplayContent freeformDisplay = createNewDisplayContent( + WINDOWING_MODE_FREEFORM); + + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(freeformDisplay.mDisplayId); + + final ActivityRecord source = createSourceActivity(freeformDisplay); + source.setBounds(0, 0, 412, 732); + + mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP; + mActivity.info.setMinAspectRatio(5f); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder().setSource(source).setOptions(options).calculate()); + + final Rect displayBounds = freeformDisplay.getBounds(); + assertTrue("Left bounds should be larger than 0.", mResult.mBounds.left > 0); + assertTrue("Top bounds should be larger than 0.", mResult.mBounds.top > 0); + assertTrue("Bounds should be centered at somewhere in the left half, but it's " + + "centerX is " + mResult.mBounds.centerX(), + mResult.mBounds.centerX() < displayBounds.centerX()); + assertTrue("Bounds should be centered at somewhere in the top half, but it's " + + "centerY is " + mResult.mBounds.centerY(), + mResult.mBounds.centerY() < displayBounds.centerY()); + final float aspectRatio = + (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height()) + / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height()); + assertTrue("Bounds aspect ratio should be at least 5.0, but was " + aspectRatio, + aspectRatio >= 5f); + } + + @Test + public void testCascadesToSourceSizeForFreeformRespectingMaxAspectRatio() { + final TestDisplayContent freeformDisplay = createNewDisplayContent( + WINDOWING_MODE_FREEFORM); + + final ActivityOptions options = ActivityOptions.makeBasic(); + options.setLaunchDisplayId(freeformDisplay.mDisplayId); + + final ActivityRecord source = createSourceActivity(freeformDisplay); + source.setBounds(0, 0, 412, 732); + + mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP; + mActivity.info.setMaxAspectRatio(1.5f); + + assertEquals(RESULT_CONTINUE, + new CalculateRequestBuilder().setSource(source).setOptions(options).calculate()); + + final Rect displayBounds = freeformDisplay.getBounds(); + assertTrue("Left bounds should be larger than 0.", mResult.mBounds.left > 0); + assertTrue("Top bounds should be larger than 0.", mResult.mBounds.top > 0); + assertTrue("Bounds should be centered at somewhere in the left half, but it's " + + "centerX is " + mResult.mBounds.centerX(), + mResult.mBounds.centerX() < displayBounds.centerX()); + assertTrue("Bounds should be centered at somewhere in the top half, but it's " + + "centerY is " + mResult.mBounds.centerY(), + mResult.mBounds.centerY() < displayBounds.centerY()); + final float aspectRatio = + (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height()) + / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height()); + assertTrue("Bounds aspect ratio should be at most 1.5, but was " + aspectRatio, + aspectRatio <= 1.5f); + } + + @Test public void testAdjustBoundsToFitDisplay_TopLeftOutOfDisplay() { final TestDisplayContent freeformDisplay = createNewDisplayContent( WINDOWING_MODE_FREEFORM); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index acadb74d333f..9001578cf37a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -128,10 +128,6 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public void setKeyguardCandidateLw(WindowState win) { - } - - @Override public Animation createHiddenByKeyguardExit(boolean onWallpaper, boolean goingToNotificationShade, boolean subtleAnimation) { return null; @@ -368,7 +364,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public int applyKeyguardOcclusionChange() { + public int applyKeyguardOcclusionChange(boolean keyguardOccludingStarted) { return 0; } diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 6d60bcf1fce6..a1c24c26af35 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -34,8 +34,11 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.os.IBinder; import android.platform.test.annotations.Presubmit; @@ -448,7 +451,8 @@ public class TransitionTests extends WindowTestsBase { @Test public void testIntermediateVisibility() { - final TransitionController controller = new TransitionController(mAtm); + final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class); + final TransitionController controller = new TransitionController(mAtm, snapshotController); final ITransitionPlayer player = new ITransitionPlayer.Default(); controller.registerTransitionPlayer(player); ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); @@ -511,6 +515,71 @@ public class TransitionTests extends WindowTestsBase { assertTrue(activity2.isVisible()); } + @Test + public void testTransientLaunch() { + final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class); + final TransitionController controller = new TransitionController(mAtm, snapshotController); + final ITransitionPlayer player = new ITransitionPlayer.Default(); + controller.registerTransitionPlayer(player); + ITaskOrganizer mockOrg = mock(ITaskOrganizer.class); + final Transition openTransition = controller.createTransition(TRANSIT_OPEN); + + // Start out with task2 visible and set up a transition that closes task2 and opens task1 + final Task task1 = createTask(mDisplayContent); + task1.mTaskOrganizer = mockOrg; + final ActivityRecord activity1 = createActivityRecord(task1); + activity1.mVisibleRequested = false; + activity1.setVisible(false); + final Task task2 = createTask(mDisplayContent); + task2.mTaskOrganizer = mockOrg; + final ActivityRecord activity2 = createActivityRecord(task2); + activity2.mVisibleRequested = true; + activity2.setVisible(true); + + openTransition.collectExistenceChange(task1); + openTransition.collectExistenceChange(activity1); + openTransition.collectExistenceChange(task2); + openTransition.collectExistenceChange(activity2); + + activity1.mVisibleRequested = true; + activity1.setVisible(true); + activity2.mVisibleRequested = false; + + // Using abort to force-finish the sync (since we can't wait for drawing in unit test). + // We didn't call abort on the transition itself, so it will still run onTransactionReady + // normally. + mWm.mSyncEngine.abort(openTransition.getSyncId()); + + verify(snapshotController, times(1)).recordTaskSnapshot(eq(task2), eq(false)); + + openTransition.finishTransition(); + + // We are now going to simulate closing task1 to return back to (open) task2. + final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE); + + closeTransition.collectExistenceChange(task1); + closeTransition.collectExistenceChange(activity1); + closeTransition.collectExistenceChange(task2); + closeTransition.collectExistenceChange(activity2); + closeTransition.setTransientLaunch(activity2); + + activity1.mVisibleRequested = false; + activity2.mVisibleRequested = true; + + // Using abort to force-finish the sync (since we obviously can't wait for drawing). + // We didn't call abort on the actual transition, so it will still run onTransactionReady + // normally. + mWm.mSyncEngine.abort(closeTransition.getSyncId()); + + // Make sure we haven't called recordSnapshot (since we are transient, it shouldn't be + // called until finish). + verify(snapshotController, times(0)).recordTaskSnapshot(eq(task1), eq(false)); + + closeTransition.finishTransition(); + + verify(snapshotController, times(1)).recordTaskSnapshot(eq(task1), eq(false)); + } + /** Fill the change map with all the parents of top. Change maps are usually fully populated */ private static void fillChangeMap(ArrayMap<WindowContainer, Transition.ChangeInfo> changes, WindowContainer top) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 68053eb324b2..bbeb980353ed 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -25,6 +25,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE; import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; @@ -52,6 +53,7 @@ import static com.android.server.wm.WindowContainer.POSITION_TOP; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -1106,6 +1108,71 @@ public class WindowContainerTests extends WindowTestsBase { verify(surfaceAnimator, never()).setRelativeLayer(any(), any(), anyInt()); } + @Test + public void testStartChangeTransitionWhenPreviousIsNotFinished() { + final WindowContainer container = createTaskFragmentWithParentTask( + createTask(mDisplayContent), false); + container.mSurfaceControl = mock(SurfaceControl.class); + final SurfaceAnimator surfaceAnimator = container.mSurfaceAnimator; + final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer; + final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + spyOn(container); + spyOn(surfaceAnimator); + spyOn(surfaceFreezer); + doReturn(t).when(container).getPendingTransaction(); + doReturn(t).when(container).getSyncTransaction(); + + // Leash and snapshot created for change transition. + container.initializeChangeTransition(new Rect(0, 0, 1000, 2000)); + // Can't really take a snapshot, manually set one. + surfaceFreezer.mSnapshot = mock(SurfaceFreezer.Snapshot.class); + + assertNotNull(surfaceFreezer.mLeash); + assertEquals(surfaceFreezer.mLeash, container.getAnimationLeash()); + + // Start animation: surfaceAnimator take over the leash and snapshot from surfaceFreezer. + container.applyAnimationUnchecked(null /* lp */, true /* enter */, + TRANSIT_OLD_TASK_FRAGMENT_CHANGE, false /* isVoiceInteraction */, + null /* sources */); + + assertNull(surfaceFreezer.mLeash); + assertNull(surfaceFreezer.mSnapshot); + assertNotNull(surfaceAnimator.mLeash); + assertNotNull(surfaceAnimator.mSnapshot); + final SurfaceControl prevLeash = surfaceAnimator.mLeash; + final SurfaceFreezer.Snapshot prevSnapshot = surfaceAnimator.mSnapshot; + + // Prepare another change transition. + container.initializeChangeTransition(new Rect(0, 0, 1000, 2000)); + surfaceFreezer.mSnapshot = mock(SurfaceFreezer.Snapshot.class); + + assertNotNull(surfaceFreezer.mLeash); + assertEquals(surfaceFreezer.mLeash, container.getAnimationLeash()); + assertNotEquals(prevLeash, container.getAnimationLeash()); + + // Start another animation before the previous one is finished, it should reset the previous + // one, but not change the current one. + container.applyAnimationUnchecked(null /* lp */, true /* enter */, + TRANSIT_OLD_TASK_FRAGMENT_CHANGE, false /* isVoiceInteraction */, + null /* sources */); + + verify(container, never()).onAnimationLeashLost(any()); + verify(surfaceFreezer, never()).unfreeze(any()); + assertNotNull(surfaceAnimator.mLeash); + assertNotNull(surfaceAnimator.mSnapshot); + assertEquals(surfaceAnimator.mLeash, container.getAnimationLeash()); + assertNotEquals(prevLeash, surfaceAnimator.mLeash); + assertNotEquals(prevSnapshot, surfaceAnimator.mSnapshot); + + // Clean up after animation finished. + surfaceAnimator.mInnerAnimationFinishedCallback.onAnimationFinished( + ANIMATION_TYPE_APP_TRANSITION, surfaceAnimator.getAnimation()); + + verify(container).onAnimationLeashLost(any()); + assertNull(surfaceAnimator.mLeash); + assertNull(surfaceAnimator.mSnapshot); + } + /* Used so we can gain access to some protected members of the {@link WindowContainer} class */ private static class TestWindowContainer extends WindowContainer<TestWindowContainer> { private final int mLayer; diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index 2cc19438b927..64cb790d324b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -17,22 +17,29 @@ @file:JvmName("CommonAssertions") package com.android.server.wm.flicker -import android.content.ComponentName import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName -val LAUNCHER_COMPONENT = ComponentName("com.google.android.apps.nexuslauncher", +val LAUNCHER_COMPONENT = FlickerComponentName("com.google.android.apps.nexuslauncher", "com.google.android.apps.nexuslauncher.NexusLauncherActivity") +/** + * Checks that [FlickerComponentName.STATUS_BAR] window is visible and above the app windows in + * all WM trace entries + */ fun FlickerTestParameter.statusBarWindowIsVisible() { assertWm { - this.isAboveAppWindowVisible(WindowManagerStateHelper.STATUS_BAR_COMPONENT) + this.isAboveAppWindowVisible(FlickerComponentName.STATUS_BAR) } } +/** + * Checks that [FlickerComponentName.NAV_BAR] window is visible and above the app windows in + * all WM trace entries + */ fun FlickerTestParameter.navBarWindowIsVisible() { assertWm { - this.isAboveAppWindowVisible(WindowManagerStateHelper.NAV_BAR_COMPONENT) + this.isAboveAppWindowVisible(FlickerComponentName.NAV_BAR) } } @@ -69,53 +76,59 @@ fun FlickerTestParameter.entireScreenCovered(allStates: Boolean = true) { } } +/** + * Checks that [FlickerComponentName.NAV_BAR] layer is visible at the start and end of the SF + * trace + */ fun FlickerTestParameter.navBarLayerIsVisible() { assertLayersStart { - this.isVisible(WindowManagerStateHelper.NAV_BAR_COMPONENT) + this.isVisible(FlickerComponentName.NAV_BAR) } assertLayersEnd { - this.isVisible(WindowManagerStateHelper.NAV_BAR_COMPONENT) + this.isVisible(FlickerComponentName.NAV_BAR) } } +/** + * Checks that [FlickerComponentName.STATUS_BAR] layer is visible at the start and end of the SF + * trace + */ fun FlickerTestParameter.statusBarLayerIsVisible() { assertLayersStart { - this.isVisible(WindowManagerStateHelper.STATUS_BAR_COMPONENT) + this.isVisible(FlickerComponentName.STATUS_BAR) } assertLayersEnd { - this.isVisible(WindowManagerStateHelper.STATUS_BAR_COMPONENT) + this.isVisible(FlickerComponentName.STATUS_BAR) } } -@JvmOverloads -fun FlickerTestParameter.navBarLayerRotatesAndScales( - beginRotation: Int, - endRotation: Int = beginRotation -) { - val startingPos = WindowUtils.getNavigationBarPosition(beginRotation) - val endingPos = WindowUtils.getNavigationBarPosition(endRotation) - +fun FlickerTestParameter.navBarLayerRotatesAndScales() { assertLayersStart { - this.visibleRegion(WindowManagerStateHelper.NAV_BAR_COMPONENT).coversExactly(startingPos) + val display = this.entry.displays.minByOrNull { it.id } + ?: throw RuntimeException("There is no display!") + this.visibleRegion(FlickerComponentName.NAV_BAR) + .coversExactly(WindowUtils.getNavigationBarPosition(display)) } assertLayersEnd { - this.visibleRegion(WindowManagerStateHelper.NAV_BAR_COMPONENT).coversExactly(endingPos) + val display = this.entry.displays.minByOrNull { it.id } + ?: throw RuntimeException("There is no display!") + this.visibleRegion(FlickerComponentName.NAV_BAR) + .coversExactly(WindowUtils.getNavigationBarPosition(display)) } } -@JvmOverloads -fun FlickerTestParameter.statusBarLayerRotatesScales( - beginRotation: Int, - endRotation: Int = beginRotation -) { - val startingPos = WindowUtils.getStatusBarPosition(beginRotation) - val endingPos = WindowUtils.getStatusBarPosition(endRotation) - +fun FlickerTestParameter.statusBarLayerRotatesScales() { assertLayersStart { - this.visibleRegion(WindowManagerStateHelper.STATUS_BAR_COMPONENT).coversExactly(startingPos) + val display = this.entry.displays.minByOrNull { it.id } + ?: throw RuntimeException("There is no display!") + this.visibleRegion(FlickerComponentName.STATUS_BAR) + .coversExactly(WindowUtils.getStatusBarPosition(display)) } assertLayersEnd { - this.visibleRegion(WindowManagerStateHelper.STATUS_BAR_COMPONENT).coversExactly(endingPos) + val display = this.entry.displays.minByOrNull { it.id } + ?: throw RuntimeException("There is no display!") + this.visibleRegion(FlickerComponentName.STATUS_BAR) + .coversExactly(WindowUtils.getStatusBarPosition(display)) } } @@ -132,15 +145,15 @@ fun FlickerTestParameter.statusBarLayerRotatesScales( * (useful mostly for app launch) */ fun FlickerTestParameter.replacesLayer( - originalLayer: ComponentName, - newLayer: ComponentName, + originalLayer: FlickerComponentName, + newLayer: FlickerComponentName, ignoreSnapshot: Boolean = false ) { assertLayers { val assertion = this.isVisible(originalLayer) if (ignoreSnapshot) { assertion.then() - .isVisible(WindowManagerStateHelper.SNAPSHOT_COMPONENT, isOptional = true) + .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) } assertion.then().isVisible(newLayer) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index 90c851d6e266..9f26c31a6d63 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt @@ -17,13 +17,12 @@ package com.android.server.wm.flicker.close -import android.platform.test.annotations.Postsubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import org.junit.FixMethodOrder import org.junit.Test @@ -33,13 +32,38 @@ import org.junit.runners.Parameterized /** * Test app closes by pressing back button + * * To run this test: `atest FlickerTests:CloseAppBackButtonTest` + * + * Actions: + * Make sure no apps are running on the device + * Launch an app [testApp] and wait animation to complete + * Press back button + * + * To run only the presubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * + * To run only the postsubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * + * To run only the flaky assertions add: `-- + * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [CloseAppTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 +@Group4 class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) { override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { @@ -50,14 +74,18 @@ class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio } } + /** {@inheritDoc} */ @FlakyTest @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @Postsubmit - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): List<FlickerTestParameter> { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index e8391ed9cfa1..795766fccfbd 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -16,13 +16,12 @@ package com.android.server.wm.flicker.close -import android.platform.test.annotations.Postsubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import org.junit.FixMethodOrder import org.junit.Test @@ -31,14 +30,39 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test app closes by pressing home button. + * Test app closes by pressing home button + * * To run this test: `atest FlickerTests:CloseAppHomeButtonTest` + * + * Actions: + * Make sure no apps are running on the device + * Launch an app [testApp] and wait animation to complete + * Press home button + * + * To run only the presubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * + * To run only the postsubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * + * To run only the flaky assertions add: `-- + * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [CloseAppTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 +@Group4 class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) { override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { @@ -49,14 +73,18 @@ class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio } } + /** {@inheritDoc} */ @FlakyTest @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @Postsubmit - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt index 1efb6daae31b..511fc26fdb38 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt @@ -18,7 +18,6 @@ package com.android.server.wm.flicker.close import android.app.Instrumentation import android.platform.test.annotations.Presubmit -import android.view.Surface import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter @@ -62,6 +61,10 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Entry point for the test runner. It will use this method to initialize and cache + * flicker executions + */ @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { @@ -69,42 +72,60 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Checks that the navigation bar window is visible during the whole transition + */ @Presubmit @Test open fun navBarWindowIsVisible() { testSpec.navBarWindowIsVisible() } + /** + * Checks that the status bar window is visible during the whole transition + */ @Presubmit @Test open fun statusBarWindowIsVisible() { testSpec.statusBarWindowIsVisible() } + /** + * Checks that the navigation bar layer is visible during the whole transition + */ @Presubmit @Test open fun navBarLayerIsVisible() { testSpec.navBarLayerIsVisible() } + /** + * Checks that the status bar layer is visible during the whole transition + */ @Presubmit @Test open fun statusBarLayerIsVisible() { testSpec.statusBarLayerIsVisible() } + /** + * Checks the position of the navigation bar at the start and end of the transition + */ @Presubmit @Test - open fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } + open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() + /** + * Checks the position of the status bar at the start and end of the transition + */ @Presubmit @Test - open fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - } + open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + /** + * Checks that all windows that are visible on the trace, are visible for at least 2 + * consecutive entries. + */ @Presubmit @Test open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { @@ -113,6 +134,10 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Checks that all layers that are visible on the trace, are visible for at least 2 + * consecutive entries. + */ @Presubmit @Test open fun visibleLayersShownMoreThanOneConsecutiveEntry() { @@ -121,10 +146,17 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Checks that all parts of the screen are covered during the transition + */ @Presubmit @Test open fun entireScreenCovered() = testSpec.entireScreenCovered() + /** + * Checks that [testApp] is the top visible app window at the start of the transition and + * that it is replaced by [LAUNCHER_COMPONENT] during the transition + */ @Presubmit @Test open fun launcherReplacesAppWindowAsTopWindow() { @@ -135,19 +167,26 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Checks that [LAUNCHER_COMPONENT] is invisible at the start of the transition and that + * it becomes visible during the transition + */ @Presubmit @Test open fun launcherWindowBecomesVisible() { testSpec.assertWm { - this.isAppWindowInvisible(LAUNCHER_COMPONENT) + this.isAppWindowNotOnTop(LAUNCHER_COMPONENT) .then() .isAppWindowOnTop(LAUNCHER_COMPONENT) } } + /** + * Checks that [LAUNCHER_COMPONENT] layer becomes visible when [testApp] becomes invisible + */ @Presubmit @Test open fun launcherLayerReplacesApp() { testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT) } -} +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt index fad25b4fa0b9..75900df978df 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt @@ -14,6 +14,7 @@ * limitations under the License. */ +@file:JvmName("FlickerExtensions") package com.android.server.wm.flicker.helpers import com.android.server.wm.flicker.Flicker diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt index bd7c1855fea9..0b1748a6bda4 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt @@ -17,9 +17,10 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import androidx.test.uiautomator.UiDevice import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper class ImeAppAutoFocusHelper @JvmOverloads constructor( @@ -27,7 +28,8 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor( private val rotation: Int, private val imePackageName: String = IME_PACKAGE, launcherName: String = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME, - component: ComponentName = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME + component: FlickerComponentName = + ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() ) : ImeAppHelper(instr, launcherName, component) { override fun openIME( device: UiDevice, diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt index d224af97462e..1c2164a70a55 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt @@ -17,19 +17,21 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper open class ImeAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.IME_ACTIVITY_LAUNCHER_NAME, - component: ComponentName = ActivityOptions.IME_ACTIVITY_COMPONENT_NAME, + component: FlickerComponentName = + ActivityOptions.IME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) .launcherStrategy diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt new file mode 100644 index 000000000000..be68704fc32d --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt @@ -0,0 +1,52 @@ +/* + * 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.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper + +class NewTasksAppHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy +) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { + fun openNewTask(device: UiDevice, wmHelper: WindowManagerStateHelper) { + val button = device.wait( + Until.findObject(By.res(getPackage(), "launch_new_task")), + FIND_TIMEOUT) + + require(button != null) { + "Button not found, this usually happens when the device " + + "was left in an unknown state (e.g. in split screen)" + } + button.click() + wmHelper.waitForAppTransitionIdle() + wmHelper.waitForFullScreenApp(component) + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt index 3074e28b43fa..f7ca5ce1c001 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt @@ -17,15 +17,17 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent class NonResizeableAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME, - component: ComponentName = ActivityOptions.NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME, + component: FlickerComponentName = + ActivityOptions.NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) .launcherStrategy diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt index 02be3cf0a8a3..7bab981ce231 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt @@ -17,15 +17,17 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent class SeamlessRotationAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.SEAMLESS_ACTIVITY_LAUNCHER_NAME, - component: ComponentName = ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME, + component: FlickerComponentName = + ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) .launcherStrategy diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt index d7cbaaee2627..f6a8817e5b08 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt @@ -17,15 +17,17 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent class SimpleAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.SIMPLE_ACTIVITY_LAUNCHER_NAME, - component: ComponentName = ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME, + component: FlickerComponentName = + ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) .launcherStrategy diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt index 19fefb93b487..59e8dc826007 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt @@ -17,19 +17,21 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.content.ComponentName import android.support.test.launcherhelper.ILauncherStrategy import android.support.test.launcherhelper.LauncherStrategyFactory import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper class TwoActivitiesAppHelper @JvmOverloads constructor( instr: Instrumentation, launcherName: String = ActivityOptions.BUTTON_ACTIVITY_LAUNCHER_NAME, - component: ComponentName = ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME, + component: FlickerComponentName = + ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), launcherStrategy: ILauncherStrategy = LauncherStrategyFactory .getInstance(instr) .launcherStrategy diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt index d17e77d74c1c..5e21aff94769 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt @@ -37,7 +37,7 @@ import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -125,7 +125,7 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter @Test fun imeLayerVisibleStart() { testSpec.assertLayersStart { - this.isVisible(WindowManagerStateHelper.IME_COMPONENT) + this.isVisible(FlickerComponentName.IME) } } @@ -133,7 +133,7 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter @Test fun imeLayerInvisibleEnd() { testSpec.assertLayersEnd { - this.isInvisible(WindowManagerStateHelper.IME_COMPONENT) + this.isInvisible(FlickerComponentName.IME) } } @@ -151,15 +151,11 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter @Presubmit @Test - fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) - } + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation) - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index 6f0f55aa4888..0582685f2c54 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -38,7 +38,7 @@ import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -110,7 +110,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete testSpec.assertWm { this.isAppWindowOnTop(testApp.component) .then() - .appWindowNotOnTop(testApp.component) + .isAppWindowNotOnTop(testApp.component) } } @@ -122,7 +122,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @Test fun imeLayerVisibleStart() { testSpec.assertLayersStart { - this.isVisible(WindowManagerStateHelper.IME_COMPONENT) + this.isVisible(FlickerComponentName.IME) } } @@ -130,7 +130,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @Test fun imeLayerInvisibleEnd() { testSpec.assertLayersEnd { - this.isInvisible(WindowManagerStateHelper.IME_COMPONENT) + this.isInvisible(FlickerComponentName.IME) } } @@ -150,15 +150,11 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @Presubmit @Test - fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test @@ -173,8 +169,8 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete fun visibleLayersShownMoreThanOneConsecutiveEntry() { testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry(listOf( - WindowManagerStateHelper.IME_COMPONENT, - WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT)) + FlickerComponentName.IME, + FlickerComponentName.SPLASH_SCREEN)) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt index 6751439709bf..91b3d3dae3cd 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt @@ -32,11 +32,10 @@ import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.Assume -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -91,9 +90,9 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { fun visibleWindowsShownMoreThanOneConsecutiveEntry() { testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf( - WindowManagerStateHelper.IME_COMPONENT, - WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT)) + FlickerComponentName.IME, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT)) } } @@ -121,21 +120,19 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { @Test fun navBarLayerRotatesAndScales() { Assume.assumeFalse(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) + testSpec.navBarLayerRotatesAndScales() } @FlakyTest @Test fun navBarLayerRotatesAndScales_Flaky() { Assume.assumeTrue(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) + testSpec.navBarLayerRotatesAndScales() } @Presubmit @Test - fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation) - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test @@ -165,4 +162,4 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { .getConfigNonRotationTests(repetitions = 5) } } -} +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt index 8aaf9251018f..b589969dee14 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -34,10 +34,9 @@ import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -95,9 +94,9 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { fun visibleWindowsShownMoreThanOneConsecutiveEntry() { testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf( - WindowManagerStateHelper.IME_COMPONENT, - WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT)) + FlickerComponentName.IME, + FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT)) } } @@ -143,22 +142,19 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun navBarLayerRotatesAndScales() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test fun visibleLayersShownMoreThanOneConsecutiveEntry() { testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry(listOf( - WindowManagerStateHelper.IME_COMPONENT, - WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT)) + FlickerComponentName.IME, + FlickerComponentName.SPLASH_SCREEN)) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt index 7659d9471e2f..ba78e25580ec 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt @@ -18,52 +18,52 @@ package com.android.server.wm.flicker.ime import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName fun FlickerTestParameter.imeLayerBecomesVisible() { assertLayers { - this.isInvisible(WindowManagerStateHelper.IME_COMPONENT) + this.isInvisible(FlickerComponentName.IME) .then() - .isVisible(WindowManagerStateHelper.IME_COMPONENT) + .isVisible(FlickerComponentName.IME) } } fun FlickerTestParameter.imeLayerBecomesInvisible() { assertLayers { - this.isVisible(WindowManagerStateHelper.IME_COMPONENT) + this.isVisible(FlickerComponentName.IME) .then() - .isInvisible(WindowManagerStateHelper.IME_COMPONENT) + .isInvisible(FlickerComponentName.IME) } } fun FlickerTestParameter.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) { if (rotatesScreen) { assertWm { - this.isNonAppWindowVisible(WindowManagerStateHelper.IME_COMPONENT) + this.isNonAppWindowVisible(FlickerComponentName.IME) .then() - .isNonAppWindowInvisible(WindowManagerStateHelper.IME_COMPONENT) + .isNonAppWindowInvisible(FlickerComponentName.IME) .then() - .isNonAppWindowVisible(WindowManagerStateHelper.IME_COMPONENT) + .isNonAppWindowVisible(FlickerComponentName.IME) } } else { assertWm { - this.isNonAppWindowVisible(WindowManagerStateHelper.IME_COMPONENT) + this.isNonAppWindowVisible(FlickerComponentName.IME) } } } fun FlickerTestParameter.imeWindowBecomesVisible() { assertWm { - this.isNonAppWindowInvisible(WindowManagerStateHelper.IME_COMPONENT) + this.isNonAppWindowInvisible(FlickerComponentName.IME) .then() - .isNonAppWindowVisible(WindowManagerStateHelper.IME_COMPONENT) + .isNonAppWindowVisible(FlickerComponentName.IME) } } fun FlickerTestParameter.imeWindowBecomesInvisible() { assertWm { - this.isNonAppWindowVisible(WindowManagerStateHelper.IME_COMPONENT) + this.isNonAppWindowVisible(FlickerComponentName.IME) .then() - .isNonAppWindowInvisible(WindowManagerStateHelper.IME_COMPONENT) + .isNonAppWindowInvisible(FlickerComponentName.IME) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt new file mode 100644 index 000000000000..a9568b325af2 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt @@ -0,0 +1,153 @@ +/* + * 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.flicker.ime + +import android.app.Instrumentation +import android.platform.test.annotations.Presubmit +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper +import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.flicker.startRotation +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Launch an app that automatically displays the IME + * + * To run this test: `atest FlickerTests:LaunchAppShowImeOnStartTest` + * + * Actions: + * Make sure no apps are running on the device + * Launch an app [testApp] that automatically displays IME and wait animation to complete + * + * To run only the presubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * + * To run only the postsubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * + * To run only the flaky assertions add: `-- + * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [CloseAppTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + eachRun { + this.setRotation(testSpec.config.startRotation) + } + } + teardown { + eachRun { + testApp.exit() + } + } + transitions { + testApp.launchViaIntent(wmHelper) + wmHelper.waitImeShown() + } + } + } + + /** + * Checks that [FlickerComponentName.IME] window becomes visible during the transition + */ + @Presubmit + @Test + fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible() + + /** + * Checks that [FlickerComponentName.IME] layer becomes visible during the transition + */ + @Presubmit + @Test + fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible() + + /** + * Checks that [FlickerComponentName.IME] layer is invisible at the start of the transition + */ + @Presubmit + @Test + fun imeLayerNotExistsStart() { + testSpec.assertLayersStart { + this.isInvisible(FlickerComponentName.IME) + } + } + + /** + * Checks that [FlickerComponentName.IME] layer is visible at the end of the transition + */ + @Presubmit + @Test + fun imeLayerExistsEnd() { + testSpec.assertLayersEnd { + this.isVisible(FlickerComponentName.IME) + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 5, + supportedRotations = listOf(Surface.ROTATION_0), + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt index 665204bc9e1e..7bf0186cd857 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt @@ -20,6 +20,7 @@ import android.app.Instrumentation import android.platform.test.annotations.Presubmit import android.view.Surface import android.view.WindowManagerPolicyConstants +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider @@ -33,7 +34,6 @@ import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible @@ -124,15 +124,11 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) - } + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation) - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test @@ -142,7 +138,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { } } - @Presubmit + @FlakyTest @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() { testSpec.assertWm { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt index b37c40447934..f6febe9e2234 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt @@ -17,7 +17,6 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation -import android.content.ComponentName import android.os.SystemProperties import android.platform.test.annotations.Presubmit import android.view.Surface @@ -39,11 +38,10 @@ import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.startRotation -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -104,12 +102,12 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - val component = ComponentName("", "RecentTaskScreenshotSurface") + val component = FlickerComponentName("", "RecentTaskScreenshotSurface") testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry( - ignoreWindows = listOf(WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT, - component) + ignoreWindows = listOf(FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT, + component) ) } } @@ -138,9 +136,9 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { // Since we log 1x per frame, sometimes the activity visibility and the app visibility // are updated together, sometimes not, thus ignore activity check at the start testSpec.assertWm { - this.isAppWindowVisible(testApp.component, ignoreActivity = true) + this.isAppWindowVisible(testApp.component) .then() - .isAppWindowInvisible(testApp.component, ignoreActivity = true) + .isAppWindowInvisible(testApp.component) .then() .isAppWindowVisible(testApp.component) } @@ -155,7 +153,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { // and the app visibility are updated together, sometimes not, thus ignore activity // check at the start testSpec.assertWm { - this.isAppWindowVisible(testApp.component, ignoreActivity = true) + this.isAppWindowVisible(testApp.component) } } @@ -177,11 +175,11 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { fun imeLayerIsBecomesVisibleLegacy() { Assume.assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { - this.isVisible(WindowManagerStateHelper.IME_COMPONENT) + this.isVisible(FlickerComponentName.IME) .then() - .isInvisible(WindowManagerStateHelper.IME_COMPONENT) + .isInvisible(FlickerComponentName.IME) .then() - .isVisible(WindowManagerStateHelper.IME_COMPONENT) + .isVisible(FlickerComponentName.IME) } } @@ -190,7 +188,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { fun imeLayerIsBecomesVisible() { Assume.assumeTrue(isShellTransitionsEnabled) testSpec.assertLayers { - this.isVisible(WindowManagerStateHelper.IME_COMPONENT) + this.isVisible(FlickerComponentName.IME) } } @@ -200,7 +198,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { testSpec.assertLayers { this.isVisible(LAUNCHER_COMPONENT) .then() - .isVisible(WindowManagerStateHelper.SNAPSHOT_COMPONENT, isOptional = true) + .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) .then() .isVisible(testApp.component) } @@ -208,26 +206,22 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation) - } + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() @Presubmit @Test - fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation) - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test fun visibleLayersShownMoreThanOneConsecutiveEntry() { // depends on how much of the animation transactions are sent to SF at once // sometimes this layer appears for 2-3 frames, sometimes for only 1 - val recentTaskComponent = ComponentName("", "RecentTaskScreenshotSurface") + val recentTaskComponent = FlickerComponentName("", "RecentTaskScreenshotSurface") testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry( - listOf(WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT, recentTaskComponent) + listOf(FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT, recentTaskComponent) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt index f9dd88e8cb29..4c506b0fea4d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt @@ -17,7 +17,6 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation -import android.content.ComponentName import android.platform.test.annotations.Presubmit import android.view.Surface import android.view.WindowManagerPolicyConstants @@ -28,7 +27,7 @@ import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.WindowUtils @@ -36,7 +35,7 @@ import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test @@ -52,7 +51,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 +@Group4 @Presubmit class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() @@ -107,51 +106,52 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame @Test fun imeAppWindowVisibility() { - val component = ComponentName(imeTestApp.`package`, "") testSpec.assertWm { - this.isAppWindowOnTop(component) - .then() - .isAppWindowVisible(component, ignoreActivity = true) + isAppWindowVisible(imeTestApp.component) + .then() + .isAppWindowVisible(testApp.component) + .then() + .isAppWindowVisible(imeTestApp.component) } } @Test fun navBarLayerIsVisibleAroundSwitching() { testSpec.assertLayersStart { - isVisible(WindowManagerStateHelper.NAV_BAR_COMPONENT) + isVisible(FlickerComponentName.NAV_BAR) } testSpec.assertLayersEnd { - isVisible(WindowManagerStateHelper.NAV_BAR_COMPONENT) + isVisible(FlickerComponentName.NAV_BAR) } } @Test fun statusBarLayerIsVisibleAroundSwitching() { testSpec.assertLayersStart { - isVisible(WindowManagerStateHelper.STATUS_BAR_COMPONENT) + isVisible(FlickerComponentName.STATUS_BAR) } testSpec.assertLayersEnd { - isVisible(WindowManagerStateHelper.STATUS_BAR_COMPONENT) + isVisible(FlickerComponentName.STATUS_BAR) } } @Test fun imeLayerIsVisibleWhenSwitchingToImeApp() { testSpec.assertLayersStart { - isVisible(WindowManagerStateHelper.IME_COMPONENT) + isVisible(FlickerComponentName.IME) } testSpec.assertLayersTag(TAG_IME_VISIBLE) { - isVisible(WindowManagerStateHelper.IME_COMPONENT) + isVisible(FlickerComponentName.IME) } testSpec.assertLayersEnd { - isVisible(WindowManagerStateHelper.IME_COMPONENT) + isVisible(FlickerComponentName.IME) } } @Test fun imeLayerIsInvisibleWhenSwitchingToTestApp() { testSpec.assertLayersTag(TAG_IME_INVISIBLE) { - isInvisible(WindowManagerStateHelper.IME_COMPONENT) + isInvisible(FlickerComponentName.IME) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt index 42c252e70eea..f74a7718461f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt @@ -27,10 +27,11 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.repetitions -import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.parser.toFlickerComponent import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -39,17 +40,33 @@ import org.junit.runners.Parameterized /** * Test the back and forward transition between 2 activities. + * * To run this test: `atest FlickerTests:ActivitiesTransitionTest` + * + * Actions: + * Launch an app + * Launch a secondary activity within the app + * Close the secondary activity back to the initial one + * + * Notes: + * 1. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 +@Group4 class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) { val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation) + /** + * Entry point for the test runner. It will use this method to initialize and cache + * flicker executions + */ @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { @@ -75,30 +92,52 @@ class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) { } } + /** + * Checks that the [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] activity is visible at + * the start of the transition, that + * [ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME] becomes visible during the + * transition, and that [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] is again visible + * at the end + */ @Presubmit @Test fun finishSubActivity() { + val buttonActivityComponent = ActivityOptions + .BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent() + val imeAutoFocusActivityComponent = ActivityOptions + .SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() testSpec.assertWm { - this.isAppWindowOnTop(ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME) - .then() - .isAppWindowOnTop(ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME) - .then() - .isAppWindowOnTop(ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME) + this.isAppWindowOnTop(buttonActivityComponent) + .then() + .isAppWindowOnTop(imeAutoFocusActivityComponent) + .then() + .isAppWindowOnTop(buttonActivityComponent) } } + /** + * Checks that all parts of the screen are covered during the transition + */ @Presubmit @Test fun entireScreenCovered() = testSpec.entireScreenCovered() + /** + * Checks that the [LAUNCHER_COMPONENT] window is not on top. The launcher cannot be + * asserted with `isAppWindowVisible` because it contains 2 windows with the exact same name, + * and both are never simultaneously visible + */ @Presubmit @Test - fun launcherWindowNotVisible() { + fun launcherWindowNotOnTop() { testSpec.assertWm { - this.isAppWindowInvisible(LAUNCHER_COMPONENT, ignoreActivity = true) + this.isAppWindowNotOnTop(LAUNCHER_COMPONENT) } } + /** + * Checks that the [LAUNCHER_COMPONENT] layer is never visible during the transition + */ @Presubmit @Test fun launcherLayerNotVisible() { @@ -106,6 +145,12 @@ class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) { } companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index 3678f33aa46e..663af703f76d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -27,6 +27,7 @@ import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.traces.common.WindowManagerConditionsFactory import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -78,6 +79,11 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransitio } transitions { device.reopenAppFromOverview(wmHelper) + wmHelper.waitFor( + WindowManagerConditionsFactory.hasLayersAnimating().negate(), + WindowManagerConditionsFactory.isWMStateComplete(), + WindowManagerConditionsFactory.isHomeActivityVisible().negate() + ) wmHelper.waitForFullScreenApp(testApp.component) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt index b7176122095b..cf10c5366ce9 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt @@ -16,7 +16,6 @@ package com.android.server.wm.flicker.launch -import android.content.ComponentName import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface @@ -29,7 +28,8 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName import com.google.common.truth.Truth import org.junit.FixMethodOrder import org.junit.Test @@ -61,7 +61,7 @@ import org.junit.runners.Parameterized @Group1 class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSpec) { override val testApp = NonResizeableAppHelper(instrumentation) - private val colorFadComponent = ComponentName("", "ColorFade BLAST#") + private val colorFadComponent = FlickerComponentName("", "ColorFade BLAST#") /** * Defines the transition used to run the test @@ -92,15 +92,15 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti * Checks that the nav bar layer starts visible, becomes invisible during unlocking animation * and becomes visible at the end */ - @Presubmit + @Postsubmit @Test fun navBarLayerVisibilityChanges() { testSpec.assertLayers { - this.isVisible(WindowManagerStateHelper.NAV_BAR_COMPONENT) + this.isVisible(FlickerComponentName.NAV_BAR) .then() - .isInvisible(WindowManagerStateHelper.NAV_BAR_COMPONENT) + .isInvisible(FlickerComponentName.NAV_BAR) .then() - .isVisible(WindowManagerStateHelper.NAV_BAR_COMPONENT) + .isVisible(FlickerComponentName.NAV_BAR) } } @@ -108,7 +108,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti * Checks that the app layer doesn't exist at the start of the transition, that it is * created (invisible) and becomes visible during the transition */ - @Presubmit + @FlakyTest @Test fun appLayerBecomesVisible() { testSpec.assertLayers { @@ -133,10 +133,9 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti testSpec.assertWm { this.notContains(testApp.component) .then() - .isAppWindowInvisible(testApp.component, - ignoreActivity = true, isOptional = true) + .isAppWindowInvisible(testApp.component, isOptional = true) .then() - .isAppWindowVisible(testApp.component, ignoreActivity = true) + .isAppWindowVisible(testApp.component) } } @@ -147,7 +146,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti @Test fun appWindowBecomesVisibleAtEnd() { testSpec.assertWmEnd { - this.isVisible(testApp.component) + this.isAppWindowVisible(testApp.component) } } @@ -159,20 +158,29 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti @Test fun navBarWindowsVisibilityChanges() { testSpec.assertWm { - this.isAboveAppWindowVisible(WindowManagerStateHelper.NAV_BAR_COMPONENT) + this.isAboveAppWindowVisible(FlickerComponentName.NAV_BAR) .then() - .isNonAppWindowInvisible(WindowManagerStateHelper.NAV_BAR_COMPONENT) + .isNonAppWindowInvisible(FlickerComponentName.NAV_BAR) .then() - .isAboveAppWindowVisible(WindowManagerStateHelper.NAV_BAR_COMPONENT) + .isAboveAppWindowVisible(FlickerComponentName.NAV_BAR) } } /** {@inheritDoc} */ @FlakyTest @Test + override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() + + /** {@inheritDoc} */ + @FlakyTest + @Test override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = super.visibleWindowsShownMoreThanOneConsecutiveEntry() + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + /** {@inheritDoc} */ @FlakyTest @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt index 14d17f82b805..7af7b3ab6f24 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt @@ -18,13 +18,11 @@ package com.android.server.wm.flicker.launch import android.app.Instrumentation import android.platform.test.annotations.Presubmit -import android.view.Surface import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.StandardAppHelper @@ -39,10 +37,12 @@ import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.SNAPSHOT_COMPONENT -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.SPLASH_SCREEN_COMPONENT +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.Test +/** + * Base class for app launch tests + */ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation) @@ -85,7 +85,7 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { } /** - * Checks that the navigation bar layer is visible during the whole transition + * Checks that the navigation bar layer is visible at the start and end of the trace */ open fun navBarLayerIsVisible() { testSpec.navBarLayerIsVisible() @@ -96,9 +96,7 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { */ @Presubmit @Test - open fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation) - } + open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() /** * Checks that the status bar window is visible during the whole transition @@ -110,7 +108,7 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { } /** - * Checks that the status bar layer is visible during the whole transition + * Checks that the status bar layer is visible at the start and end of the trace */ @Presubmit @Test @@ -123,9 +121,7 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { */ @Presubmit @Test - open fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation) - } + open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() /** * Checks that all windows that are visible on the trace, are visible for at least 2 @@ -188,9 +184,9 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { testSpec.assertWm { this.isAppWindowOnTop(LAUNCHER_COMPONENT) .then() - .isAppWindowOnTop(SNAPSHOT_COMPONENT, isOptional = true) + .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true) .then() - .isAppWindowOnTop(SPLASH_SCREEN_COMPONENT, isOptional = true) + .isAppWindowOnTop(FlickerComponentName.SPLASH_SCREEN, isOptional = true) .then() .isAppWindowOnTop(testApp.component) } @@ -202,9 +198,9 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { */ open fun launcherWindowBecomesInvisible() { testSpec.assertWm { - this.isAppWindowVisible(LAUNCHER_COMPONENT) + this.isAppWindowOnTop(LAUNCHER_COMPONENT) .then() - .isAppWindowInvisible(LAUNCHER_COMPONENT) + .isAppWindowNotOnTop(LAUNCHER_COMPONENT) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt new file mode 100644 index 000000000000..495e2d62a11d --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt @@ -0,0 +1,248 @@ +/* + * 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.flicker.launch + +import android.app.Instrumentation +import android.app.WallpaperManager +import android.platform.test.annotations.Postsubmit +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.LAUNCHER_COMPONENT +import com.android.server.wm.flicker.annotation.Group4 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.entireScreenCovered +import com.android.server.wm.flicker.helpers.NewTasksAppHelper +import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.navBarLayerIsVisible +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.repetitions +import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.statusBarLayerIsVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.flicker.testapp.ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME +import com.android.server.wm.flicker.testapp.ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.common.FlickerComponentName.Companion.SPLASH_SCREEN +import com.android.server.wm.traces.common.FlickerComponentName.Companion.WALLPAPER_BBQ_WRAPPER +import com.android.server.wm.traces.parser.toFlickerComponent +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test the back and forward transition between 2 activities. + * + * To run this test: `atest FlickerTests:ActivitiesTransitionTest` + * + * Actions: + * Launch the NewTaskLauncherApp [mTestApp] + * Open a new task (SimpleActivity) from the NewTaskLauncherApp [mTestApp] + * Go back to the NewTaskLauncherApp [mTestApp] + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group4 +class TaskTransitionTest(val testSpec: FlickerTestParameter) { + val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val mTestApp: NewTasksAppHelper = NewTasksAppHelper(instrumentation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + withTestName { testSpec.name } + repeat { testSpec.config.repetitions } + setup { + eachRun { + mTestApp.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(mTestApp.component) + } + } + teardown { + test { + mTestApp.exit() + } + } + transitions { + mTestApp.openNewTask(device, wmHelper) + device.pressBack() + wmHelper.waitForAppTransitionIdle() + wmHelper.waitForFullScreenApp(mTestApp.component) + } + } + } + + /** + * Checks that the wallpaper window is never visible when performing task transitions. + * A solid color background should be shown instead. + */ + @Postsubmit + @Test + fun wallpaperWindowIsNeverVisible() { + testSpec.assertWm { + this.isNonAppWindowInvisible(WALLPAPER) + } + } + + /** + * Checks that the wallpaper layer is never visible when performing task transitions. + * A solid color background should be shown instead. + */ + @Postsubmit + @Test + fun wallpaperLayerIsNeverVisible() { + testSpec.assertLayers { + this.isInvisible(WALLPAPER) + this.isInvisible(WALLPAPER_BBQ_WRAPPER) + } + } + + /** + * Check that the launcher window is never visible when performing task transitions. + * A solid color background should be shown above it. + */ + @Postsubmit + @Test + fun launcherWindowIsNeverVisible() { + testSpec.assertWm { + this.isAppWindowInvisible(LAUNCHER_COMPONENT) + } + } + + /** + * Checks that the launcher layer is never visible when performing task transitions. + * A solid color background should be shown above it. + */ + @Postsubmit + @Test + fun launcherLayerIsNeverVisible() { + testSpec.assertLayers { + this.isInvisible(LAUNCHER_COMPONENT) + } + } + + /** + * Checks that a color background is visible while the task transition is occurring. + */ + @Postsubmit + @Test + fun colorLayerIsVisibleDuringTransition() { + val bgColorLayer = FlickerComponentName("", "colorBackgroundLayer") + val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + + testSpec.assertLayers { + this.coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY) + .isInvisible(bgColorLayer) + .then() + // Transitioning + .isVisible(bgColorLayer) + .then() + // Fully transitioned to simple SIMPLE_ACTIVITY + .coversExactly(displayBounds, SIMPLE_ACTIVITY) + .isInvisible(bgColorLayer) + .then() + // Transitioning back + .isVisible(bgColorLayer) + .then() + // Fully transitioned back to LAUNCH_NEW_TASK_ACTIVITY + .isInvisible(bgColorLayer) + .coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY) + } + } + + /** + * Checks that we start with the LaunchNewTask activity on top and then open up + * the SimpleActivity and then go back to the LaunchNewTask activity. + */ + @Postsubmit + @Test + fun newTaskOpensOnTopAndThenCloses() { + testSpec.assertWm { + this.isAppWindowOnTop(LAUNCH_NEW_TASK_ACTIVITY) + .then() + .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true) + .then() + .isAppWindowOnTop(SIMPLE_ACTIVITY) + .then() + .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true) + .then() + .isAppWindowOnTop(LAUNCH_NEW_TASK_ACTIVITY) + } + } + + /** + * Checks that all parts of the screen are covered at the start and end of the transition + */ + @Postsubmit + @Test + fun entireScreenCovered() = testSpec.entireScreenCovered() + + /** + * Checks that the navbar window is visible throughout the transition + */ + @Postsubmit + @Test + fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() + + /** + * Checks that the navbar layer is visible throughout the transition + */ + @Postsubmit + @Test + fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() + + /** + * Checks that the status bar window is visible throughout the transition + */ + @Postsubmit + @Test + fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() + + /** + * Checks that the status bar layer is visible throughout the transition + */ + @Postsubmit + @Test + fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() + + companion object { + private val WALLPAPER = getWallpaperPackage(InstrumentationRegistry.getInstrumentation()) + private val LAUNCH_NEW_TASK_ACTIVITY = + LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent() + private val SIMPLE_ACTIVITY = SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() + + private fun getWallpaperPackage(instrumentation: Instrumentation): FlickerComponentName { + val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext) + + return wallpaperManager.wallpaperInfo.component.toFlickerComponent() + } + + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests(repetitions = 5) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt index 035aac1c5e86..52904cce8772 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt @@ -39,7 +39,7 @@ import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.SNAPSHOT_COMPONENT +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -189,9 +189,9 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet testSpec.assertWm { this.isAppWindowInvisible(testApp1.component) .then() - .isAppWindowVisible(SNAPSHOT_COMPONENT, isOptional = true) + .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) .then() - .isAppWindowVisible(testApp1.component, ignoreActivity = true) + .isAppWindowVisible(testApp1.component) } } @@ -217,7 +217,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet @Test fun app2WindowBecomesAndStaysInvisible() { testSpec.assertWm { - this.isAppWindowVisible(testApp2.component, ignoreActivity = true) + this.isAppWindowVisible(testApp2.component) .then() .isAppWindowInvisible(testApp2.component) } @@ -251,7 +251,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet // TODO: Do we actually want to test this? Seems too implementation specific... .isAppWindowVisible(LAUNCHER_COMPONENT, isOptional = true) .then() - .isAppWindowVisible(SNAPSHOT_COMPONENT, isOptional = true) + .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) .then() .isAppWindowVisible(testApp1.component) } @@ -270,7 +270,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet .then() .isVisible(LAUNCHER_COMPONENT, isOptional = true) .then() - .isVisible(SNAPSHOT_COMPONENT, isOptional = true) + .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) .then() .isVisible(testApp1.component) } @@ -297,8 +297,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet */ @Postsubmit @Test - fun navbarIsAlwaysInRightPosition() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) + fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales() /** * Checks that the status bar window is visible throughout the entire transition. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt new file mode 100644 index 000000000000..842aa2b548db --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt @@ -0,0 +1,347 @@ +/* + * 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.flicker.quickswitch + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.RequiresDevice +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.LAUNCHER_COMPONENT +import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.helpers.isRotated +import com.android.server.wm.flicker.navBarLayerIsVisible +import com.android.server.wm.flicker.navBarLayerRotatesAndScales +import com.android.server.wm.flicker.navBarWindowIsVisible +import com.android.server.wm.flicker.repetitions +import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.statusBarLayerIsVisible +import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test quick switching back to previous app from last opened app + * + * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest` + * + * Actions: + * Launch an app [testApp1] + * Launch another app [testApp2] + * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1] + * Swipe left from the bottom of the screen to quick switch forward to the second app [testApp2] + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Group1 +class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + + private val testApp1 = SimpleAppHelper(instrumentation) + private val testApp2 = NonResizeableAppHelper(instrumentation) + + private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + withTestName { testSpec.name } + repeat { testSpec.config.repetitions } + setup { + eachRun { + testApp1.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(testApp1.component) + + testApp2.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(testApp2.component) + + // Swipe right from bottom to quick switch back + // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle + // as to not accidentally trigger a swipe back or forward action which would result + // in the same behavior but not testing quick swap. + device.swipe( + startDisplayBounds.bounds.right / 3, + startDisplayBounds.bounds.bottom, + 2 * startDisplayBounds.bounds.right / 3, + startDisplayBounds.bounds.bottom, + if (testSpec.config.startRotation.isRotated()) 75 else 30 + ) + + wmHelper.waitForFullScreenApp(testApp1.component) + wmHelper.waitForAppTransitionIdle() + } + } + transitions { + // Swipe left from bottom to quick switch forward + // NOTE: We don't perform an edge-to-edge swipe but instead only swipe in the middle + // as to not accidentally trigger a swipe back or forward action which would result + // in the same behavior but not testing quick swap. + device.swipe( + 2 * startDisplayBounds.bounds.right / 3, + startDisplayBounds.bounds.bottom, + startDisplayBounds.bounds.right / 3, + startDisplayBounds.bounds.bottom, + if (testSpec.config.startRotation.isRotated()) 75 else 30 + ) + + wmHelper.waitForFullScreenApp(testApp2.component) + wmHelper.waitForAppTransitionIdle() + } + + teardown { + test { + testApp1.exit() + testApp2.exit() + } + } + } + } + + /** + * Checks that the transition starts with [testApp1]'s windows filling/covering exactly the + * entirety of the display. + */ + @Postsubmit + @Test + fun startsWithApp1WindowsCoverFullScreen() { + testSpec.assertWmStart { + this.frameRegion(testApp1.component).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with [testApp1]'s layers filling/covering exactly the + * entirety of the display. + */ + @Postsubmit + @Test + fun startsWithApp1LayersCoverFullScreen() { + testSpec.assertLayersStart { + this.visibleRegion(testApp1.component).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with [testApp1] being the top window. + */ + @Postsubmit + @Test + fun startsWithApp1WindowBeingOnTop() { + testSpec.assertWmStart { + this.isAppWindowOnTop(testApp1.component) + } + } + + /** + * Checks that [testApp2] windows fill the entire screen (i.e. is "fullscreen") at the end of the + * transition once we have fully quick switched from [testApp1] back to the [testApp2]. + */ + @Postsubmit + @Test + fun endsWithApp2WindowsCoveringFullScreen() { + testSpec.assertWmEnd { + this.frameRegion(testApp2.component).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that [testApp2] layers fill the entire screen (i.e. is "fullscreen") at the end of the + * transition once we have fully quick switched from [testApp1] back to the [testApp2]. + */ + @Postsubmit + @Test + fun endsWithApp2LayersCoveringFullScreen() { + testSpec.assertLayersEnd { + this.visibleRegion(testApp2.component).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that [testApp2] is the top window at the end of the transition once we have fully quick + * switched from [testApp1] back to the [testApp2]. + */ + @Postsubmit + @Test + fun endsWithApp2BeingOnTop() { + testSpec.assertWmEnd { + this.isAppWindowOnTop(testApp2.component) + } + } + + /** + * Checks that [testApp2]'s window starts off invisible and becomes visible at some point before + * the end of the transition and then stays visible until the end of the transition. + */ + @Postsubmit + @Test + fun app2WindowBecomesAndStaysVisible() { + testSpec.assertWm { + this.isAppWindowInvisible(testApp2.component) + .then() + .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(testApp2.component) + } + } + + /** + * Checks that [testApp2]'s layer starts off invisible and becomes visible at some point before + * the end of the transition and then stays visible until the end of the transition. + */ + @Postsubmit + @Test + fun app2LayerBecomesAndStaysVisible() { + testSpec.assertLayers { + this.isInvisible(testApp2.component) + .then() + .isVisible(testApp2.component) + } + } + + /** + * Checks that [testApp1]'s window starts off visible and becomes invisible at some point before + * the end of the transition and then stays invisible until the end of the transition. + */ + @Postsubmit + @Test + fun app1WindowBecomesAndStaysInvisible() { + testSpec.assertWm { + this.isAppWindowVisible(testApp1.component) + .then() + .isAppWindowInvisible(testApp1.component) + } + } + + /** + * Checks that [testApp1]'s layer starts off visible and becomes invisible at some point before + * the end of the transition and then stays invisible until the end of the transition. + */ + @Postsubmit + @Test + fun app1LayerBecomesAndStaysInvisible() { + testSpec.assertLayers { + this.isVisible(testApp1.component) + .then() + .isInvisible(testApp1.component) + } + } + + /** + * Checks that [testApp1]'s window is visible at least until [testApp2]'s window is visible. + * Ensures that at any point, either [testApp2] or [testApp1]'s windows are at least partially + * visible. + */ + @Postsubmit + @Test + fun app2WindowIsVisibleOnceApp1WindowIsInvisible() { + testSpec.assertWm { + this.isAppWindowVisible(testApp1.component) + .then() + .isAppWindowVisible(LAUNCHER_COMPONENT, isOptional = true) + .then() + .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(testApp2.component) + } + } + + /** + * Checks that [testApp1]'s layer is visible at least until [testApp2]'s window is visible. + * Ensures that at any point, either [testApp2] or [testApp1]'s windows are at least partially + * visible. + */ + @Postsubmit + @Test + fun app2LayerIsVisibleOnceApp1LayerIsInvisible() { + testSpec.assertLayers { + this.isVisible(testApp1.component) + .then() + .isVisible(LAUNCHER_COMPONENT, isOptional = true) + .then() + .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + .then() + .isVisible(testApp2.component) + } + } + + /** + * Checks that the navbar window is visible throughout the entire transition. + */ + @Postsubmit + @Test + fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsVisible() + + /** + * Checks that the navbar layer is visible throughout the entire transition. + */ + @Postsubmit + @Test + fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible() + + /** + * Checks that the navbar is always in the right position and covers the expected region. + * + * NOTE: This doesn't check that the navbar is visible or not. + */ + @Postsubmit + @Test + fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales() + + /** + * Checks that the status bar window is visible throughout the entire transition. + */ + @Postsubmit + @Test + fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible() + + /** + * Checks that the status bar layer is visible throughout the entire transition. + */ + @Postsubmit + @Test + fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 5, + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ), + supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt index ca8f8af2df94..10ca0d9b323b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt @@ -27,7 +27,7 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.LAUNCHER_COMPONENT -import com.android.server.wm.flicker.annotation.Group1 +import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.SimpleAppHelper @@ -38,7 +38,7 @@ import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.SNAPSHOT_COMPONENT +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -60,7 +60,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 +@Group4 class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val testApp = SimpleAppHelper(instrumentation) @@ -145,7 +145,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { @Test fun startsWithHomeActivityFlaggedVisible() { testSpec.assertWmStart { - this.isHomeActivityVisible(true) + this.isHomeActivityVisible() } } @@ -192,7 +192,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { @Test fun endsWithHomeActivityFlaggedInvisible() { testSpec.assertWmEnd { - this.isHomeActivityVisible(false) + this.isHomeActivityInvisible() } } @@ -204,9 +204,9 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { @Test fun appWindowBecomesAndStaysVisible() { testSpec.assertWm { - this.isAppWindowInvisible(testApp.component, ignoreActivity = true) + this.isAppWindowInvisible(testApp.component) .then() - .isAppWindowVisible(testApp.component, ignoreActivity = true) + .isAppWindowVisible(testApp.component) } } @@ -232,9 +232,9 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { @Test fun launcherWindowBecomesAndStaysInvisible() { testSpec.assertWm { - this.isAppWindowVisible(LAUNCHER_COMPONENT) + this.isAppWindowOnTop(LAUNCHER_COMPONENT) .then() - .isAppWindowInvisible(LAUNCHER_COMPONENT) + .isAppWindowNotOnTop(LAUNCHER_COMPONENT) } } @@ -260,9 +260,9 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { @Test fun appWindowIsVisibleOnceLauncherWindowIsInvisible() { testSpec.assertWm { - this.isAppWindowVisible(LAUNCHER_COMPONENT) + this.isAppWindowOnTop(LAUNCHER_COMPONENT) .then() - .isAppWindowVisible(SNAPSHOT_COMPONENT) + .isAppWindowVisible(FlickerComponentName.SNAPSHOT) .then() .isAppWindowVisible(testApp.component) } @@ -278,7 +278,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { testSpec.assertLayers { this.isVisible(LAUNCHER_COMPONENT) .then() - .isVisible(SNAPSHOT_COMPONENT) + .isVisible(FlickerComponentName.SNAPSHOT) .then() .isVisible(testApp.component) } @@ -305,8 +305,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { */ @Presubmit @Test - fun navbarIsAlwaysInRightPosition() = - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) + fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales() /** * Checks that the status bar window is visible throughout the entire transition. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index d57c6698e35c..fd8abc621b33 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -16,6 +16,7 @@ package com.android.server.wm.flicker.rotation +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -24,22 +25,53 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.ROTATION_COMPONENT +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Cycle through supported app rotations. + * Test opening an app and cycling through app rotations + * + * Currently runs: + * 0 -> 90 degrees + * 90 -> 0 degrees + * + * Actions: + * Launch an app (via intent) + * Set initial device orientation + * Start tracing + * Change device orientation + * Stop tracing + * * To run this test: `atest FlickerTests:ChangeAppRotationTest` + * + * To run only the presubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * + * To run only the postsubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * + * To run only the flaky assertions add: `-- + * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [RotationTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @@ -49,6 +81,9 @@ import org.junit.runners.Parameterized class ChangeAppRotationTest( testSpec: FlickerTestParameter ) : RotationTransition(testSpec) { + @get:Rule + val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec) + override val testApp = SimpleAppHelper(instrumentation) override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { @@ -60,50 +95,94 @@ class ChangeAppRotationTest( } } + @Postsubmit + @Test + fun runPresubmitAssertion() { + flickerRule.checkPresubmitAssertions() + } + + @Postsubmit + @Test + fun runPostsubmitAssertion() { + flickerRule.checkPostsubmitAssertions() + } + + @FlakyTest + @Test + fun runFlakyAssertion() { + flickerRule.checkFlakyAssertions() + } + + /** {@inheritDoc} */ @FlakyTest(bugId = 190185577) @Test override fun focusDoesNotChange() { super.focusDoesNotChange() } + /** + * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition, + * doesn't flicker, and disappears before the transition is complete + */ @Presubmit @Test - fun screenshotLayerBecomesInvisible() { + fun rotationLayerAppearsAndVanishes() { testSpec.assertLayers { this.isVisible(testApp.component) .then() - .isVisible(ROTATION_COMPONENT) + .isVisible(FlickerComponentName.ROTATION) .then() .isVisible(testApp.component) + .isInvisible(FlickerComponentName.ROTATION) } } + /** + * Checks that the status bar window is visible and above the app windows in all WM + * trace entries + */ @Presubmit @Test fun statusBarWindowIsVisible() { testSpec.statusBarWindowIsVisible() } + /** + * Checks that the status bar layer is visible at the start and end of the transition + */ @Presubmit @Test fun statusBarLayerIsVisible() { testSpec.statusBarLayerIsVisible() } + /** + * Checks the position of the status bar at the start and end of the transition + */ @Presubmit @Test - fun statusBarLayerRotatesScales() { - testSpec.statusBarLayerRotatesScales( - testSpec.config.startRotation, testSpec.config.endRotation) - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + /** {@inheritDoc} */ @FlakyTest @Test override fun navBarLayerRotatesAndScales() { super.navBarLayerRotatesAndScales() } + /** {@inheritDoc} */ + @FlakyTest + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt index 612ff9d3a153..e850632ed8af 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt @@ -17,7 +17,6 @@ package com.android.server.wm.flicker.rotation import android.app.Instrumentation -import android.content.ComponentName import android.platform.test.annotations.Presubmit import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider @@ -31,9 +30,12 @@ import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.startRotation -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.Test +/** + * Base class for app rotation tests + */ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) { protected abstract val testApp: StandardAppHelper @@ -55,6 +57,10 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Entry point for the test runner. It will use this method to initialize and cache + * flicker executions + */ @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { @@ -62,38 +68,53 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Checks that the navigation bar window is visible and above the app windows in all WM + * trace entries + */ @Presubmit @Test open fun navBarWindowIsVisible() { testSpec.navBarWindowIsVisible() } + /** + * Checks that the navigation bar layer is visible at the start and end of the transition + */ @Presubmit @Test open fun navBarLayerIsVisible() { testSpec.navBarLayerIsVisible() } + /** + * Checks the position of the navigation bar at the start and end of the transition + */ @Presubmit @Test - open fun navBarLayerRotatesAndScales() { - testSpec.navBarLayerRotatesAndScales( - testSpec.config.startRotation, testSpec.config.endRotation) - } + open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() + /** + * Checks that all layers that are visible on the trace, are visible for at least 2 + * consecutive entries. + */ @Presubmit @Test open fun visibleLayersShownMoreThanOneConsecutiveEntry() { testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry( - ignoreLayers = listOf(WindowManagerStateHelper.SPLASH_SCREEN_COMPONENT, - WindowManagerStateHelper.SNAPSHOT_COMPONENT, - ComponentName("", "SecondaryHomeHandle") + ignoreLayers = listOf(FlickerComponentName.SPLASH_SCREEN, + FlickerComponentName.SNAPSHOT, + FlickerComponentName("", "SecondaryHomeHandle") ) ) } } + /** + * Checks that all windows that are visible on the trace, are visible for at least 2 + * consecutive entries. + */ @Presubmit @Test open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { @@ -102,10 +123,16 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Checks that all parts of the screen are covered during the transition + */ @Presubmit @Test open fun entireScreenCovered() = testSpec.entireScreenCovered() + /** + * Checks that the focus doesn't change during animation + */ @Presubmit @Test open fun focusDoesNotChange() { @@ -114,6 +141,9 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Checks that [testApp] layer covers the entire screen at the start of the transition + */ @Presubmit @Test open fun appLayerRotates_StartingPos() { @@ -124,6 +154,9 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) } } + /** + * Checks that [testApp] layer covers the entire screen at the end of the transition + */ @Presubmit @Test open fun appLayerRotates_EndingPos() { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index 48efe73312c3..310f04b9710f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -27,7 +27,7 @@ import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -35,8 +35,41 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Cycle through supported app rotations using seamless rotations. + * Test opening an app and cycling through app rotations using seamless rotations + * + * Currently runs: + * 0 -> 90 degrees + * 0 -> 90 degrees (with starved UI thread) + * 90 -> 0 degrees + * 90 -> 0 degrees (with starved UI thread) + * + * Actions: + * Launch an app in fullscreen and supporting seamless rotation (via intent) + * Set initial device orientation + * Start tracing + * Change device orientation + * Stop tracing + * * To run this test: `atest FlickerTests:SeamlessAppRotationTest` + * + * To run only the presubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * + * To run only the postsubmit assertions add: `-- + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * + * To run only the flaky assertions add: `-- + * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * + * Notes: + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [RotationTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup */ @RequiresDevice @RunWith(Parameterized::class) @@ -61,6 +94,9 @@ class SeamlessAppRotationTest( } } + /** + * Checks that [testApp] window is always in full screen + */ @Presubmit @Test fun appWindowFullScreen() { @@ -75,6 +111,9 @@ class SeamlessAppRotationTest( } } + /** + * Checks that [testApp] window is always with seamless rotation + */ @Presubmit @Test fun appWindowSeamlessRotation() { @@ -90,6 +129,9 @@ class SeamlessAppRotationTest( } } + /** + * Checks that [testApp] window is always visible + */ @Presubmit @Test fun appLayerAlwaysVisible() { @@ -98,6 +140,9 @@ class SeamlessAppRotationTest( } } + /** + * Checks that [testApp] layer covers the entire screen during the whole transition + */ @Presubmit @Test fun appLayerRotates() { @@ -110,29 +155,36 @@ class SeamlessAppRotationTest( } } + /** + * Checks that the [FlickerComponentName.STATUS_BAR] window is invisible during the whole + * transition + */ @Presubmit @Test fun statusBarWindowIsAlwaysInvisible() { testSpec.assertWm { - this.isAboveAppWindowInvisible(WindowManagerStateHelper.STATUS_BAR_COMPONENT) + this.isAboveAppWindowInvisible(FlickerComponentName.STATUS_BAR) } } + /** + * Checks that the [FlickerComponentName.STATUS_BAR] layer is invisible during the whole + * transition + */ @Presubmit @Test fun statusBarLayerIsAlwaysInvisible() { testSpec.assertLayers { - this.isInvisible(WindowManagerStateHelper.STATUS_BAR_COMPONENT) + this.isInvisible(FlickerComponentName.STATUS_BAR) } } + /** {@inheritDoc} */ @FlakyTest @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() companion object { - private val testFactory = FlickerTestParameterFactory.getInstance() - private val Map<String, Any?>.starveUiThread get() = this.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean @@ -144,20 +196,34 @@ class SeamlessAppRotationTest( return config } + /** + * Creates the test configurations for seamless rotation based on the default rotation + * tests from [FlickerTestParameterFactory.getConfigRotationTests], but adding an + * additional flag ([ActivityOptions.EXTRA_STARVE_UI_THREAD]) to indicate if the app + * should starve the UI thread of not + */ @JvmStatic private fun getConfigurations(): List<FlickerTestParameter> { - return testFactory.getConfigRotationTests(repetitions = 2).flatMap { - val defaultRun = it.createConfig(starveUiThread = false) - val busyUiRun = it.createConfig(starveUiThread = true) - listOf( - FlickerTestParameter(defaultRun), - FlickerTestParameter(busyUiRun, - name = "${FlickerTestParameter.defaultName(busyUiRun)}_BUSY_UI_THREAD" + return FlickerTestParameterFactory.getInstance() + .getConfigRotationTests(repetitions = 2) + .flatMap { + val defaultRun = it.createConfig(starveUiThread = false) + val busyUiRun = it.createConfig(starveUiThread = true) + listOf( + FlickerTestParameter(defaultRun), + FlickerTestParameter(busyUiRun, + name = "${FlickerTestParameter.defaultName(busyUiRun)}_BUSY_UI_THREAD" + ) ) - ) - } + } } + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 3b9f33aaded1..cb37fc7b47e9 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -80,5 +80,15 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".LaunchNewTaskActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewTaskActivity" + android:configChanges="orientation|screenSize" + android:label="LaunchNewTaskActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> </application> </manifest> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml new file mode 100644 index 000000000000..8f75d175d00a --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/task_button.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/holo_orange_light"> + <Button + android:id="@+id/launch_new_task" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="New task" /> +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index 224d2ac38a11..baf36ab0e132 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -51,4 +51,9 @@ public class ActivityOptions { public static final ComponentName BUTTON_ACTIVITY_COMPONENT_NAME = new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ButtonActivity"); + + public static final String LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME = "LaunchNewTaskApp"; + public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity"); } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java new file mode 100644 index 000000000000..1809781b33e6 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewTaskActivity.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.WindowManager; +import android.widget.Button; + +public class LaunchNewTaskActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WindowManager.LayoutParams p = getWindow().getAttributes(); + p.layoutInDisplayCutoutMode = WindowManager.LayoutParams + .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(p); + setContentView(R.layout.task_button); + + Button button = findViewById(R.id.launch_new_task); + button.setOnClickListener(v -> { + Intent intent = new Intent(LaunchNewTaskActivity.this, SimpleActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK); + startActivity(intent); + }); + } +} |