diff options
597 files changed, 12701 insertions, 2812 deletions
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 4e2b6fa56b17..d189bab85195 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -24,7 +24,6 @@ import android.app.GrantedUriPermission; import android.app.IApplicationThread; import android.app.IActivityClientController; import android.app.IActivityController; -import android.app.IAppTask; import android.app.IAssistDataReceiver; import android.app.IInstrumentationWatcher; import android.app.IProcessObserver; @@ -107,14 +106,6 @@ interface IActivityTaskManager { in ProfilerInfo profilerInfo, in Bundle options, int userId); boolean startNextMatchingActivity(in IBinder callingActivity, in Intent intent, in Bundle options); - - /** - * The DreamActivity has to be started in a special way that does not involve the PackageParser. - * The DreamActivity is a framework component inserted in the dream application process. Hence, - * it is not declared in the application's manifest and cannot be parsed. startDreamActivity - * creates the activity and starts it without reaching out to the PackageParser. - */ - boolean startDreamActivity(in Intent intent); int startActivityIntentSender(in IApplicationThread caller, in IIntentSender target, in IBinder whitelistToken, in Intent fillInIntent, in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl index fbb0748fc01f..63cae63e1e50 100644 --- a/core/java/android/app/IUiAutomationConnection.aidl +++ b/core/java/android/app/IUiAutomationConnection.aidl @@ -24,6 +24,8 @@ import android.view.SurfaceControl; import android.view.WindowContentFrameStats; import android.view.WindowAnimationFrameStats; import android.os.ParcelFileDescriptor; +import android.window.ScreenCapture.ScreenCaptureListener; +import android.window.ScreenCapture.LayerCaptureArgs; import java.util.List; @@ -43,8 +45,8 @@ interface IUiAutomationConnection { void injectInputEventToInputFilter(in InputEvent event); void syncInputTransactions(boolean waitForAnimations); boolean setRotation(int rotation); - Bitmap takeScreenshot(in Rect crop); - Bitmap takeSurfaceControlScreenshot(in SurfaceControl surfaceControl); + boolean takeScreenshot(in Rect crop, in ScreenCaptureListener listener); + boolean takeSurfaceControlScreenshot(in SurfaceControl surfaceControl, in ScreenCaptureListener listener); boolean clearWindowContentFrameStats(int windowId); WindowContentFrameStats getWindowContentFrameStats(int windowId); void clearWindowAnimationFrameStats(); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 785470f2f22e..79b68c1456c7 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -571,6 +571,12 @@ public class NotificationManager { */ public static final int BUBBLE_PREFERENCE_SELECTED = 2; + /** + * Maximum length of the component name of a registered NotificationListenerService. + * @hide + */ + public static int MAX_SERVICE_COMPONENT_NAME_LENGTH = 500; + @UnsupportedAppUsage private static INotificationManager sService; diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index c4e49954f745..2b5175ca6659 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -242,6 +242,18 @@ public class TaskInfo { public boolean isLetterboxDoubleTapEnabled; /** + * Whether the user aspect ratio settings button is enabled + * @hide + */ + public boolean topActivityEligibleForUserAspectRatioButton; + + /** + * Hint about the letterbox state of the top activity. + * @hide + */ + public boolean topActivityBoundsLetterboxed; + + /** * Whether the update comes from a letterbox double-tap action from the user or not. * @hide */ @@ -460,7 +472,8 @@ public class TaskInfo { public boolean hasCompatUI() { return hasCameraCompatControl() || topActivityInSizeCompat || topActivityEligibleForLetterboxEducation - || isLetterboxDoubleTapEnabled; + || isLetterboxDoubleTapEnabled + || topActivityEligibleForUserAspectRatioButton; } /** @@ -510,6 +523,8 @@ public class TaskInfo { && supportsMultiWindow == that.supportsMultiWindow && displayAreaFeatureId == that.displayAreaFeatureId && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap + && topActivityEligibleForUserAspectRatioButton + == that.topActivityEligibleForUserAspectRatioButton && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition && topActivityLetterboxWidth == that.topActivityLetterboxWidth && topActivityLetterboxHeight == that.topActivityLetterboxHeight @@ -543,6 +558,8 @@ public class TaskInfo { && taskId == that.taskId && topActivityInSizeCompat == that.topActivityInSizeCompat && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap + && topActivityEligibleForUserAspectRatioButton + == that.topActivityEligibleForUserAspectRatioButton && topActivityEligibleForLetterboxEducation == that.topActivityEligibleForLetterboxEducation && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition @@ -606,6 +623,8 @@ public class TaskInfo { displayAreaFeatureId = source.readInt(); cameraCompatControlState = source.readInt(); isLetterboxDoubleTapEnabled = source.readBoolean(); + topActivityEligibleForUserAspectRatioButton = source.readBoolean(); + topActivityBoundsLetterboxed = source.readBoolean(); isFromLetterboxDoubleTap = source.readBoolean(); topActivityLetterboxVerticalPosition = source.readInt(); topActivityLetterboxHorizontalPosition = source.readInt(); @@ -660,6 +679,8 @@ public class TaskInfo { dest.writeInt(displayAreaFeatureId); dest.writeInt(cameraCompatControlState); dest.writeBoolean(isLetterboxDoubleTapEnabled); + dest.writeBoolean(topActivityEligibleForUserAspectRatioButton); + dest.writeBoolean(topActivityBoundsLetterboxed); dest.writeBoolean(isFromLetterboxDoubleTap); dest.writeInt(topActivityLetterboxVerticalPosition); dest.writeInt(topActivityLetterboxHorizontalPosition); @@ -701,8 +722,11 @@ public class TaskInfo { + " topActivityInSizeCompat=" + topActivityInSizeCompat + " topActivityEligibleForLetterboxEducation= " + topActivityEligibleForLetterboxEducation - + " topActivityLetterboxed= " + isLetterboxDoubleTapEnabled - + " isFromDoubleTap= " + isFromLetterboxDoubleTap + + " isLetterboxDoubleTapEnabled= " + isLetterboxDoubleTapEnabled + + " topActivityEligibleForUserAspectRatioButton= " + + topActivityEligibleForUserAspectRatioButton + + " topActivityBoundsLetterboxed= " + topActivityBoundsLetterboxed + + " isFromLetterboxDoubleTap= " + isFromLetterboxDoubleTap + " topActivityLetterboxVerticalPosition= " + topActivityLetterboxVerticalPosition + " topActivityLetterboxHorizontalPosition= " + topActivityLetterboxHorizontalPosition diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 247d5bc77ffb..c3d26d49c93a 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -37,6 +37,7 @@ import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManagerGlobal; import android.os.Build; import android.os.Handler; @@ -71,6 +72,8 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.inputmethod.EditorInfo; +import android.window.ScreenCapture; +import android.window.ScreenCapture.ScreenshotHardwareBuffer; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -1160,17 +1163,12 @@ public final class UiAutomation { Point displaySize = new Point(); display.getRealSize(displaySize); - int rotation = display.getRotation(); - // Take the screenshot - Bitmap screenShot = null; + ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture = + ScreenCapture.createSyncCaptureListener(); try { - // Calling out without a lock held. - screenShot = mUiAutomationConnection.takeScreenshot( - new Rect(0, 0, displaySize.x, displaySize.y)); - if (screenShot == null) { - Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display " - + mDisplayId); + if (!mUiAutomationConnection.takeScreenshot( + new Rect(0, 0, displaySize.x, displaySize.y), syncScreenCapture)) { return null; } } catch (RemoteException re) { @@ -1178,10 +1176,23 @@ public final class UiAutomation { return null; } - // Optimization - screenShot.setHasAlpha(false); + final ScreenshotHardwareBuffer screenshotBuffer = + syncScreenCapture.getBuffer(); + Bitmap screenShot = screenshotBuffer.asBitmap(); + if (screenShot == null) { + Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display " + + mDisplayId); + return null; + } + Bitmap swBitmap; + try (HardwareBuffer buffer = screenshotBuffer.getHardwareBuffer()) { + swBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, false); + } + screenShot.recycle(); - return screenShot; + // Optimization + swBitmap.setHasAlpha(false); + return swBitmap; } /** @@ -1218,12 +1229,27 @@ public final class UiAutomation { // Apply a sync transaction to ensure SurfaceFlinger is flushed before capturing a // screenshot. new SurfaceControl.Transaction().apply(true); + ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture = + ScreenCapture.createSyncCaptureListener(); try { - return mUiAutomationConnection.takeSurfaceControlScreenshot(sc); + if (!mUiAutomationConnection.takeSurfaceControlScreenshot(sc, syncScreenCapture)) { + return null; + } + } catch (RemoteException re) { Log.e(LOG_TAG, "Error while taking screenshot!", re); return null; } + ScreenCapture.ScreenshotHardwareBuffer captureBuffer = + syncScreenCapture.getBuffer(); + Bitmap screenShot = captureBuffer.asBitmap(); + Bitmap swBitmap; + try (HardwareBuffer buffer = captureBuffer.getHardwareBuffer()) { + swBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, false); + } + + screenShot.recycle(); + return swBitmap; } /** diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 34f0964cf823..52949d6d1fbd 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -25,7 +25,6 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; -import android.graphics.Bitmap; import android.graphics.Rect; import android.hardware.input.InputManager; import android.hardware.input.InputManagerGlobal; @@ -51,8 +50,6 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.IAccessibilityManager; import android.window.ScreenCapture; import android.window.ScreenCapture.CaptureArgs; -import android.window.ScreenCapture.ScreenshotHardwareBuffer; -import android.window.ScreenCapture.SynchronousScreenCaptureListener; import libcore.io.IoUtils; @@ -224,56 +221,54 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } @Override - public Bitmap takeScreenshot(Rect crop) { + public boolean takeScreenshot(Rect crop, ScreenCapture.ScreenCaptureListener listener) { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); throwIfShutdownLocked(); throwIfNotConnectedLocked(); } + final long identity = Binder.clearCallingIdentity(); try { final CaptureArgs captureArgs = new CaptureArgs.Builder<>() .setSourceCrop(crop) .build(); - SynchronousScreenCaptureListener syncScreenCapture = - ScreenCapture.createSyncCaptureListener(); - mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs, - syncScreenCapture); - final ScreenshotHardwareBuffer screenshotBuffer = - syncScreenCapture.getBuffer(); - return screenshotBuffer == null ? null : screenshotBuffer.asBitmap(); + mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs, listener); } catch (RemoteException re) { re.rethrowAsRuntimeException(); } finally { Binder.restoreCallingIdentity(identity); } - return null; + + return true; } @Nullable @Override - public Bitmap takeSurfaceControlScreenshot(@NonNull SurfaceControl surfaceControl) { + public boolean takeSurfaceControlScreenshot(@NonNull SurfaceControl surfaceControl, + ScreenCapture.ScreenCaptureListener listener) { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); throwIfShutdownLocked(); throwIfNotConnectedLocked(); } - ScreenCapture.ScreenshotHardwareBuffer captureBuffer; final long identity = Binder.clearCallingIdentity(); try { - captureBuffer = ScreenCapture.captureLayers( + ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(surfaceControl) - .setChildrenOnly(false) - .build()); + .setChildrenOnly(false) + .build(); + int status = ScreenCapture.captureLayers(args, listener); + + if (status != 0) { + return false; + } } finally { Binder.restoreCallingIdentity(identity); } - if (captureBuffer == null) { - return null; - } - return captureBuffer.asBitmap(); + return true; } @Override diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index 0e78275fa30b..8dd50f0c42e8 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -250,6 +250,16 @@ public abstract class DevicePolicyManagerInternal { public abstract ComponentName getProfileOwnerAsUser(@UserIdInt int userId); /** + * Returns the device owner component for the device, or {@code null} if there is not one. + * + * @deprecated added temporarily to support Android Role permission granting. + * Please contact Android Enterprise Device Policy team before calling this function. + */ + @Deprecated + @Nullable + public abstract ComponentName getDeviceOwnerComponent(boolean callingUserOnly); + + /** * Returns the user id of the device owner, or {@link UserHandle#USER_NULL} if there is not one. */ @UserIdInt diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index 3d76b28a3ccc..d7195a76d873 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -65,7 +65,7 @@ public abstract class BroadcastReceiver { * thread of your app. * * <p>Note on threading: the state inside of this class is not itself - * thread-safe, however you can use it from any thread if you properly + * thread-safe. However, you can use it from any thread if you make * sure that you do not have races. Typically this means you will hand * the entire object to another thread, which will be solely responsible * for setting any results and finally calling {@link #finish()}. diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 5d076d487b30..6d82922484bc 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1413,7 +1413,7 @@ public abstract class Context { * </ul> * <p> * If a shared storage device is emulated (as determined by - * {@link Environment#isExternalStorageEmulated(File)}), it's contents are + * {@link Environment#isExternalStorageEmulated(File)}), its contents are * backed by a private user data partition, which means there is little * benefit to storing data here instead of the private directories returned * by {@link #getFilesDir()}, etc. @@ -1501,7 +1501,7 @@ public abstract class Context { * </ul> * <p> * If a shared storage device is emulated (as determined by - * {@link Environment#isExternalStorageEmulated(File)}), it's contents are + * {@link Environment#isExternalStorageEmulated(File)}), its contents are * backed by a private user data partition, which means there is little * benefit to storing data here instead of the private directories returned * by {@link #getFilesDir()}, etc. @@ -1812,7 +1812,7 @@ public abstract class Context { * </ul> * <p> * If a shared storage device is emulated (as determined by - * {@link Environment#isExternalStorageEmulated(File)}), it's contents are + * {@link Environment#isExternalStorageEmulated(File)}), its contents are * backed by a private user data partition, which means there is little * benefit to storing data here instead of the private directory returned by * {@link #getCacheDir()}. diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 47a5db8ea22a..3fdd023a6b68 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -770,6 +770,11 @@ interface IPackageManager { void setSplashScreenTheme(String packageName, String themeName, int userId); + int getUserMinAspectRatio(String packageName, int userId); + + @EnforcePermission("INSTALL_PACKAGES") + void setUserMinAspectRatio(String packageName, int userId, int aspectRatio); + List<String> getMimeGroup(String packageName, String group); boolean isAutoRevokeWhitelisted(String packageName); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 105b38a40825..4b883cd4c17b 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -3045,10 +3045,6 @@ public class PackageInstaller { * The update ownership enforcement can only be enabled on initial installation. Set * this to {@code true} on package update is a no-op. * - * Apps may opt themselves out of update ownership by setting the - * <a href="https://developer.android.com/guide/topics/manifest/manifest-element.html#allowupdateownership">android:alllowUpdateOwnership</a> - * attribute in their manifest to <code>false</code>. - * * Note: To enable the update ownership enforcement, the installer must have the * {@link android.Manifest.permission#ENFORCE_UPDATE_OWNERSHIP ENFORCE_UPDATE_OWNERSHIP} * permission. diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 8fafb18393ef..66aadac6295d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2342,6 +2342,64 @@ public abstract class PackageManager { */ public static final int INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST = -130; + /** + * App minimum aspect ratio set by the user which will override app-defined aspect ratio. + * + * @hide + */ + @IntDef(prefix = { "USER_MIN_ASPECT_RATIO_" }, value = { + USER_MIN_ASPECT_RATIO_UNSET, + USER_MIN_ASPECT_RATIO_SPLIT_SCREEN, + USER_MIN_ASPECT_RATIO_DISPLAY_SIZE, + USER_MIN_ASPECT_RATIO_4_3, + USER_MIN_ASPECT_RATIO_16_9, + USER_MIN_ASPECT_RATIO_3_2, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface UserMinAspectRatio {} + + /** + * No aspect ratio override has been set by user. + * + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_UNSET = 0; + + /** + * Aspect ratio override code: user forces app to split screen aspect ratio. This is adjusted to + * half of the screen without the split screen divider. + * + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_SPLIT_SCREEN = 1; + + /** + * Aspect ratio override code: user forces app to the aspect ratio of the device display size. + * This will be the portrait aspect ratio of the device if the app is portrait or the landscape + * aspect ratio of the device if the app is landscape. + * + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_DISPLAY_SIZE = 2; + + /** + * Aspect ratio override code: user forces app to 4:3 min aspect ratio + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_4_3 = 3; + + /** + * Aspect ratio override code: user forces app to 16:9 min aspect ratio + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_16_9 = 4; + + /** + * Aspect ratio override code: user forces app to 3:2 min aspect ratio + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_3_2 = 5; + /** @hide */ @IntDef(flag = true, prefix = { "DELETE_" }, value = { DELETE_KEEP_DATA, diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index d48e20e128b9..6baf91d720c3 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -34,6 +34,7 @@ import android.hardware.camera2.impl.CameraExtensionUtils; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; +import android.os.Binder; import android.os.ConditionVariable; import android.os.IBinder; import android.os.RemoteException; @@ -48,7 +49,6 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -346,28 +346,29 @@ public final class CameraExtensionCharacteristics { } } - public long registerClient(Context ctx) { + public boolean registerClient(Context ctx, IBinder token) { synchronized (mLock) { connectToProxyLocked(ctx); - if (mProxy != null) { - try { - return mProxy.registerClient(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to initialize extension! Extension service does " - + " not respond!"); - return -1; - } - } else { - return -1; + if (mProxy == null) { + return false; } + + try { + return mProxy.registerClient(token); + } catch (RemoteException e) { + Log.e(TAG, "Failed to initialize extension! Extension service does " + + " not respond!"); + } + + return false; } } - public void unregisterClient(long clientId) { + public void unregisterClient(IBinder token) { synchronized (mLock) { if (mProxy != null) { try { - mProxy.unregisterClient(clientId); + mProxy.unregisterClient(token); } catch (RemoteException e) { Log.e(TAG, "Failed to de-initialize extension! Extension service does" + " not respond!"); @@ -438,15 +439,15 @@ public final class CameraExtensionCharacteristics { /** * @hide */ - public static long registerClient(Context ctx) { - return CameraExtensionManagerGlobal.get().registerClient(ctx); + public static boolean registerClient(Context ctx, IBinder token) { + return CameraExtensionManagerGlobal.get().registerClient(ctx, token); } /** * @hide */ - public static void unregisterClient(long clientId) { - CameraExtensionManagerGlobal.get().unregisterClient(clientId); + public static void unregisterClient(IBinder token) { + CameraExtensionManagerGlobal.get().unregisterClient(token); } /** @@ -564,8 +565,9 @@ public final class CameraExtensionCharacteristics { */ public @NonNull List<Integer> getSupportedExtensions() { ArrayList<Integer> ret = new ArrayList<>(); - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#getSupportedExtensions:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { return Collections.unmodifiableList(ret); } @@ -576,7 +578,7 @@ public final class CameraExtensionCharacteristics { } } } finally { - unregisterClient(clientId); + unregisterClient(token); } return Collections.unmodifiableList(ret); @@ -599,8 +601,9 @@ public final class CameraExtensionCharacteristics { * supported device-specific extension */ public boolean isPostviewAvailable(@Extension int extension) { - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#isPostviewAvailable:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -623,7 +626,7 @@ public final class CameraExtensionCharacteristics { Log.e(TAG, "Failed to query the extension for postview availability! Extension " + "service does not respond!"); } finally { - unregisterClient(clientId); + unregisterClient(token); } return false; @@ -656,9 +659,9 @@ public final class CameraExtensionCharacteristics { @NonNull public List<Size> getPostviewSupportedSizes(@Extension int extension, @NonNull Size captureSize, int format) { - - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#getPostviewSupportedSizes:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -719,7 +722,7 @@ public final class CameraExtensionCharacteristics { + "service does not respond!"); return Collections.emptyList(); } finally { - unregisterClient(clientId); + unregisterClient(token); } } @@ -756,8 +759,9 @@ public final class CameraExtensionCharacteristics { // TODO: Revisit this code once the Extension preview processor output format // ambiguity is resolved in b/169799538. - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -787,7 +791,7 @@ public final class CameraExtensionCharacteristics { + " not respond!"); return new ArrayList<>(); } finally { - unregisterClient(clientId); + unregisterClient(token); } } @@ -814,8 +818,9 @@ public final class CameraExtensionCharacteristics { public @NonNull List<Size> getExtensionSupportedSizes(@Extension int extension, int format) { try { - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -867,7 +872,7 @@ public final class CameraExtensionCharacteristics { } } } finally { - unregisterClient(clientId); + unregisterClient(token); } } catch (RemoteException e) { Log.e(TAG, "Failed to query the extension supported sizes! Extension service does" @@ -888,7 +893,6 @@ public final class CameraExtensionCharacteristics { * @param format device-specific extension output format * @return the range of estimated minimal and maximal capture latency in milliseconds * or null if no capture latency info can be provided - * * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG} / * {@link ImageFormat#YUV_420_888}; or unsupported extension. */ @@ -903,8 +907,9 @@ public final class CameraExtensionCharacteristics { throw new IllegalArgumentException("Unsupported format: " + format); } - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#getEstimatedCaptureLatencyRangeMillis:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -952,7 +957,7 @@ public final class CameraExtensionCharacteristics { Log.e(TAG, "Failed to query the extension capture latency! Extension service does" + " not respond!"); } finally { - unregisterClient(clientId); + unregisterClient(token); } return null; @@ -968,8 +973,9 @@ public final class CameraExtensionCharacteristics { * @throws IllegalArgumentException in case of an unsupported extension. */ public boolean isCaptureProcessProgressAvailable(@Extension int extension) { - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#isCaptureProcessProgressAvailable:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -992,7 +998,7 @@ public final class CameraExtensionCharacteristics { Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does" + " not respond!"); } finally { - unregisterClient(clientId); + unregisterClient(token); } return false; @@ -1013,8 +1019,9 @@ public final class CameraExtensionCharacteristics { */ @NonNull public Set<CaptureRequest.Key> getAvailableCaptureRequestKeys(@Extension int extension) { - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#getAvailableCaptureRequestKeys:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -1033,10 +1040,11 @@ public final class CameraExtensionCharacteristics { } else { Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = initializeExtension(extension); - extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId)); + extenders.second.onInit(token, mCameraId, + mCharacteristicsMapNative.get(mCameraId)); extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); captureRequestMeta = extenders.second.getAvailableCaptureRequestKeys(); - extenders.second.onDeInit(); + extenders.second.onDeInit(token); } if (captureRequestMeta != null) { @@ -1067,7 +1075,7 @@ public final class CameraExtensionCharacteristics { } catch (RemoteException e) { throw new IllegalStateException("Failed to query the available capture request keys!"); } finally { - unregisterClient(clientId); + unregisterClient(token); } return Collections.unmodifiableSet(ret); @@ -1092,8 +1100,9 @@ public final class CameraExtensionCharacteristics { */ @NonNull public Set<CaptureResult.Key> getAvailableCaptureResultKeys(@Extension int extension) { - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#getAvailableCaptureResultKeys:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -1111,10 +1120,11 @@ public final class CameraExtensionCharacteristics { } else { Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = initializeExtension(extension); - extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId)); + extenders.second.onInit(token, mCameraId, + mCharacteristicsMapNative.get(mCameraId)); extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); captureResultMeta = extenders.second.getAvailableCaptureResultKeys(); - extenders.second.onDeInit(); + extenders.second.onDeInit(token); } if (captureResultMeta != null) { @@ -1126,7 +1136,7 @@ public final class CameraExtensionCharacteristics { } CameraCharacteristics resultChars = new CameraCharacteristics(captureResultMeta); Object crKey = CaptureResult.Key.class; - Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>)crKey; + Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>) crKey; ret.addAll(resultChars.getAvailableKeyList(CaptureResult.class, crKeyTyped, resultKeys, /*includeSynthetic*/ true)); @@ -1145,7 +1155,7 @@ public final class CameraExtensionCharacteristics { } catch (RemoteException e) { throw new IllegalStateException("Failed to query the available capture result keys!"); } finally { - unregisterClient(clientId); + unregisterClient(token); } return Collections.unmodifiableSet(ret); diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 85f8ca66715b..a098362f16aa 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -67,6 +67,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; @@ -178,22 +179,20 @@ public final class CameraManager { boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state); mFoldedDeviceState = folded; - ArrayList<WeakReference<DeviceStateListener>> invalidListeners = new ArrayList<>(); - for (WeakReference<DeviceStateListener> listener : mDeviceStateListeners) { - DeviceStateListener callback = listener.get(); + Iterator<WeakReference<DeviceStateListener>> it = mDeviceStateListeners.iterator(); + while(it.hasNext()) { + DeviceStateListener callback = it.next().get(); if (callback != null) { callback.onDeviceStateChanged(folded); } else { - invalidListeners.add(listener); + it.remove(); } } - if (!invalidListeners.isEmpty()) { - mDeviceStateListeners.removeAll(invalidListeners); - } } public synchronized void addDeviceStateListener(DeviceStateListener listener) { listener.onDeviceStateChanged(mFoldedDeviceState); + mDeviceStateListeners.removeIf(l -> l.get() == null); mDeviceStateListeners.add(new WeakReference<>(listener)); } diff --git a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl index b52c650086f4..3b7d801afd85 100644 --- a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl +++ b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl @@ -20,11 +20,13 @@ import android.hardware.camera2.extension.IPreviewExtenderImpl; import android.hardware.camera2.extension.IImageCaptureExtenderImpl; import android.hardware.camera2.extension.IInitializeSessionCallback; +import android.os.IBinder; + /** @hide */ interface ICameraExtensionsProxyService { - long registerClient(); - void unregisterClient(long clientId); + boolean registerClient(in IBinder token); + void unregisterClient(in IBinder token); boolean advancedExtensionsSupported(); void initializeSession(in IInitializeSessionCallback cb); void releaseSession(); diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl index 754f8f6625c9..5a2241820534 100644 --- a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl +++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl @@ -24,11 +24,13 @@ import android.hardware.camera2.extension.LatencyRange; import android.hardware.camera2.extension.Size; import android.hardware.camera2.extension.SizeList; +import android.os.IBinder; + /** @hide */ interface IImageCaptureExtenderImpl { - void onInit(in String cameraId, in CameraMetadataNative cameraCharacteristics); - void onDeInit(); + void onInit(in IBinder token, in String cameraId, in CameraMetadataNative cameraCharacteristics); + void onDeInit(in IBinder token); @nullable CaptureStageImpl onPresetSession(); @nullable CaptureStageImpl onEnableSession(); @nullable CaptureStageImpl onDisableSession(); diff --git a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl index 01046d01233c..9ea8a7407049 100644 --- a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl +++ b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl @@ -22,11 +22,13 @@ import android.hardware.camera2.extension.IPreviewImageProcessorImpl; import android.hardware.camera2.extension.IRequestUpdateProcessorImpl; import android.hardware.camera2.extension.SizeList; +import android.os.IBinder; + /** @hide */ interface IPreviewExtenderImpl { - void onInit(in String cameraId, in CameraMetadataNative cameraCharacteristics); - void onDeInit(); + void onInit(in IBinder token, in String cameraId, in CameraMetadataNative cameraCharacteristics); + void onDeInit(in IBinder token); @nullable CaptureStageImpl onPresetSession(); @nullable CaptureStageImpl onEnableSession(); @nullable CaptureStageImpl onDisableSession(); diff --git a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl index 13b93a8c5e92..0581ec08a131 100644 --- a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl +++ b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl @@ -25,13 +25,15 @@ import android.hardware.camera2.extension.LatencyPair; import android.hardware.camera2.extension.LatencyRange; import android.hardware.camera2.extension.OutputSurface; +import android.os.IBinder; + /** @hide */ interface ISessionProcessorImpl { - CameraSessionConfig initSession(in String cameraId, + CameraSessionConfig initSession(in IBinder token, in String cameraId, in Map<String, CameraMetadataNative> charsMap, in OutputSurface previewSurface, in OutputSurface imageCaptureSurface, in OutputSurface postviewSurface); - void deInitSession(); + void deInitSession(in IBinder token); void onCaptureSessionStart(IRequestProcessorImpl requestProcessor); void onCaptureSessionEnd(); int startRepeating(in ICaptureCallback callback); diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index 65d4b433f132..ae700a0a3c41 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -60,6 +60,7 @@ import android.media.ImageReader; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.util.Size; @@ -79,7 +80,6 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes private final Executor mExecutor; private CameraDevice mCameraDevice; private final Map<String, CameraMetadataNative> mCharacteristicsMap; - private final long mExtensionClientId; private final Handler mHandler; private final HandlerThread mHandlerThread; private final CameraExtensionSession.StateCallback mCallbacks; @@ -90,6 +90,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes private final HashMap<Integer, ImageReader> mReaderMap = new HashMap<>(); private RequestProcessor mRequestProcessor = new RequestProcessor(); private final int mSessionId; + private final IBinder mToken; private Surface mClientRepeatingRequestSurface; private Surface mClientCaptureSurface; @@ -114,8 +115,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes @NonNull Map<String, CameraCharacteristics> characteristicsMap, @NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId) throws CameraAccessException, RemoteException { - long clientId = CameraExtensionCharacteristics.registerClient(ctx); - if (clientId < 0) { + final IBinder token = new Binder(TAG + " : " + sessionId); + boolean success = CameraExtensionCharacteristics.registerClient(ctx, token); + if (!success) { throw new UnsupportedOperationException("Unsupported extension!"); } @@ -202,11 +204,10 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes IAdvancedExtenderImpl extender = CameraExtensionCharacteristics.initializeAdvancedExtension( config.getExtension()); extender.init(cameraId, characteristicsMapNative); - - CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(clientId, - extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface, + CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(extender, + cameraDevice, characteristicsMapNative, repeatingRequestSurface, burstCaptureSurface, postviewSurface, config.getStateCallback(), - config.getExecutor(), sessionId); + config.getExecutor(), sessionId, token); ret.mStatsAggregator.setClientName(ctx.getOpPackageName()); ret.mStatsAggregator.setExtensionType(config.getExtension()); @@ -216,15 +217,13 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes return ret; } - private CameraAdvancedExtensionSessionImpl(long extensionClientId, - @NonNull IAdvancedExtenderImpl extender, + private CameraAdvancedExtensionSessionImpl(@NonNull IAdvancedExtenderImpl extender, @NonNull CameraDeviceImpl cameraDevice, Map<String, CameraMetadataNative> characteristicsMap, @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface, @Nullable Surface postviewSurface, @NonNull StateCallback callback, @NonNull Executor executor, - int sessionId) { - mExtensionClientId = extensionClientId; + int sessionId, @NonNull IBinder token) { mAdvancedExtender = extender; mCameraDevice = cameraDevice; mCharacteristicsMap = characteristicsMap; @@ -240,6 +239,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes mSessionClosed = false; mInitializeHandler = new InitializeSessionHandler(); mSessionId = sessionId; + mToken = token; mInterfaceLock = cameraDevice.mInterfaceLock; mStatsAggregator = new ExtensionSessionStatsAggregator(mCameraDevice.getId(), @@ -260,7 +260,8 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes OutputSurface postviewSurface = initializeParcelable(mClientPostviewSurface); mSessionProcessor = mAdvancedExtender.getSessionProcessor(); - CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mCameraDevice.getId(), + CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mToken, + mCameraDevice.getId(), mCharacteristicsMap, previewSurface, captureSurface, postviewSurface); List<CameraOutputConfig> outputConfigs = sessionConfig.outputConfigs; ArrayList<OutputConfiguration> outputList = new ArrayList<>(); @@ -526,7 +527,11 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes synchronized (mInterfaceLock) { if (mInitialized) { try { - mCaptureSession.stopRepeating(); + try { + mCaptureSession.stopRepeating(); + } catch (IllegalStateException e) { + // OK: already be closed, nothing else to do + } mSessionProcessor.stopRepeating(); mSessionProcessor.onCaptureSessionEnd(); mSessionClosed = true; @@ -565,7 +570,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes if (!mSessionClosed) { mSessionProcessor.onCaptureSessionEnd(); } - mSessionProcessor.deInitSession(); + mSessionProcessor.deInitSession(mToken); } catch (RemoteException e) { Log.e(TAG, "Failed to de-initialize session processor, extension service" + " does not respond!") ; @@ -573,12 +578,10 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes mSessionProcessor = null; } - if (mExtensionClientId >= 0) { - CameraExtensionCharacteristics.unregisterClient(mExtensionClientId); - if (mInitialized || (mCaptureSession != null)) { - notifyClose = true; - CameraExtensionCharacteristics.releaseSession(); - } + CameraExtensionCharacteristics.unregisterClient(mToken); + if (mInitialized || (mCaptureSession != null)) { + notifyClose = true; + CameraExtensionCharacteristics.releaseSession(); } mInitialized = false; diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 9ebef0b59ece..1db4808b6430 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -56,6 +56,7 @@ import android.media.ImageWriter; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.util.LongSparseArray; @@ -79,7 +80,6 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private final Executor mExecutor; private final CameraDevice mCameraDevice; - private final long mExtensionClientId; private final IImageCaptureExtenderImpl mImageExtender; private final IPreviewExtenderImpl mPreviewExtender; private final Handler mHandler; @@ -91,6 +91,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private final Set<CaptureRequest.Key> mSupportedRequestKeys; private final Set<CaptureResult.Key> mSupportedResultKeys; private final ExtensionSessionStatsAggregator mStatsAggregator; + private final IBinder mToken; private boolean mCaptureResultsSupported; private CameraCaptureSession mCaptureSession = null; @@ -111,6 +112,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private int mPreviewProcessorType = IPreviewExtenderImpl.PROCESSOR_TYPE_NONE; private boolean mInitialized; + private boolean mSessionClosed; // Enable/Disable internal preview/(repeating request). Extensions expect // that preview/(repeating request) is enabled and active at any point in time. // In case the client doesn't explicitly enable repeating requests, the framework @@ -135,8 +137,9 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { @NonNull ExtensionSessionConfiguration config, int sessionId) throws CameraAccessException, RemoteException { - long clientId = CameraExtensionCharacteristics.registerClient(ctx); - if (clientId < 0) { + final IBinder token = new Binder(TAG + " : " + sessionId); + boolean success = CameraExtensionCharacteristics.registerClient(ctx, token); + if (!success) { throw new UnsupportedOperationException("Unsupported extension!"); } @@ -224,15 +227,16 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } extenders.first.init(cameraId, characteristicsMap.get(cameraId).getNativeMetadata()); - extenders.first.onInit(cameraId, characteristicsMap.get(cameraId).getNativeMetadata()); + extenders.first.onInit(token, cameraId, + characteristicsMap.get(cameraId).getNativeMetadata()); extenders.second.init(cameraId, characteristicsMap.get(cameraId).getNativeMetadata()); - extenders.second.onInit(cameraId, characteristicsMap.get(cameraId).getNativeMetadata()); + extenders.second.onInit(token, cameraId, + characteristicsMap.get(cameraId).getNativeMetadata()); CameraExtensionSessionImpl session = new CameraExtensionSessionImpl( extenders.second, extenders.first, supportedPreviewSizes, - clientId, cameraDevice, repeatingRequestSurface, burstCaptureSurface, @@ -240,6 +244,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { config.getStateCallback(), config.getExecutor(), sessionId, + token, extensionChars.getAvailableCaptureRequestKeys(config.getExtension()), extensionChars.getAvailableCaptureResultKeys(config.getExtension())); @@ -254,7 +259,6 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { public CameraExtensionSessionImpl(@NonNull IImageCaptureExtenderImpl imageExtender, @NonNull IPreviewExtenderImpl previewExtender, @NonNull List<Size> previewSizes, - long extensionClientId, @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice, @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface, @@ -262,9 +266,9 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { @NonNull StateCallback callback, @NonNull Executor executor, int sessionId, + @NonNull IBinder token, @NonNull Set<CaptureRequest.Key> requestKeys, @Nullable Set<CaptureResult.Key> resultKeys) { - mExtensionClientId = extensionClientId; mImageExtender = imageExtender; mPreviewExtender = previewExtender; mCameraDevice = cameraDevice; @@ -278,8 +282,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); mInitialized = false; + mSessionClosed = false; mInitializeHandler = new InitializeSessionHandler(); mSessionId = sessionId; + mToken = token; mSupportedRequestKeys = requestKeys; mSupportedResultKeys = resultKeys; mCaptureResultsSupported = !resultKeys.isEmpty(); @@ -775,7 +781,12 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { synchronized (mInterfaceLock) { if (mInitialized) { mInternalRepeatingRequestEnabled = false; - mCaptureSession.stopRepeating(); + try { + mCaptureSession.stopRepeating(); + } catch (IllegalStateException e) { + // OK: already be closed, nothing else to do + mSessionClosed = true; + } ArrayList<CaptureStageImpl> captureStageList = new ArrayList<>(); try { @@ -793,13 +804,14 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { Log.e(TAG, "Failed to disable extension! Extension service does not " + "respond!"); } - if (!captureStageList.isEmpty()) { + if (!captureStageList.isEmpty() && !mSessionClosed) { CaptureRequest disableRequest = createRequest(mCameraDevice, captureStageList, mCameraRepeatingSurface, CameraDevice.TEMPLATE_PREVIEW); mCaptureSession.capture(disableRequest, new CloseRequestHandler(mRepeatingRequestImageCallback), mHandler); } + mSessionClosed = true; mStatsAggregator.commit(/*isFinal*/true); // Commit stats before closing session mCaptureSession.close(); } @@ -854,19 +866,22 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mHandlerThread.quit(); try { - mPreviewExtender.onDeInit(); - mImageExtender.onDeInit(); + if (!mSessionClosed) { + // return value is omitted. nothing can do after session is closed. + mPreviewExtender.onDisableSession(); + mImageExtender.onDisableSession(); + } + mPreviewExtender.onDeInit(mToken); + mImageExtender.onDeInit(mToken); } catch (RemoteException e) { Log.e(TAG, "Failed to release extensions! Extension service does not" + " respond!"); } - if (mExtensionClientId >= 0) { - CameraExtensionCharacteristics.unregisterClient(mExtensionClientId); - if (mInitialized || (mCaptureSession != null)) { - notifyClose = true; - CameraExtensionCharacteristics.releaseSession(); - } + CameraExtensionCharacteristics.unregisterClient(mToken); + if (mInitialized || (mCaptureSession != null)) { + notifyClose = true; + CameraExtensionCharacteristics.releaseSession(); } mInitialized = false; diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 76efce56dcf0..022f3c4c3a20 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1762,6 +1762,24 @@ public final class DisplayManager { * 123,1,critical,0.8,default;123,1,moderate,0.6,id_2;456,2,moderate,0.9,critical,0.7 */ String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data"; + + /** + * Key for new power controller feature flag. If enabled new DisplayPowerController will + * be used. + * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)} + * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace. + * @hide + */ + String KEY_NEW_POWER_CONTROLLER = "use_newly_structured_display_power_controller"; + + /** + * Key for normal brightness mode controller feature flag. + * It enables NormalBrightnessModeController. + * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)} + * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace. + * @hide + */ + String KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER = "use_normal_brightness_mode_controller"; } /** diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 2f9c2073cd38..a9c4818393a8 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -52,6 +52,8 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; +import static java.lang.annotation.RetentionPolicy.SOURCE; + import android.annotation.AnyThread; import android.annotation.CallSuper; import android.annotation.DrawableRes; @@ -158,7 +160,6 @@ import com.android.internal.inputmethod.InputMethodNavButtonFlags; import com.android.internal.inputmethod.InputMethodPrivilegedOperations; import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry; import com.android.internal.inputmethod.SoftInputShowHideReason; -import com.android.internal.util.Preconditions; import com.android.internal.util.RingBuffer; import org.xmlpull.v1.XmlPullParserException; @@ -481,53 +482,43 @@ public class InputMethodService extends AbstractInputMethodService { public static final int BACK_DISPOSITION_ADJUST_NOTHING = 3; /** - * Enum values to be used for {@link #setBackDisposition(int)}. + * Enum flag to be used for {@link #setBackDisposition(int)}. * * @hide */ - @IntDef(prefix = { "BACK_DISPOSITION_" }, value = { - BACK_DISPOSITION_DEFAULT, - BACK_DISPOSITION_WILL_NOT_DISMISS, - BACK_DISPOSITION_WILL_DISMISS, - BACK_DISPOSITION_ADJUST_NOTHING, - }) - @Retention(RetentionPolicy.SOURCE) + @Retention(SOURCE) + @IntDef(value = {BACK_DISPOSITION_DEFAULT, BACK_DISPOSITION_WILL_NOT_DISMISS, + BACK_DISPOSITION_WILL_DISMISS, BACK_DISPOSITION_ADJUST_NOTHING}, + prefix = "BACK_DISPOSITION_") public @interface BackDispositionMode {} /** - * Enum flags to be used for {@link #setImeWindowStatus}, representing the current state of the - * IME window visibility. - * * @hide + * The IME is active. It may or may not be visible. */ - @IntDef(flag = true, prefix = { "IME_" }, value = { - IME_ACTIVE, - IME_VISIBLE, - IME_VISIBLE_IMPERCEPTIBLE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ImeWindowVisibility {} + public static final int IME_ACTIVE = 0x1; /** - * The IME is active. It may or may not be visible. * @hide + * The IME is perceptibly visible to the user. */ - public static final int IME_ACTIVE = 0x1; + public static final int IME_VISIBLE = 0x2; /** - * The IME is perceptibly visible to the user. * @hide + * The IME is active and ready with views but set invisible. + * This flag cannot be combined with {@link #IME_VISIBLE}. */ - public static final int IME_VISIBLE = 0x2; + public static final int IME_INVISIBLE = 0x4; /** + * @hide * The IME is visible, but not yet perceptible to the user (e.g. fading in) * by {@link android.view.WindowInsetsController}. * * @see InputMethodManager#reportPerceptible - * @hide */ - public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x4; + public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x8; // Min and max values for back disposition. private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT; @@ -640,18 +631,9 @@ public class InputMethodService extends AbstractInputMethodService { int mStatusIcon; - /** - * Latest value reported of back disposition mode. - */ @BackDispositionMode int mBackDisposition; - /** - * Latest value reported of IME window visibility flags. - */ - @ImeWindowVisibility - private int mImeWindowVisibility; - private Object mLock = new Object(); @GuardedBy("mLock") private boolean mNotifyUserActionSent; @@ -1228,14 +1210,8 @@ public class InputMethodService extends AbstractInputMethodService { mImeSurfaceRemoverRunnable = null; } - private void setImeWindowStatus(@ImeWindowVisibility int vis, - @BackDispositionMode int backDisposition) { - if (vis == mImeWindowVisibility && backDisposition == mBackDisposition) { - return; - } - mImeWindowVisibility = Preconditions.checkFlagsArgument(vis, IME_ACTIVE | IME_VISIBLE); - mBackDisposition = backDisposition; - mPrivOps.setImeWindowStatusAsync(mImeWindowVisibility, mBackDisposition); + private void setImeWindowStatus(int visibilityFlags, int backDisposition) { + mPrivOps.setImeWindowStatusAsync(visibilityFlags, backDisposition); } /** Set region of the keyboard to be avoided from back gesture */ @@ -1909,11 +1885,15 @@ public class InputMethodService extends AbstractInputMethodService { * @param disposition disposition mode to be set */ public void setBackDisposition(@BackDispositionMode int disposition) { - if (disposition < BACK_DISPOSITION_MIN || disposition > BACK_DISPOSITION_MAX) { + if (disposition == mBackDisposition) { + return; + } + if (disposition > BACK_DISPOSITION_MAX || disposition < BACK_DISPOSITION_MIN) { Log.e(TAG, "Invalid back disposition value (" + disposition + ") specified."); return; } - setImeWindowStatus(mImeWindowVisibility, disposition); + mBackDisposition = disposition; + setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); } /** @@ -2887,8 +2867,14 @@ public class InputMethodService extends AbstractInputMethodService { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showWindow"); mDecorViewWasVisible = mDecorViewVisible; mInShowWindow = true; + final int previousImeWindowStatus = + (mDecorViewVisible ? IME_ACTIVE : 0) | (isInputViewShown() + ? (!mWindowVisible ? IME_INVISIBLE : IME_VISIBLE) : 0); startViews(prepareWindow(showInput)); - setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); + final int nextImeWindowStatus = mapToImeWindowStatus(); + if (previousImeWindowStatus != nextImeWindowStatus) { + setImeWindowStatus(nextImeWindowStatus, mBackDisposition); + } mNavigationBarController.onWindowShown(); // compute visibility @@ -4099,9 +4085,9 @@ public class InputMethodService extends AbstractInputMethodService { }; } - @ImeWindowVisibility private int mapToImeWindowStatus() { - return IME_ACTIVE | (mDecorViewVisible ? IME_VISIBLE : 0); + return IME_ACTIVE + | (isInputViewShown() ? IME_VISIBLE : 0); } private boolean isAutomotive() { diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index c9073fa4b72c..7664bada2c28 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -112,9 +112,19 @@ public class GraphicsEnvironment { private static final int ANGLE_GL_DRIVER_ALL_ANGLE_OFF = 0; // Values for ANGLE_GL_DRIVER_SELECTION_VALUES - private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default"; - private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle"; - private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native"; + private enum AngleDriverChoice { + DEFAULT("default"), + ANGLE("angle"), + NATIVE("native"); + + public final String choice; + + AngleDriverChoice(String choice) { + this.choice = choice; + } + } + + private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported"; private ClassLoader mClassLoader; private String mLibrarySearchPaths; @@ -193,15 +203,16 @@ public class GraphicsEnvironment { } /** - * Query to determine if ANGLE should be used + * Query to determine the ANGLE driver choice. */ - private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) { + private AngleDriverChoice queryAngleChoice(Context context, Bundle coreSettings, + String packageName) { if (TextUtils.isEmpty(packageName)) { Log.v(TAG, "No package name specified; use the system driver"); - return false; + return AngleDriverChoice.DEFAULT; } - return shouldUseAngleInternal(context, coreSettings, packageName); + return queryAngleChoiceInternal(context, coreSettings, packageName); } private int getVulkanVersion(PackageManager pm) { @@ -422,10 +433,11 @@ public class GraphicsEnvironment { * forces a choice; * 3) Use ANGLE if isAngleEnabledByGameMode() returns true; */ - private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) { + private AngleDriverChoice queryAngleChoiceInternal(Context context, Bundle bundle, + String packageName) { // Make sure we have a good package name if (TextUtils.isEmpty(packageName)) { - return false; + return AngleDriverChoice.DEFAULT; } // Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE @@ -440,7 +452,7 @@ public class GraphicsEnvironment { } if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) { Log.v(TAG, "Turn on ANGLE for all applications."); - return true; + return AngleDriverChoice.ANGLE; } // Get the per-application settings lists @@ -463,7 +475,7 @@ public class GraphicsEnvironment { + optInPackages.size() + ", " + "number of values: " + optInValues.size()); - return mEnabledByGameMode; + return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT; } // See if this application is listed in the per-application settings list @@ -471,7 +483,7 @@ public class GraphicsEnvironment { if (pkgIndex < 0) { Log.v(TAG, packageName + " is not listed in per-application setting"); - return mEnabledByGameMode; + return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT; } mAngleOptInIndex = pkgIndex; @@ -481,14 +493,14 @@ public class GraphicsEnvironment { Log.v(TAG, "ANGLE Developer option for '" + packageName + "' " + "set to: '" + optInValue + "'"); - if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) { - return true; - } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) { - return false; + if (optInValue.equals(AngleDriverChoice.ANGLE.choice)) { + return AngleDriverChoice.ANGLE; + } else if (optInValue.equals(AngleDriverChoice.NATIVE.choice)) { + return AngleDriverChoice.NATIVE; } else { // The user either chose default or an invalid value; go with the default driver or what // the game mode indicates - return mEnabledByGameMode; + return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT; } } @@ -501,10 +513,12 @@ public class GraphicsEnvironment { final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY); if (resolveInfos.size() != 1) { - Log.e(TAG, "Invalid number of ANGLE packages. Required: 1, Found: " + Log.v(TAG, "Invalid number of ANGLE packages. Required: 1, Found: " + resolveInfos.size()); - for (ResolveInfo resolveInfo : resolveInfos) { - Log.e(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName); + if (DEBUG) { + for (ResolveInfo resolveInfo : resolveInfos) { + Log.d(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName); + } } return ""; } @@ -539,26 +553,48 @@ public class GraphicsEnvironment { } /** - * Determine whether ANGLE should be used, set it up if so, and pass ANGLE details down to - * the C++ GraphicsEnv class. - * - * If ANGLE will be used, GraphicsEnv::setAngleInfo() will be called to enable ANGLE to be - * properly used. + * Determine whether ANGLE should be used, attempt to set up from apk first, if ANGLE can be + * set up from apk, pass ANGLE details down to the C++ GraphicsEnv class via + * GraphicsEnv::setAngleInfo(). If apk setup fails, attempt to set up to use system ANGLE. + * Return false if both fail. * - * @param context - * @param bundle - * @param pm + * @param context - Context of the application. + * @param bundle - Bundle of the application. + * @param packageManager - PackageManager of the application process. * @param packageName - package name of the application. - * @return true: ANGLE setup successfully - * false: ANGLE not setup (not on allowlist, ANGLE not present, etc.) + * @return true: can set up to use ANGLE successfully. + * false: can not set up to use ANGLE (not on allowlist, ANGLE not present, etc.) */ - private boolean setupAngle(Context context, Bundle bundle, PackageManager pm, + private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager, String packageName) { - if (!shouldUseAngle(context, bundle, packageName)) { + final AngleDriverChoice angleDriverChoice = queryAngleChoice(context, bundle, packageName); + if (angleDriverChoice == AngleDriverChoice.DEFAULT) { return false; } + if (queryAngleChoice(context, bundle, packageName) == AngleDriverChoice.NATIVE) { + nativeSetAngleInfo("", true, packageName, null); + return false; + } + + return setupAngleFromApk(context, bundle, packageManager, packageName) + || setupAngleFromSystem(context, bundle, packageName); + } + + /** + * Attempt to set up ANGLE from the packaged apk, if the apk can be found, pass ANGLE details to + * the C++ GraphicsEnv class. + * + * @param context - Context of the application. + * @param bundle - Bundle of the application. + * @param packageManager - PackageManager of the application process. + * @param packageName - package name of the application. + * @return true: can set up to use ANGLE apk. + * false: can not set up to use ANGLE apk (ANGLE apk not present, etc.) + */ + private boolean setupAngleFromApk(Context context, Bundle bundle, PackageManager packageManager, + String packageName) { ApplicationInfo angleInfo = null; // If the developer has specified a debug package over ADB, attempt to find it @@ -567,7 +603,7 @@ public class GraphicsEnvironment { Log.v(TAG, "ANGLE debug package enabled: " + anglePkgName); try { // Note the debug package does not have to be pre-installed - angleInfo = pm.getApplicationInfo(anglePkgName, 0); + angleInfo = packageManager.getApplicationInfo(anglePkgName, 0); } catch (PackageManager.NameNotFoundException e) { // If the debug package is specified but not found, abort. Log.v(TAG, "ANGLE debug package '" + anglePkgName + "' not installed"); @@ -577,7 +613,7 @@ public class GraphicsEnvironment { // Otherwise, check to see if ANGLE is properly installed if (angleInfo == null) { - anglePkgName = getAnglePackageName(pm); + anglePkgName = getAnglePackageName(packageManager); if (TextUtils.isEmpty(anglePkgName)) { Log.v(TAG, "Failed to find ANGLE package."); return false; @@ -586,7 +622,7 @@ public class GraphicsEnvironment { Log.v(TAG, "ANGLE package enabled: " + anglePkgName); try { // Production ANGLE libraries must be pre-installed as a system app - angleInfo = pm.getApplicationInfo(anglePkgName, + angleInfo = packageManager.getApplicationInfo(anglePkgName, PackageManager.MATCH_SYSTEM_ONLY); } catch (PackageManager.NameNotFoundException e) { Log.v(TAG, "ANGLE package '" + anglePkgName + "' not installed"); @@ -607,15 +643,39 @@ public class GraphicsEnvironment { Log.d(TAG, "ANGLE package libs: " + paths); } - // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name, - // and features to use. + // If we make it to here, ANGLE apk will be used. Call nativeSetAngleInfo() with the + // application package name and ANGLE features to use. final String[] features = getAngleEglFeatures(context, bundle); - setAngleInfo(paths, packageName, ANGLE_GL_DRIVER_CHOICE_ANGLE, features); + nativeSetAngleInfo(paths, false, packageName, features); return true; } /** + * Attempt to set up ANGLE from system, if the apk can be found, pass ANGLE details to + * the C++ GraphicsEnv class. + * + * @param context - Context of the application. + * @param bundle - Bundle of the application. + * @param packageName - package name of the application. + * @return true: can set up to use system ANGLE. + * false: can not set up to use system ANGLE because it doesn't exist. + */ + private boolean setupAngleFromSystem(Context context, Bundle bundle, String packageName) { + final boolean systemAngleSupported = SystemProperties + .getBoolean(PROPERTY_RO_ANGLE_SUPPORTED, false); + if (!systemAngleSupported) { + return false; + } + + // If we make it to here, system ANGLE will be used. Call nativeSetAngleInfo() with + // the application package name and ANGLE features to use. + final String[] features = getAngleEglFeatures(context, bundle); + nativeSetAngleInfo("system", false, packageName, features); + return true; + } + + /** * Determine if the "ANGLE In Use" dialog box should be shown. */ private boolean shouldShowAngleInUseDialogBox(Context context) { @@ -651,7 +711,9 @@ public class GraphicsEnvironment { final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE); final String anglePkg = getAnglePackageName(context.getPackageManager()); - intent.setPackage(anglePkg); + if (anglePkg.isEmpty()) { + return; + } context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { @Override @@ -890,8 +952,8 @@ public class GraphicsEnvironment { private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries); private static native void setGpuStats(String driverPackageName, String driverVersionName, long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion); - private static native void setAngleInfo(String path, String packageName, - String devOptIn, String[] features); + private static native void nativeSetAngleInfo(String path, boolean useNativeDriver, + String packageName, String[] features); private static native boolean setInjectLayersPrSetDumpable(); private static native void nativeToggleAngleAsSystemDriver(boolean enabled); diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index cc5426631a7b..5c4aa4a233fc 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -52,8 +52,20 @@ ], "name": "FrameworksServicesTests", "options": [ - { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, - { "include-filter": "com.android.server.power.stats.BatteryStatsTests" } + { "include-filter": "com.android.server.am.BatteryStatsServiceTest" } + ] + }, + { + "file_patterns": [ + "BatteryStats[^/]*\\.java", + "BatteryUsageStats[^/]*\\.java", + "PowerComponents\\.java", + "[^/]*BatteryConsumer[^/]*\\.java" + ], + "name": "FrameworksServicesTests", + "options": [ + { "include-filter": "com.android.server.power.stats" }, + { "exclude-filter": "com.android.server.power.stats.BatteryStatsTests" } ] }, { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d425bf8ae557..820f454abe80 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -12613,6 +12613,26 @@ public final class Settings { public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on"; /** + * The duration in milliseconds of each action, separated by commas. Ex: + * + * "18000,18000,18000,18000,0" + * + * See com.android.internal.telephony.data.DataStallRecoveryManager for more info + * @hide + */ + public static final String DSRM_DURATION_MILLIS = "dsrm_duration_millis"; + + /** + * The list of DSRM enabled actions, separated by commas. Ex: + * + * "true,true,false,true,true" + * + * See com.android.internal.telephony.data.DataStallRecoveryManager for more info + * @hide + */ + public static final String DSRM_ENABLED_ACTIONS = "dsrm_enabled_actions"; + + /** * Whether the wifi data connection should remain active even when higher * priority networks like Ethernet are active, to keep both networks. * In the case where higher priority networks are connected, wifi will be diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 5e7f5d62e256..9b19937444bd 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -26,7 +26,6 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.TestApi; import android.app.Activity; -import android.app.ActivityTaskManager; import android.app.AlarmManager; import android.app.Service; import android.compat.annotation.UnsupportedAppUsage; @@ -1268,9 +1267,7 @@ public class DreamService extends Service implements Window.Callback { fetchDreamLabel(this, serviceInfo, isPreviewMode)); try { - if (!ActivityTaskManager.getService().startDreamActivity(i)) { - detach(); - } + mDreamManager.startDreamActivity(i); } catch (SecurityException e) { Log.w(mTag, "Received SecurityException trying to start DreamActivity. " diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index 609425c95b0d..dd8b3deabc01 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -17,6 +17,7 @@ package android.service.dreams; import android.content.ComponentName; +import android.content.Intent; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.IBinder; @@ -45,4 +46,5 @@ interface IDreamManager { void setDreamComponentsForUser(int userId, in ComponentName[] componentNames); void setSystemDreamComponent(in ComponentName componentName); void registerDreamOverlayService(in ComponentName componentName); + void startDreamActivity(in Intent intent); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index dbc1be141571..8eb02a16350d 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -432,8 +432,7 @@ public abstract class WallpaperService extends Service { @Override public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, InsetsState insetsState, - boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, - int syncSeqId, boolean dragResizing) { + boolean forceLayout, int displayId, int syncSeqId, boolean dragResizing) { Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED, reportDraw ? 1 : 0, mergedConfiguration); @@ -1287,9 +1286,9 @@ public abstract class WallpaperService extends Service { visibleFrame.intersect(mInsetsState.getDisplayFrame()); WindowInsets windowInsets = mInsetsState.calculateInsets(visibleFrame, null /* ignoringVisibilityState */, config.isScreenRound(), - false /* alwaysConsumeSystemBars */, mLayout.softInputMode, - mLayout.flags, SYSTEM_UI_FLAG_VISIBLE, mLayout.type, - config.windowConfiguration.getWindowingMode(), null /* idSideMap */); + mLayout.softInputMode, mLayout.flags, SYSTEM_UI_FLAG_VISIBLE, + mLayout.type, config.windowConfiguration.getWindowingMode(), + null /* idSideMap */); if (!fixedSize) { final Rect padding = mIWallpaperEngine.mDisplayPadding; diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 6371da4f3776..ab9cff078ac4 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -25,7 +25,6 @@ import android.graphics.Paint; import android.graphics.text.LineBreakConfig; import android.graphics.text.LineBreaker; import android.os.Build; -import android.os.SystemProperties; import android.text.style.LeadingMarginSpan; import android.text.style.LeadingMarginSpan.LeadingMarginSpan2; import android.text.style.LineHeightSpan; @@ -33,7 +32,6 @@ import android.text.style.TabStopSpan; import android.util.Log; import android.util.Pools.SynchronizedPool; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; @@ -75,13 +73,6 @@ public class StaticLayout extends Layout { * default values. */ public final static class Builder { - // The content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE. - private static final int DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE = 3; - - // The property of content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE. - private static final String PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE = - "android.phrase.linecount.threshold"; - private Builder() {} /** @@ -440,55 +431,11 @@ public class StaticLayout extends Layout { */ @NonNull public StaticLayout build() { - reviseLineBreakConfig(); StaticLayout result = new StaticLayout(this); Builder.recycle(this); return result; } - private void reviseLineBreakConfig() { - boolean autoPhraseBreaking = mLineBreakConfig.getAutoPhraseBreaking(); - int wordStyle = mLineBreakConfig.getLineBreakWordStyle(); - if (autoPhraseBreaking) { - if (wordStyle != LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) { - if (shouldEnablePhraseBreaking()) { - mLineBreakConfig = LineBreakConfig.getLineBreakConfig( - mLineBreakConfig.getLineBreakStyle(), - LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE, - mLineBreakConfig.getAutoPhraseBreaking()); - } - } - } - } - - private boolean shouldEnablePhraseBreaking() { - if (TextUtils.isEmpty(mText) || mWidth <= 0) { - return false; - } - int lineLimit = SystemProperties.getInt( - PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE, - DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE); - double desiredWidth = (double) Layout.getDesiredWidth(mText, mStart, - mEnd, mPaint, mTextDir); - int lineCount = (int) Math.ceil(desiredWidth / mWidth); - if (lineCount > 0 && lineCount <= lineLimit) { - return true; - } - return false; - } - - /** - * Get the line break word style. - * - * @return The current line break word style. - * - * @hide - */ - @VisibleForTesting - public int getLineBreakWordStyle() { - return mLineBreakConfig.getLineBreakWordStyle(); - } - private CharSequence mText; private int mStart; private int mEnd; diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index ff7d8bb956ca..0071a0d00105 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -86,9 +86,6 @@ public class FeatureFlagUtils { public static final String SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST = "settings_need_connected_ble_device_for_broadcast"; - /** @hide */ - public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping"; - /** * Enable new language and keyboard settings UI * @hide @@ -225,7 +222,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true"); DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true"); DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true"); - DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true"); @@ -253,7 +249,6 @@ public class FeatureFlagUtils { PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN); PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS); PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME); - PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD); diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index d554514349c3..33edc27118f2 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -56,8 +56,7 @@ oneway interface IWindow { void resized(in ClientWindowFrames frames, boolean reportDraw, in MergedConfiguration newMergedConfiguration, in InsetsState insetsState, - boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, - int syncSeqId, boolean dragResizing); + boolean forceLayout, int displayId, int syncSeqId, boolean dragResizing); /** * Called when this window retrieved control over a specified set of insets sources. diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 6d96bb9423c5..8813c397b1e4 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -737,10 +737,8 @@ interface IWindowManager /** * Called to get the expected window insets. - * - * @return {@code true} if system bars are always consumed. */ - boolean getWindowInsets(int displayId, in IBinder token, out InsetsState outInsetsState); + void getWindowInsets(int displayId, in IBinder token, out InsetsState outInsetsState); /** * Returns a list of {@link android.view.DisplayInfo} for the logical display. This is not diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 6c5f195ba2a0..4c50858b13b2 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -40,6 +40,7 @@ import static android.view.InsetsState.ISIDE_LEFT; import static android.view.InsetsState.ISIDE_RIGHT; import static android.view.InsetsState.ISIDE_TOP; import static android.view.WindowInsets.Type.ime; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY; import static android.view.inputmethod.ImeTracker.TOKEN_NONE; @@ -63,7 +64,6 @@ import android.view.InsetsState.InternalInsetsSide; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; -import android.view.WindowManager.LayoutParams; import android.view.animation.Interpolator; import android.view.inputmethod.ImeTracker; @@ -401,8 +401,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro private Insets getInsetsFromState(InsetsState state, Rect frame, @Nullable @InternalInsetsSide SparseIntArray idSideMap) { return state.calculateInsets(frame, null /* ignoringVisibilityState */, - false /* isScreenRound */, false /* alwaysConsumeSystemBars */, - LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/, + false /* isScreenRound */, SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode */, 0 /* legacyWindowFlags */, 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, idSideMap).getInsets(mTypes); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 5019b85ca503..e5d031b7a51c 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -797,9 +797,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/, - mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeSystemBars(), - mLastLegacySoftInputMode, mLastLegacyWindowFlags, mLastLegacySystemUiFlags, - mWindowType, mLastWindowingMode, null /* idSideMap */); + mLastInsets.isRound(), mLastLegacySoftInputMode, mLastLegacyWindowFlags, + mLastLegacySystemUiFlags, mWindowType, mLastWindowingMode, + null /* idSideMap */); mHost.dispatchWindowInsetsAnimationProgress(insets, Collections.unmodifiableList(runningAnimations)); if (DEBUG) { @@ -955,21 +955,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } /** - * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, boolean, int, int, int, int, - * int, android.util.SparseIntArray) + * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, int, int, int, int, int, + * android.util.SparseIntArray) */ @VisibleForTesting - public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars, - int windowType, int windowingMode, int legacySoftInputMode, int legacyWindowFlags, - int legacySystemUiFlags) { + public WindowInsets calculateInsets(boolean isScreenRound, int windowType, int windowingMode, + int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags) { mWindowType = windowType; mLastWindowingMode = windowingMode; mLastLegacySoftInputMode = legacySoftInputMode; mLastLegacyWindowFlags = legacyWindowFlags; mLastLegacySystemUiFlags = legacySystemUiFlags; mLastInsets = mState.calculateInsets(mFrame, null /* ignoringVisibilityState*/, - isScreenRound, alwaysConsumeSystemBars, legacySoftInputMode, legacyWindowFlags, - legacySystemUiFlags, windowType, windowingMode, null /* idSideMap */); + isScreenRound, legacySoftInputMode, legacyWindowFlags, legacySystemUiFlags, + windowType, windowingMode, null /* idSideMap */); return mLastInsets; } diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index e10184976abe..64411866f020 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -68,10 +68,16 @@ public class InsetsSource implements Parcelable { */ public static final int FLAG_INSETS_ROUNDED_CORNER = 1 << 1; + /** + * Controls whether the insets provided by this source should be forcibly consumed. + */ + public static final int FLAG_FORCE_CONSUMING = 1 << 2; + @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = "FLAG_", value = { FLAG_SUPPRESS_SCRIM, FLAG_INSETS_ROUNDED_CORNER, + FLAG_FORCE_CONSUMING, }) public @interface Flags {} @@ -328,6 +334,9 @@ public class InsetsSource implements Parcelable { if ((flags & FLAG_INSETS_ROUNDED_CORNER) != 0) { joiner.add("INSETS_ROUNDED_CORNER"); } + if ((flags & FLAG_FORCE_CONSUMING) != 0) { + joiner.add("FORCE_CONSUMING"); + } return joiner.toString(); } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index dceae90822b8..828938b99e8f 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER; import static android.view.InsetsStateProto.DISPLAY_CUTOUT; import static android.view.InsetsStateProto.DISPLAY_FRAME; @@ -39,6 +40,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.WindowConfiguration; +import android.app.WindowConfiguration.WindowingMode; import android.graphics.Insets; import android.graphics.Rect; import android.os.Parcel; @@ -135,21 +137,26 @@ public class InsetsState implements Parcelable { * @return The calculated insets. */ public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState, - boolean isScreenRound, boolean alwaysConsumeSystemBars, - int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags, - int windowType, @WindowConfiguration.WindowingMode int windowingMode, + boolean isScreenRound, int legacySoftInputMode, int legacyWindowFlags, + int legacySystemUiFlags, int windowType, @WindowingMode int windowingMode, @Nullable @InternalInsetsSide SparseIntArray idSideMap) { Insets[] typeInsetsMap = new Insets[Type.SIZE]; Insets[] typeMaxInsetsMap = new Insets[Type.SIZE]; boolean[] typeVisibilityMap = new boolean[Type.SIZE]; final Rect relativeFrame = new Rect(frame); final Rect relativeFrameMax = new Rect(frame); + @InsetsType int forceConsumingTypes = 0; @InsetsType int suppressScrimTypes = 0; for (int i = mSources.size() - 1; i >= 0; i--) { final InsetsSource source = mSources.valueAt(i); + final @InsetsType int type = source.getType(); + + if ((source.getFlags() & InsetsSource.FLAG_FORCE_CONSUMING) != 0) { + forceConsumingTypes |= type; + } if ((source.getFlags() & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) { - suppressScrimTypes |= source.getType(); + suppressScrimTypes |= type; } processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap, @@ -157,7 +164,7 @@ public class InsetsState implements Parcelable { // IME won't be reported in max insets as the size depends on the EditorInfo of the IME // target. - if (source.getType() != WindowInsets.Type.ime()) { + if (type != WindowInsets.Type.ime()) { InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null ? ignoringVisibilityState.peekSource(source.getId()) : source; @@ -178,13 +185,13 @@ public class InsetsState implements Parcelable { if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) { compatInsetsTypes &= ~statusBars(); } - if (clearsCompatInsets(windowType, legacyWindowFlags, windowingMode) - && !alwaysConsumeSystemBars) { - compatInsetsTypes = 0; + if (clearsCompatInsets(windowType, legacyWindowFlags, windowingMode)) { + // Clear all types but forceConsumingTypes. + compatInsetsTypes &= forceConsumingTypes; } return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound, - alwaysConsumeSystemBars, suppressScrimTypes, calculateRelativeCutout(frame), + forceConsumingTypes, suppressScrimTypes, calculateRelativeCutout(frame), calculateRelativeRoundedCorners(frame), calculateRelativePrivacyIndicatorBounds(frame), calculateRelativeDisplayShape(frame), @@ -290,9 +297,8 @@ public class InsetsState implements Parcelable { public Insets calculateVisibleInsets(Rect frame, int windowType, int windowingMode, @SoftInputModeFlags int softInputMode, int windowFlags) { - if (clearsCompatInsets(windowType, windowFlags, windowingMode)) { - return Insets.NONE; - } + final boolean clearsCompatInsets = clearsCompatInsets( + windowType, windowFlags, windowingMode); final int softInputAdjustMode = softInputMode & SOFT_INPUT_MASK_ADJUST; final int visibleInsetsTypes = softInputAdjustMode != SOFT_INPUT_ADJUST_NOTHING ? systemBars() | ime() @@ -303,6 +309,9 @@ public class InsetsState implements Parcelable { if ((source.getType() & visibleInsetsTypes) == 0) { continue; } + if (clearsCompatInsets && !source.hasFlags(FLAG_FORCE_CONSUMING)) { + continue; + } insets = Insets.max(source.calculateVisibleInsets(frame), insets); } return insets; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f1cde3b4bb7e..f51e3a369e9b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -30744,13 +30744,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final Rect mCaptionInsets = new Rect(); /** - * In multi-window we force show the system bars. Because we don't want that the surface - * size changes in this mode, we instead have a flag whether the system bars sizes should - * always be consumed, so the app is treated like there are no virtual system bars at all. - */ - boolean mAlwaysConsumeSystemBars; - - /** * The internal insets given by this window. This value is * supplied by the client (through * {@link ViewTreeObserver.OnComputeInternalInsetsListener}) and will diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 9316dbf8a458..ef76f4ad77fb 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -82,7 +82,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS; import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW; -import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; @@ -741,7 +740,6 @@ public final class ViewRootImpl implements ViewParent, final Rect mPendingBackDropFrame = new Rect(); - boolean mPendingAlwaysConsumeSystemBars; private int mRelayoutSeq; private final Rect mWinFrameInScreen = new Rect(); private final InsetsState mTempInsets = new InsetsState(); @@ -1366,9 +1364,6 @@ public final class ViewRootImpl implements ViewParent, } } - mAttachInfo.mAlwaysConsumeSystemBars = - (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0; - mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars; mInsetsController.onStateChanged(mTempInsets); mInsetsController.onControlsChanged(mTempControls.get()); final InsetsState state = mInsetsController.getState(); @@ -1882,8 +1877,8 @@ public final class ViewRootImpl implements ViewParent, final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg2; CompatibilityInfo.applyOverrideScaleIfNeeded(mergedConfiguration); final boolean forceNextWindowRelayout = args.argi1 != 0; - final int displayId = args.argi3; - final boolean dragResizing = args.argi5 != 0; + final int displayId = args.argi2; + final boolean dragResizing = args.argi4 != 0; final Rect frame = frames.frame; final Rect displayFrame = frames.displayFrame; @@ -1935,8 +1930,7 @@ public final class ViewRootImpl implements ViewParent, } mForceNextWindowRelayout |= forceNextWindowRelayout; - mPendingAlwaysConsumeSystemBars = args.argi2 != 0; - mSyncSeqId = args.argi4 > mSyncSeqId ? args.argi4 : mSyncSeqId; + mSyncSeqId = args.argi3 > mSyncSeqId ? args.argi3 : mSyncSeqId; if (msg == MSG_RESIZED_REPORT) { reportNextDraw("resized"); @@ -2821,8 +2815,8 @@ public final class ViewRootImpl implements ViewParent, if (mLastWindowInsets == null || forceConstruct) { final Configuration config = getConfiguration(); mLastWindowInsets = mInsetsController.calculateInsets( - config.isScreenRound(), mAttachInfo.mAlwaysConsumeSystemBars, - mWindowAttributes.type, config.windowConfiguration.getWindowingMode(), + config.isScreenRound(), mWindowAttributes.type, + config.windowConfiguration.getWindowingMode(), mWindowAttributes.softInputMode, mWindowAttributes.flags, (mWindowAttributes.systemUiVisibility | mWindowAttributes.subtreeSystemUiVisibility)); @@ -3284,8 +3278,6 @@ public final class ViewRootImpl implements ViewParent, surfaceSizeChanged = true; mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y); } - final boolean alwaysConsumeSystemBarsChanged = - mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars; updateColorModeIfNeeded(lp.getColorMode()); surfaceCreated = !hadSurface && mSurface.isValid(); surfaceDestroyed = hadSurface && !mSurface.isValid(); @@ -3299,10 +3291,6 @@ public final class ViewRootImpl implements ViewParent, if (surfaceReplaced) { mSurfaceSequenceId++; } - if (alwaysConsumeSystemBarsChanged) { - mAttachInfo.mAlwaysConsumeSystemBars = mPendingAlwaysConsumeSystemBars; - dispatchApplyInsets = true; - } if (updateCaptionInsets()) { dispatchApplyInsets = true; } @@ -3855,7 +3843,15 @@ public final class ViewRootImpl implements ViewParent, mWmsRequestSyncGroupState = WMS_SYNC_PENDING; mWmsRequestSyncGroup = new SurfaceSyncGroup("wmsSync-" + mTag, t -> { mWmsRequestSyncGroupState = WMS_SYNC_MERGED; - reportDrawFinished(t, seqId); + // See b/286355097. If the current process is not system, then invoking finishDraw on + // any thread is fine since once it calls into system process, finishDrawing will run + // on a different thread. However, when the current process is system, the finishDraw in + // system server will be run on the current thread, which could result in a deadlock. + if (mWindowSession instanceof Binder) { + reportDrawFinished(t, seqId); + } else { + mHandler.postAtFrontOfQueue(() -> reportDrawFinished(t, seqId)); + } }); if (DEBUG_BLAST) { Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName()); @@ -8293,9 +8289,6 @@ public final class ViewRootImpl implements ViewParent, CompatibilityInfo.applyOverrideScaleIfNeeded(mPendingMergedConfiguration); mInsetsController.onStateChanged(mTempInsets); mInsetsController.onControlsChanged(mTempControls.get()); - - mPendingAlwaysConsumeSystemBars = - (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0; } final int transformHint = SurfaceControl.rotationToBufferTransform( @@ -8918,7 +8911,7 @@ public final class ViewRootImpl implements ViewParent, @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void dispatchResized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, - boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) { + int displayId, int syncSeqId, boolean dragResizing) { Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED); SomeArgs args = SomeArgs.obtain(); final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid()); @@ -8937,10 +8930,9 @@ public final class ViewRootImpl implements ViewParent, ? new MergedConfiguration(mergedConfiguration) : mergedConfiguration; args.arg3 = insetsState; args.argi1 = forceLayout ? 1 : 0; - args.argi2 = alwaysConsumeSystemBars ? 1 : 0; - args.argi3 = displayId; - args.argi4 = syncSeqId; - args.argi5 = dragResizing ? 1 : 0; + args.argi2 = displayId; + args.argi3 = syncSeqId; + args.argi4 = dragResizing ? 1 : 0; msg.obj = args; mHandler.sendMessage(msg); @@ -10334,12 +10326,11 @@ public final class ViewRootImpl implements ViewParent, @Override public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, InsetsState insetsState, - boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, - boolean dragResizing) { + boolean forceLayout, int displayId, int syncSeqId, boolean dragResizing) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState, - forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing); + forceLayout, displayId, syncSeqId, dragResizing); } } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 4acaea849586..7757c256164e 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -84,13 +84,7 @@ public final class WindowInsets { @Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds; @Nullable private final DisplayShape mDisplayShape; - /** - * In multi-window we force show the navigation bar. Because we don't want that the surface size - * changes in this mode, we instead have a flag whether the navigation bar size should always - * be consumed, so the app is treated like there is no virtual navigation bar at all. - */ - private final boolean mAlwaysConsumeSystemBars; - + private final @InsetsType int mForceConsumingTypes; private final @InsetsType int mSuppressScrimTypes; private final boolean mSystemWindowInsetsConsumed; private final boolean mStableInsetsConsumed; @@ -117,7 +111,7 @@ public final class WindowInsets { static { CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null), - createCompatVisibilityMap(createCompatTypeMap(null)), false, false, 0, null, + createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, 0, null, null, null, null, systemBars(), false); } @@ -137,7 +131,8 @@ public final class WindowInsets { @Nullable Insets[] typeMaxInsetsMap, boolean[] typeVisibilityMap, boolean isRound, - boolean alwaysConsumeSystemBars, @InsetsType int suppressScrimTypes, + @InsetsType int forceConsumingTypes, + @InsetsType int suppressScrimTypes, DisplayCutout displayCutout, RoundedCorners roundedCorners, PrivacyIndicatorBounds privacyIndicatorBounds, @@ -155,7 +150,7 @@ public final class WindowInsets { mTypeVisibilityMap = typeVisibilityMap; mIsRound = isRound; - mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; + mForceConsumingTypes = forceConsumingTypes; mSuppressScrimTypes = suppressScrimTypes; mCompatInsetsTypes = compatInsetsTypes; mCompatIgnoreVisibility = compatIgnoreVisibility; @@ -178,7 +173,7 @@ public final class WindowInsets { this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap, src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap, src.mTypeVisibilityMap, src.mIsRound, - src.mAlwaysConsumeSystemBars, src.mSuppressScrimTypes, + src.mForceConsumingTypes, src.mSuppressScrimTypes, displayCutoutCopyConstructorArgument(src), src.mRoundedCorners, src.mPrivacyIndicatorBounds, @@ -235,7 +230,7 @@ public final class WindowInsets { /** @hide */ @UnsupportedAppUsage public WindowInsets(Rect systemWindowInsets) { - this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, 0, + this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, 0, null, null, null, null, systemBars(), false /* compatIgnoreVisibility */); } @@ -556,7 +551,7 @@ public final class WindowInsets { return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, - mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, + mIsRound, mForceConsumingTypes, mSuppressScrimTypes, null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -607,7 +602,7 @@ public final class WindowInsets { public WindowInsets consumeSystemWindowInsets() { return new WindowInsets(null, null, mTypeVisibilityMap, - mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, + mIsRound, mForceConsumingTypes, mSuppressScrimTypes, // If the system window insets types contain displayCutout, we should also consume // it. (mCompatInsetsTypes & displayCutout()) != 0 @@ -895,8 +890,8 @@ public final class WindowInsets { /** * @hide */ - public boolean shouldAlwaysConsumeSystemBars() { - return mAlwaysConsumeSystemBars; + public @InsetsType int getForceConsumingTypes() { + return mForceConsumingTypes; } /** @@ -930,6 +925,8 @@ public final class WindowInsets { result.append("\n "); result.append(mDisplayShape != null ? "displayShape=" + mDisplayShape : ""); result.append("\n "); + result.append("forceConsumingTypes=" + Type.toString(mForceConsumingTypes)); + result.append("\n "); result.append("suppressScrimTypes=" + Type.toString(mSuppressScrimTypes)); result.append("\n "); result.append("compatInsetsTypes=" + Type.toString(mCompatInsetsTypes)); @@ -1027,7 +1024,7 @@ public final class WindowInsets { ? null : insetInsets(mTypeMaxInsetsMap, left, top, right, bottom), mTypeVisibilityMap, - mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, + mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutoutConsumed ? null : mDisplayCutout == null @@ -1050,7 +1047,7 @@ public final class WindowInsets { WindowInsets that = (WindowInsets) o; return mIsRound == that.mIsRound - && mAlwaysConsumeSystemBars == that.mAlwaysConsumeSystemBars + && mForceConsumingTypes == that.mForceConsumingTypes && mSuppressScrimTypes == that.mSuppressScrimTypes && mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed && mStableInsetsConsumed == that.mStableInsetsConsumed @@ -1068,7 +1065,7 @@ public final class WindowInsets { public int hashCode() { return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap), Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners, - mAlwaysConsumeSystemBars, mSuppressScrimTypes, mSystemWindowInsetsConsumed, + mForceConsumingTypes, mSuppressScrimTypes, mSystemWindowInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed, mPrivacyIndicatorBounds, mDisplayShape); } @@ -1134,7 +1131,7 @@ public final class WindowInsets { private DisplayShape mDisplayShape = DisplayShape.NONE; private boolean mIsRound; - private boolean mAlwaysConsumeSystemBars; + private @InsetsType int mForceConsumingTypes; private @InsetsType int mSuppressScrimTypes; private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds(); @@ -1162,7 +1159,7 @@ public final class WindowInsets { mDisplayCutout = displayCutoutCopyConstructorArgument(insets); mRoundedCorners = insets.mRoundedCorners; mIsRound = insets.mIsRound; - mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars; + mForceConsumingTypes = insets.mForceConsumingTypes; mSuppressScrimTypes = insets.mSuppressScrimTypes; mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds; mDisplayShape = insets.mDisplayShape; @@ -1432,8 +1429,8 @@ public final class WindowInsets { /** @hide */ @NonNull - public Builder setAlwaysConsumeSystemBars(boolean alwaysConsumeSystemBars) { - mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; + public Builder setForceConsumingTypes(@InsetsType int forceConsumingTypes) { + mForceConsumingTypes = forceConsumingTypes; return this; } @@ -1453,7 +1450,7 @@ public final class WindowInsets { public WindowInsets build() { return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, - mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, mDisplayCutout, + mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutout, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, systemBars(), false /* compatIgnoreVisibility */); } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 99a4f6b41ef3..c9368f16f0ed 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -79,16 +79,9 @@ public final class WindowManagerGlobal { public static final int RELAYOUT_RES_SURFACE_RESIZED = 1 << 2; /** - * In multi-window we force show the system bars. Because we don't want that the surface size - * changes in this mode, we instead have a flag whether the system bar sizes should always be - * consumed, so the app is treated like there is no virtual system bars at all. - */ - public static final int RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS = 1 << 3; - - /** * The window manager has told the window it cannot draw this frame and should retry again. */ - public static final int RELAYOUT_RES_CANCEL_AND_REDRAW = 1 << 4; + public static final int RELAYOUT_RES_CANCEL_AND_REDRAW = 1 << 3; /** * Flag for relayout: the client will be later giving @@ -99,13 +92,7 @@ public final class WindowManagerGlobal { public static final int ADD_FLAG_IN_TOUCH_MODE = 0x1; public static final int ADD_FLAG_APP_VISIBLE = 0x2; - public static final int ADD_FLAG_USE_BLAST = 0x8; - - /** - * Like {@link #RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS}, but as a "hint" when adding the - * window. - */ - public static final int ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS = 0x4; + public static final int ADD_FLAG_USE_BLAST = 0x4; public static final int ADD_OKAY = 0; public static final int ADD_BAD_APP_TOKEN = -1; diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 7d3d283a45f2..1efac4d2927f 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -631,8 +631,8 @@ public class WindowlessWindowManager implements IWindowSession { mTmpFrames.displayFrame.set(mTmpFrames.frame); mTmpConfig.setConfiguration(mConfiguration, mConfiguration); s.mClient.resized(mTmpFrames, false /* reportDraw */, mTmpConfig, state, - false /* forceLayout */, false /* alwaysConsumeSystemBars */, s.mDisplayId, - Integer.MAX_VALUE, false /* dragResizing */); + false /* forceLayout */, s.mDisplayId, Integer.MAX_VALUE, + false /* dragResizing */); } catch (RemoteException e) { // Too bad } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 40b060ad0bbf..3f308e6fccee 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2657,17 +2657,6 @@ public final class InputMethodManager { } } - if (windowGainingFocus == null) { - windowGainingFocus = view.getWindowToken(); - if (windowGainingFocus == null) { - Log.e(TAG, "ABORT input: ServedView must be attached to a Window"); - return false; - } - startInputFlags = getStartInputFlags(view, startInputFlags); - softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode; - windowFlags = view.getViewRootImpl().mWindowAttributes.flags; - } - // Now we need to get an input connection from the served view. // This is complicated in a couple ways: we can't be holding our lock // when calling out to the view, and we need to make sure we call into @@ -2690,6 +2679,17 @@ public final class InputMethodManager { return false; } + if (windowGainingFocus == null) { + windowGainingFocus = view.getWindowToken(); + if (windowGainingFocus == null) { + Log.e(TAG, "ABORT input: ServedView must be attached to a Window"); + return false; + } + startInputFlags = getStartInputFlags(view, startInputFlags); + softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode; + windowFlags = view.getViewRootImpl().mWindowAttributes.flags; + } + // Okay we are now ready to call into the served view and have it // do its stuff. // Life is good: let's hook everything up! diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 438b9742f0af..27d8a8f23e46 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -156,7 +156,6 @@ import android.text.util.Linkify; import android.util.ArraySet; import android.util.AttributeSet; import android.util.DisplayMetrics; -import android.util.FeatureFlagUtils; import android.util.IntArray; import android.util.Log; import android.util.SparseIntArray; @@ -831,11 +830,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; - // The auto option for LINE_BREAK_WORD_STYLE_PHRASE may not be applied in recycled view due to - // one-way flag flipping. This is a tentative limitation during experiment and will not have the - // issue once this is finalized to LINE_BREAK_WORD_STYLE_PHRASE_AUTO option. - private boolean mUserSpeficiedLineBreakwordStyle = false; - // This is used to reflect the current user preference for changing font weight and making text // more bold. private int mFontWeightAdjustment; @@ -1546,9 +1540,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case com.android.internal.R.styleable.TextView_lineBreakWordStyle: - if (a.hasValue(attr)) { - mUserSpeficiedLineBreakwordStyle = true; - } mLineBreakWordStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE); break; @@ -4350,7 +4341,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle: attributes.mHasLineBreakWordStyle = true; - mUserSpeficiedLineBreakwordStyle = true; attributes.mLineBreakWordStyle = appearance.getInt(attr, attributes.mLineBreakWordStyle); break; @@ -5086,7 +5076,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @param lineBreakWordStyle The line-break word style for the text. */ public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { - mUserSpeficiedLineBreakwordStyle = true; if (mLineBreakWordStyle != lineBreakWordStyle) { mLineBreakWordStyle = lineBreakWordStyle; if (mLayout != null) { @@ -5122,12 +5111,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see PrecomputedText */ public @NonNull PrecomputedText.Params getTextMetricsParams() { - final boolean autoPhraseBreaking = - !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); return new PrecomputedText.Params(new TextPaint(mTextPaint), - LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, - autoPhraseBreaking), + LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle), getTextDirectionHeuristic(), mBreakStrategy, mHyphenationFrequency); } @@ -5147,7 +5132,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener LineBreakConfig lineBreakConfig = params.getLineBreakConfig(); mLineBreakStyle = lineBreakConfig.getLineBreakStyle(); mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle(); - mUserSpeficiedLineBreakwordStyle = true; if (mLayout != null) { nullLayouts(); requestLayout(); @@ -7077,13 +7061,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mTextDir == null) { mTextDir = getTextDirectionHeuristic(); } - final boolean autoPhraseBreaking = - !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); final @PrecomputedText.Params.CheckResultUsableResult int checkResult = precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, mHyphenationFrequency, LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); + mLineBreakStyle, mLineBreakWordStyle)); switch (checkResult) { case PrecomputedText.Params.UNUSABLE: throw new IllegalArgumentException( @@ -10640,9 +10621,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } // TODO: code duplication with makeSingleLayout() if (mHintLayout == null) { - final boolean autoPhraseBreaking = - !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, mHint.length(), mTextPaint, hintWidth) .setAlignment(alignment) @@ -10655,7 +10633,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setJustificationMode(mJustificationMode) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); + mLineBreakStyle, mLineBreakWordStyle)); if (shouldEllipsize) { builder.setEllipsize(mEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -10759,9 +10737,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } if (result == null) { - final boolean autoPhraseBreaking = - !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 0, mTransformed.length(), mTextPaint, wantWidth) .setAlignment(alignment) @@ -10774,7 +10749,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setJustificationMode(mJustificationMode) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); + mLineBreakStyle, mLineBreakWordStyle)); if (shouldEllipsize) { builder.setEllipsize(effectiveEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -11132,9 +11107,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); - final boolean autoPhraseBreaking = - !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); layoutBuilder.setAlignment(getLayoutAlignment()) .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) .setIncludePad(getIncludeFontPadding()) @@ -11145,7 +11117,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) .setTextDirection(getTextDirectionHeuristic()) .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); + mLineBreakStyle, mLineBreakWordStyle)); final StaticLayout layout = layoutBuilder.build(); diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java index 954f68633e57..d08d5cfaa2b5 100644 --- a/core/java/android/window/WindowMetricsController.java +++ b/core/java/android/window/WindowMetricsController.java @@ -112,15 +112,14 @@ public final class WindowMetricsController { Rect bounds, boolean isScreenRound, int windowingMode) { try { final InsetsState insetsState = new InsetsState(); - final boolean alwaysConsumeSystemBars = WindowManagerGlobal.getWindowManagerService() - .getWindowInsets(displayId, token, insetsState); + WindowManagerGlobal.getWindowManagerService().getWindowInsets( + displayId, token, insetsState); final float overrideInvScale = CompatibilityInfo.getOverrideInvertedScale(); if (overrideInvScale != 1f) { insetsState.scale(overrideInvScale); } return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState */, - isScreenRound, alwaysConsumeSystemBars, SOFT_INPUT_ADJUST_NOTHING, - 0 /* flags */, SYSTEM_UI_FLAG_VISIBLE, + isScreenRound, SOFT_INPUT_ADJUST_NOTHING, 0 /* flags */, SYSTEM_UI_FLAG_VISIBLE, WindowManager.LayoutParams.INVALID_WINDOW_TYPE, windowingMode, null /* idSideMap */); } catch (RemoteException e) { @@ -145,13 +144,13 @@ public final class WindowMetricsController { for (int i = 0; i < possibleDisplayInfos.size(); i++) { currentDisplayInfo = possibleDisplayInfos.get(i); - // Calculate max bounds for this rotation and state. - Rect maxBounds = new Rect(0, 0, currentDisplayInfo.logicalWidth, - currentDisplayInfo.logicalHeight); + // Calculate max bounds for natural rotation and state. + Rect maxBounds = new Rect(0, 0, currentDisplayInfo.getNaturalWidth(), + currentDisplayInfo.getNaturalHeight()); - // Calculate insets for the rotated max bounds. + // Calculate insets for the natural max bounds. final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0; - // Initialize insets based upon display rotation. Note any window-provided insets + // Initialize insets based on Surface.ROTATION_0. Note any window-provided insets // will not be set. windowInsets = getWindowInsetsFromServerForDisplay( currentDisplayInfo.displayId, null /* token */, diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 7452daa4908c..65b59790e327 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -56,6 +56,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.telecom.TelecomManager; +import android.util.Log; import android.util.Slog; import android.view.View; import android.widget.Button; @@ -124,16 +125,19 @@ public class IntentForwarderActivity extends Activity { String className = intentReceived.getComponent().getClassName(); final int targetUserId; final String userMessage; + final UserInfo managedProfile; if (className.equals(FORWARD_INTENT_TO_PARENT)) { userMessage = getForwardToPersonalMessage(); targetUserId = getProfileParent(); + managedProfile = null; getMetricsLogger().write( new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) .setSubtype(MetricsEvent.PARENT_PROFILE)); } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { userMessage = getForwardToWorkMessage(); - targetUserId = getManagedProfile(); + managedProfile = getManagedProfile(); + targetUserId = managedProfile == null ? UserHandle.USER_NULL : managedProfile.id; getMetricsLogger().write( new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) @@ -142,6 +146,7 @@ public class IntentForwarderActivity extends Activity { Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly"); userMessage = null; targetUserId = UserHandle.USER_NULL; + managedProfile = null; } if (targetUserId == UserHandle.USER_NULL) { // This covers the case where there is no parent / managed profile. @@ -185,27 +190,49 @@ public class IntentForwarderActivity extends Activity { finish(); // When switching to the work profile, ask the user for consent before launching } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { - maybeShowUserConsentMiniResolver(result, newIntent, targetUserId); + maybeShowUserConsentMiniResolver(result, newIntent, managedProfile); } }, getApplicationContext().getMainExecutor()); } private void maybeShowUserConsentMiniResolver( - ResolveInfo target, Intent launchIntent, int targetUserId) { + ResolveInfo target, Intent launchIntent, UserInfo managedProfile) { if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) { finish(); return; } - if (launchIntent.getBooleanExtra(EXTRA_SKIP_USER_CONFIRMATION, /* defaultValue= */ false) - && getCallingPackage() != null - && PERMISSION_GRANTED == getPackageManager().checkPermission( - INTERACT_ACROSS_USERS, getCallingPackage())) { + int targetUserId = managedProfile == null ? UserHandle.USER_NULL : managedProfile.id; + String callingPackage = getCallingPackage(); + boolean privilegedCallerAskedToSkipUserConsent = + launchIntent.getBooleanExtra( + EXTRA_SKIP_USER_CONFIRMATION, /* defaultValue= */ false) + && callingPackage != null + && PERMISSION_GRANTED == getPackageManager().checkPermission( + INTERACT_ACROSS_USERS, callingPackage); + + DevicePolicyManager devicePolicyManager = + getSystemService(DevicePolicyManager.class); + ComponentName profileOwnerName = devicePolicyManager.getProfileOwnerAsUser(targetUserId); + boolean intentToLaunchProfileOwner = profileOwnerName != null + && profileOwnerName.getPackageName().equals(target.getComponentInfo().packageName); + + if (privilegedCallerAskedToSkipUserConsent || intentToLaunchProfileOwner) { + Log.i("IntentForwarderActivity", String.format( + "Skipping user consent for redirection into the managed profile for intent [%s]" + + ", privilegedCallerAskedToSkipUserConsent=[%s]" + + ", intentToLaunchProfileOwner=[%s]", + launchIntent, privilegedCallerAskedToSkipUserConsent, + intentToLaunchProfileOwner)); startActivityAsCaller(launchIntent, targetUserId); finish(); return; } + Log.i("IntentForwarderActivity", String.format( + "Showing user consent for redirection into the managed profile for intent [%s] and " + + " calling package [%s]", + launchIntent, callingPackage)); int layoutId = R.layout.miniresolver; setContentView(layoutId); @@ -245,8 +272,7 @@ public class IntentForwarderActivity extends Activity { View telephonyInfo = findViewById(R.id.miniresolver_info_section); - DevicePolicyManager devicePolicyManager = - getSystemService(DevicePolicyManager.class); + // Additional information section is work telephony specific. Therefore, it is only shown // for telephony related intents, when all sim subscriptions are in the work profile. if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent)) @@ -507,20 +533,18 @@ public class IntentForwarderActivity extends Activity { } /** - * Returns the userId of the managed profile for this device or UserHandle.USER_NULL if there is - * no managed profile. + * Returns the managed profile for this device or null if there is no managed profile. * - * TODO: Remove the assumption that there is only one managed profile - * on the device. + * TODO: Remove the assumption that there is only one managed profile on the device. */ - private int getManagedProfile() { + @Nullable private UserInfo getManagedProfile() { List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId()); for (UserInfo userInfo : relatedUsers) { - if (userInfo.isManagedProfile()) return userInfo.id; + if (userInfo.isManagedProfile()) return userInfo; } Slog.wtf(TAG, FORWARD_INTENT_TO_MANAGED_PROFILE + " has been called, but there is no managed profile"); - return UserHandle.USER_NULL; + return null; } /** diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index 1a3804900665..66e3333acf7c 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -20,7 +20,6 @@ import android.annotation.AnyThread; import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; -import android.inputmethodservice.InputMethodService; import android.net.Uri; import android.os.IBinder; import android.os.RemoteException; @@ -106,10 +105,14 @@ public final class InputMethodPrivilegedOperations { * * @param vis visibility flags * @param backDisposition disposition flags + * @see android.inputmethodservice.InputMethodService#IME_ACTIVE + * @see android.inputmethodservice.InputMethodService#IME_VISIBLE + * @see android.inputmethodservice.InputMethodService#IME_INVISIBLE + * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT + * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_ADJUST_NOTHING */ @AnyThread - public void setImeWindowStatusAsync(@InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition) { + public void setImeWindowStatusAsync(int vis, int backDisposition) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { return; diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index af1fdd79169a..bb868018bc95 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -232,7 +232,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private boolean mLastHasRightStableInset = false; private boolean mLastHasLeftStableInset = false; private int mLastWindowFlags = 0; - private boolean mLastShouldAlwaysConsumeSystemBars = false; + private @InsetsType int mLastForceConsumingTypes = 0; private @InsetsType int mLastSuppressScrimTypes = 0; private int mRootScrollY = 0; @@ -1111,19 +1111,19 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind : controller.getSystemBarsAppearance(); if (insets != null) { - mLastShouldAlwaysConsumeSystemBars = insets.shouldAlwaysConsumeSystemBars(); + mLastForceConsumingTypes = insets.getForceConsumingTypes(); - final boolean clearsCompatInsets = - clearsCompatInsets(attrs.type, attrs.flags, - getResources().getConfiguration().windowConfiguration - .getWindowingMode()) - && !mLastShouldAlwaysConsumeSystemBars; + @InsetsType int compatInsetsTypes = + WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout(); + if (clearsCompatInsets(attrs.type, attrs.flags, + getResources().getConfiguration().windowConfiguration.getWindowingMode())) { + compatInsetsTypes &= mLastForceConsumingTypes; + } final Insets stableBarInsets = insets.getInsetsIgnoringVisibility( WindowInsets.Type.systemBars()); - final Insets systemInsets = clearsCompatInsets + final Insets systemInsets = compatInsetsTypes == 0 ? Insets.NONE - : Insets.min(insets.getInsets(WindowInsets.Type.systemBars() - | WindowInsets.Type.displayCutout()), stableBarInsets); + : Insets.min(insets.getInsets(compatInsetsTypes), stableBarInsets); mLastTopInset = systemInsets.top; mLastBottomInset = systemInsets.bottom; mLastRightInset = systemInsets.right; @@ -1208,7 +1208,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 && decorFitsSystemWindows && !hideNavigation) - || (mLastShouldAlwaysConsumeSystemBars && hideNavigation); + || ((mLastForceConsumingTypes & WindowInsets.Type.navigationBars()) != 0 + && hideNavigation); boolean consumingNavBar = ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 @@ -1224,13 +1225,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0 || (attrs.flags & FLAG_FULLSCREEN) != 0 || (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0; - boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 - && decorFitsSystemWindows - && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 - && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 - && mForceWindowDrawsBarBackgrounds - && mLastTopInset != 0 - || (mLastShouldAlwaysConsumeSystemBars && fullscreen); + boolean consumingStatusBar = + ((sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 + && decorFitsSystemWindows + && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 + && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 + && mForceWindowDrawsBarBackgrounds + && mLastTopInset != 0) + || ((mLastForceConsumingTypes & WindowInsets.Type.statusBars()) != 0 + && fullscreen); int consumedTop = consumingStatusBar ? mLastTopInset : 0; int consumedRight = consumingNavBar ? mLastRightInset : 0; @@ -1434,9 +1437,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private void updateColorViewInt(final ColorViewState state, int color, int dividerColor, int size, boolean verticalBar, boolean seascape, int sideMargin, boolean animate, boolean force, @InsetsType int requestedVisibleTypes) { + final @InsetsType int type = state.attributes.insetsType; state.present = state.attributes.isPresent( - (requestedVisibleTypes & state.attributes.insetsType) != 0 - || mLastShouldAlwaysConsumeSystemBars, + (requestedVisibleTypes & type) != 0 || (mLastForceConsumingTypes & type) != 0, mWindow.getAttributes().flags, force); boolean show = state.attributes.isVisible(state.present, color, mWindow.getAttributes().flags, force); diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java index 8b9a9913183d..4f827cda6afa 100644 --- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java +++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java @@ -16,7 +16,6 @@ package com.android.internal.statusbar; -import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -32,9 +31,7 @@ public final class RegisterStatusBarResult implements Parcelable { public final int mDisabledFlags1; // switch[0] public final int mAppearance; // switch[1] public final AppearanceRegion[] mAppearanceRegions; // switch[2] - @InputMethodService.ImeWindowVisibility public final int mImeWindowVis; // switch[3] - @InputMethodService.BackDispositionMode public final int mImeBackDisposition; // switch[4] public final boolean mShowImeSwitcher; // switch[5] public final int mDisabledFlags2; // switch[6] @@ -47,12 +44,10 @@ public final class RegisterStatusBarResult implements Parcelable { public final LetterboxDetails[] mLetterboxDetails; public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1, - int appearance, AppearanceRegion[] appearanceRegions, - @InputMethodService.ImeWindowVisibility int imeWindowVis, - @InputMethodService.BackDispositionMode int imeBackDisposition, boolean showImeSwitcher, - int disabledFlags2, IBinder imeToken, boolean navbarColorManagedByIme, int behavior, - int requestedVisibleTypes, String packageName, int transientBarTypes, - LetterboxDetails[] letterboxDetails) { + int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis, + int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken, + boolean navbarColorManagedByIme, int behavior, int requestedVisibleTypes, + String packageName, int transientBarTypes, LetterboxDetails[] letterboxDetails) { mIcons = new ArrayMap<>(icons); mDisabledFlags1 = disabledFlags1; mAppearance = appearance; diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index 600058e88e4b..0ac7765d4550 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -53,7 +53,7 @@ public class BaseIWindow extends IWindow.Stub { @Override public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, - boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing) { + int displayId, int seqId, boolean dragResizing) { if (reportDraw) { try { mSession.finishDrawing(this, null /* postDrawTransaction */, seqId); diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp index d94b9828808b..8fc30d1c248d 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -16,11 +16,12 @@ #define LOG_TAG "GraphicsEnvironment" -#include <vector> - #include <graphicsenv/GraphicsEnv.h> #include <nativehelper/ScopedUtfChars.h> #include <nativeloader/native_loader.h> + +#include <vector> + #include "core_jni_helpers.h" namespace { @@ -49,11 +50,10 @@ void setGpuStats_native(JNIEnv* env, jobject clazz, jstring driverPackageName, appPackageNameChars.c_str(), vulkanVersion); } -void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring packageName, - jstring devOptIn, jobjectArray featuresObj) { +void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver, + jstring packageName, jobjectArray featuresObj) { ScopedUtfChars pathChars(env, path); ScopedUtfChars packageNameChars(env, packageName); - ScopedUtfChars devOptInChars(env, devOptIn); std::vector<std::string> features; if (featuresObj != nullptr) { @@ -73,8 +73,8 @@ void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring packa } } - android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), packageNameChars.c_str(), - devOptInChars.c_str(), features); + android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver, + packageNameChars.c_str(), features); } void setLayerPaths_native(JNIEnv* env, jobject clazz, jobject classLoader, jstring layerPaths) { @@ -118,8 +118,7 @@ const JNINativeMethod g_methods[] = { reinterpret_cast<void*>(setGpuStats_native)}, {"setInjectLayersPrSetDumpable", "()Z", reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)}, - {"setAngleInfo", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V", + {"nativeSetAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V", reinterpret_cast<void*>(setAngleInfo_native)}, {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V", reinterpret_cast<void*>(setLayerPaths_native)}, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2f9f6ae3f3c4..a01c7b6355c1 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6094,7 +6094,7 @@ <!-- @hide @SystemApi Allows an application to observe usage time of apps. The app can register for callbacks when apps reach a certain usage time limit, etc. --> <permission android:name="android.permission.OBSERVE_APP_USAGE" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- @hide @TestApi @SystemApi Allows an application to change the app idle state of an app. <p>Not for use by third-party applications. --> @@ -6724,7 +6724,7 @@ it will be ignored. @hide --> <permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- @SystemApi Allows entering or exiting car mode using a specified priority. This permission is required to use UiModeManager while specifying a priority for the calling diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 04fef58e973e..f795bd7cc3fd 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3792,7 +3792,7 @@ keyboard is connected --> <string name="show_ime">Keep it on screen while physical keyboard is active</string> <!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=30] --> - <string name="hardware">Show virtual keyboard</string> + <string name="hardware">Use on-screen keyboard</string> <!-- Title of the notification to prompt the user to configure physical keyboard settings. [CHAR LIMIT=NOTIF_TITLE] --> <string name="select_keyboard_layout_notification_title">Configure <xliff:g id="device_name" example="Foobar USB Keyboard">%s</xliff:g></string> diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java index 925da4968517..0ebf03fab966 100644 --- a/core/tests/coretests/src/android/text/StaticLayoutTest.java +++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java @@ -24,7 +24,6 @@ import static org.junit.Assert.assertTrue; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; -import android.graphics.text.LineBreakConfig; import android.os.LocaleList; import android.platform.test.annotations.Presubmit; import android.text.Layout.Alignment; @@ -926,24 +925,4 @@ public class StaticLayoutTest { assertEquals(0, layout.getHeight(true)); assertEquals(2, layout.getLineCount()); } - - @Test - public void testBuilder_autoPhraseBreaking() { - { - // setAutoPhraseBreaking true - LineBreakConfig lineBreakConfig = new LineBreakConfig.Builder() - .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_STYLE_NONE) - .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE) - .setAutoPhraseBreaking(true) - .build(); - final String text = "これが正解。"; - // Obtain. - StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, - text.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH); - builder.setLineBreakConfig(lineBreakConfig); - builder.build(); - assertEquals(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE, - builder.getLineBreakWordStyle()); - } - } } diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index f45db23ace76..a9c0e41b23bd 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -81,7 +81,6 @@ public class ImeInsetsSourceConsumerTest { Insets.of(10, 10, 10, 10), rect, rect, rect, rect)); mController.calculateInsets( false, - false, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, SOFT_INPUT_ADJUST_RESIZE, 0, 0); mImeConsumer = mController.getImeSourceConsumer(); diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 06920524acfc..76e4a6bfe2c7 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -171,7 +171,6 @@ public class InsetsControllerTest { mController.onStateChanged(state); mController.calculateInsets( false, - false, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, SOFT_INPUT_ADJUST_RESIZE, 0, 0); mController.onFrameChanged(new Rect(0, 0, 100, 100)); diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index fde1a6d7b04c..6b32350c42ed 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -100,7 +100,7 @@ public class InsetsStateTest { .setVisible(true); SparseIntArray typeSideMap = new SparseIntArray(); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, + SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, typeSideMap); assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all())); @@ -119,8 +119,7 @@ public class InsetsStateTest { .setFrame(new Rect(0, 100, 100, 300)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, - null); + SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(100, insets.getStableInsetBottom()); assertEquals(Insets.of(0, 0, 0, 100), insets.getInsetsIgnoringVisibility(systemBars())); assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets()); @@ -138,7 +137,7 @@ public class InsetsStateTest { .setFrame(new Rect(80, 0, 100, 300)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); + 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars())); assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars())); @@ -153,7 +152,7 @@ public class InsetsStateTest { .setFrame(new Rect(80, 0, 100, 300)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); + 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars())); assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars())); @@ -168,8 +167,7 @@ public class InsetsStateTest { .setFrame(new Rect(0, 200, 100, 300)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, - null); + SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(0, insets.getSystemWindowInsetBottom()); assertEquals(100, insets.getInsets(ime()).bottom); assertTrue(insets.isVisible(ime())); @@ -184,10 +182,10 @@ public class InsetsStateTest { .setFrame(new Rect(0, 200, 100, 300)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_NOTHING, 0, SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION, + SOFT_INPUT_ADJUST_NOTHING, 0, SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(100, insets.getSystemWindowInsetTop()); - insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false, + insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, SOFT_INPUT_ADJUST_NOTHING, 0, 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(0, insets.getSystemWindowInsetTop()); @@ -199,10 +197,10 @@ public class InsetsStateTest { .setFrame(new Rect(0, 0, 100, 100)) .setVisible(false); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN, SYSTEM_UI_FLAG_LAYOUT_STABLE, + SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN, SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(0, insets.getSystemWindowInsetTop()); - insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false, + insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, SOFT_INPUT_ADJUST_NOTHING, 0, 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(0, insets.getSystemWindowInsetTop()); @@ -214,19 +212,19 @@ public class InsetsStateTest { .setFrame(new Rect(0, 0, 100, 100)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, + SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(0, insets.getSystemWindowInsetTop()); insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, + SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, 0 /* legacySystemUiFlags */, TYPE_SYSTEM_ERROR, WINDOWING_MODE_UNDEFINED, null); assertEquals(100, insets.getSystemWindowInsetTop()); insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, + SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, 0 /* legacySystemUiFlags */, TYPE_WALLPAPER, WINDOWING_MODE_UNDEFINED, null); assertEquals(100, insets.getSystemWindowInsetTop()); insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, + SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_FREEFORM, null); assertEquals(100, insets.getSystemWindowInsetTop()); } @@ -268,7 +266,7 @@ public class InsetsStateTest { .setFrame(new Rect(80, 0, 100, 300)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); + 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars())); assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars())); @@ -283,7 +281,7 @@ public class InsetsStateTest { .setFrame(new Rect(80, 0, 100, 300)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); + 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars())); assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars())); @@ -298,7 +296,7 @@ public class InsetsStateTest { .setFrame(new Rect(0, 200, 100, 300)) .setVisible(true); mState.removeSource(ID_IME); - WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false, + WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(0, insets.getSystemWindowInsetBottom()); } @@ -530,7 +528,7 @@ public class InsetsStateTest { new Rect(0, 0, 1, 2), new Rect(197, 296, 200, 300), new Rect(197, 296, 200, 300))); - DisplayCutout cutout = mState.calculateInsets(new Rect(1, 1, 199, 300), null, false, false, + DisplayCutout cutout = mState.calculateInsets(new Rect(1, 1, 199, 300), null, false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, new SparseIntArray()).getDisplayCutout(); assertEquals(0, cutout.getSafeInsetLeft()); @@ -556,7 +554,7 @@ public class InsetsStateTest { new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 180, 380), new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 380))); WindowInsets windowInsets = mState.calculateInsets(new Rect(1, 2, 197, 396), null, false, - false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, + SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, new SparseIntArray()); assertEquals(new RoundedCorner(POSITION_TOP_LEFT, 10, 9, 8), windowInsets.getRoundedCorner(POSITION_TOP_LEFT)); @@ -573,7 +571,7 @@ public class InsetsStateTest { mState.setDisplayFrame(new Rect(0, 0, 200, 400)); mState.setDisplayShape(DisplayShape.createDefaultDisplayShape(200, 400, false)); WindowInsets windowInsets = mState.calculateInsets(new Rect(10, 20, 200, 400), null, false, - false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, + SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, new SparseIntArray()); final DisplayShape expect = diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java index b4ba23c92a22..69abf5f3204f 100644 --- a/core/tests/coretests/src/android/view/WindowInsetsTest.java +++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java @@ -40,14 +40,14 @@ public class WindowInsetsTest { @Test public void systemWindowInsets_afterConsuming_isConsumed() { assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null, - null, false, false, 0, null, null, null, null, + null, false, 0, 0, null, null, null, null, WindowInsets.Type.systemBars(), false) .consumeSystemWindowInsets().isConsumed()); } @Test public void multiNullConstructor_isConsumed() { - assertTrue(new WindowInsets(null, null, null, false, false, 0, null, null, null, null, + assertTrue(new WindowInsets(null, null, null, false, 0, 0, null, null, null, null, WindowInsets.Type.systemBars(), false).isConsumed()); } @@ -63,7 +63,7 @@ public class WindowInsetsTest { boolean[] visible = new boolean[SIZE]; WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0)); WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0)); - WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false, + WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, 0, 0, null, null, null, DisplayShape.NONE, systemBars(), true /* compatIgnoreVisibility */); assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets()); diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java index a1a4265cd0a5..84dd2740e8b7 100644 --- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java @@ -169,7 +169,7 @@ public class ActionBarOverlayLayoutTest { private WindowInsets insetsWith(Insets content, DisplayCutout cutout) { return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null, - false, false, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false); + false, 0, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false); } private ViewGroup createViewGroupWithId(int id) { diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index d0327159b04d..0c493f5a1fdc 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -94,11 +94,6 @@ public final class LineBreakConfig { private @LineBreakWordStyle int mLineBreakWordStyle = LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; - // Whether or not enabling phrase breaking automatically. - // TODO(b/226012260): Remove this and add LINE_BREAK_WORD_STYLE_PHRASE_AUTO after - // the experiment. - private boolean mAutoPhraseBreaking = false; - /** * Builder constructor. */ @@ -128,22 +123,12 @@ public final class LineBreakConfig { } /** - * Enables or disables the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}. - * - * @hide - */ - public @NonNull Builder setAutoPhraseBreaking(boolean autoPhraseBreaking) { - mAutoPhraseBreaking = autoPhraseBreaking; - return this; - } - - /** * Builds a {@link LineBreakConfig} instance. * * @return The {@code LineBreakConfig} instance. */ public @NonNull LineBreakConfig build() { - return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mAutoPhraseBreaking); + return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle); } } @@ -164,23 +149,6 @@ public final class LineBreakConfig { .build(); } - /** - * Create the LineBreakConfig instance. - * - * @param lineBreakStyle the line break style for text wrapping. - * @param lineBreakWordStyle the line break word style for text wrapping. - * @return the {@link LineBreakConfig} instance. * - * @hide - */ - public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle, - @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) { - LineBreakConfig.Builder builder = new LineBreakConfig.Builder(); - return builder.setLineBreakStyle(lineBreakStyle) - .setLineBreakWordStyle(lineBreakWordStyle) - .setAutoPhraseBreaking(autoPhraseBreaking) - .build(); - } - /** @hide */ public static final LineBreakConfig NONE = new Builder().setLineBreakStyle(LINE_BREAK_STYLE_NONE) @@ -188,7 +156,6 @@ public final class LineBreakConfig { private final @LineBreakStyle int mLineBreakStyle; private final @LineBreakWordStyle int mLineBreakWordStyle; - private final boolean mAutoPhraseBreaking; /** * Constructor with line-break parameters. @@ -197,10 +164,9 @@ public final class LineBreakConfig { * {@code LineBreakConfig} instance. */ private LineBreakConfig(@LineBreakStyle int lineBreakStyle, - @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) { + @LineBreakWordStyle int lineBreakWordStyle) { mLineBreakStyle = lineBreakStyle; mLineBreakWordStyle = lineBreakWordStyle; - mAutoPhraseBreaking = autoPhraseBreaking; } /** @@ -221,17 +187,6 @@ public final class LineBreakConfig { return mLineBreakWordStyle; } - /** - * Used to identify if the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled. - * - * @return The result that records whether or not the automation of - * {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled. - * @hide - */ - public boolean getAutoPhraseBreaking() { - return mAutoPhraseBreaking; - } - @Override public boolean equals(Object o) { if (o == null) return false; @@ -239,8 +194,7 @@ public final class LineBreakConfig { if (!(o instanceof LineBreakConfig)) return false; LineBreakConfig that = (LineBreakConfig) o; return (mLineBreakStyle == that.mLineBreakStyle) - && (mLineBreakWordStyle == that.mLineBreakWordStyle) - && (mAutoPhraseBreaking == that.mAutoPhraseBreaking); + && (mLineBreakWordStyle == that.mLineBreakWordStyle); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java index 5e42782431fd..586a794d5020 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java @@ -347,8 +347,7 @@ public class SystemWindows { @Override public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration newMergedConfiguration, InsetsState insetsState, - boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, - boolean dragResizing) {} + boolean forceLayout, int displayId, int syncSeqId, boolean dragResizing) {} @Override public void insetsControlChanged(InsetsState insetsState, 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 24aaa9b75ebe..73eb62ae47e9 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 @@ -339,19 +339,36 @@ public class PipTransition extends PipTransitionController { } // 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. + // Or that the keyguard is up and preventing the transition from applying, in which case we + // want to manually reset pip. (b/283783868) boolean cancelled = false; if (mPipAnimationController.getCurrentAnimator() != null) { mPipAnimationController.getCurrentAnimator().cancel(); + mPipAnimationController.resetAnimatorState(); cancelled = true; } + // Unset exitTransition AFTER cancel so that finishResize knows we are merging. mExitTransition = null; - if (!cancelled || aborted) return; + if (!cancelled) return; final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo(); if (taskInfo != null) { - startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(), - mPipBoundsState.getBounds(), mPipBoundsState.getBounds(), - new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */); + if (aborted) { + // keyguard case - the transition got aborted, so we want to reset state and + // windowing mode before reapplying the resize transaction + sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP); + mPipOrganizer.onExitPipFinished(taskInfo); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP); + wct.setBounds(taskInfo.token, null); + mPipOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_LEAVE_PIP, false); + } else { + // merge case + startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(), + mPipBoundsState.getBounds(), mPipBoundsState.getBounds(), + new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */); + } } mExitDestinationBounds.setEmpty(); mCurrentPipTaskToken = null; @@ -567,7 +584,16 @@ public class PipTransition extends PipTransitionController { mPipBoundsState.getDisplayBounds()); mFinishCallback = (wct, wctCB) -> { mPipOrganizer.onExitPipFinished(taskInfo); - if (!Transitions.SHELL_TRANSITIONS_ROTATION && toFullscreen) { + + // TODO(b/286346098): remove the OPEN app flicker completely + // not checking if we go to fullscreen helps avoid getting pip into an inconsistent + // state after the flicker occurs. This is a temp solution until flicker is removed. + if (!Transitions.SHELL_TRANSITIONS_ROTATION) { + // will help to debug the case when we are not exiting to fullscreen + if (!toFullscreen) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: startExitAnimation() not exiting to fullscreen", TAG); + } wct = wct != null ? wct : new WindowContainerTransaction(); wct.setBounds(pipTaskToken, null); mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP); @@ -831,7 +857,7 @@ public class PipTransition extends PipTransitionController { } final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); - final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds(); + final Rect currentBounds = pipChange.getStartAbsBounds(); int rotationDelta = deltaRotation(startRotation, endRotation); Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( taskInfo.pictureInPictureParams, currentBounds, destinationBounds); @@ -1050,7 +1076,7 @@ public class PipTransition extends PipTransitionController { // When the PIP window is visible and being a part of the transition, such as display // rotation, we need to update its bounds and rounded corner. final SurfaceControl leash = pipChange.getLeash(); - final Rect destBounds = mPipBoundsState.getBounds(); + final Rect destBounds = mPipOrganizer.getCurrentOrAnimatingBounds(); final boolean isInPip = mPipTransitionState.isInPip(); mSurfaceTransactionHelper .crop(startTransaction, leash, destBounds) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 843e5af67af9..837f11803ab2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -567,7 +567,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { // Tasks that are always on top (e.g. bubbles), will handle their own transition // as they are on top of everything else. So cancel the merge here. - cancel("task #" + taskInfo.taskId + " is always_on_top"); + cancel(false /* toHome */, false /* withScreenshots */, + "task #" + taskInfo.taskId + " is always_on_top"); return; } final boolean isRootTask = taskInfo != null 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 acc1c5eb74b6..bf70d48e5801 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 @@ -2853,18 +2853,24 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + final ArrayMap<Integer, SurfaceControl> dismissingTasks = new ArrayMap<>(); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo == null) continue; + if (getStageOfTask(taskInfo) != null + || getSplitItemPosition(change.getLastParent()) != SPLIT_POSITION_UNDEFINED) { + dismissingTasks.put(taskInfo.taskId, change.getLeash()); + } + } + + if (shouldBreakPairedTaskInRecents(dismissReason)) { // Notify recents if we are exiting in a way that breaks the pair, and disable further // updates to splits in the recents until we enter split again mRecentTasks.ifPresent(recentTasks -> { - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (taskInfo != null && (getStageOfTask(taskInfo) != null - || getSplitItemPosition(change.getLastParent()) - != SPLIT_POSITION_UNDEFINED)) { - recentTasks.removeSplitPair(taskInfo.taskId); - } + for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) { + recentTasks.removeSplitPair(dismissingTasks.keyAt(i)); } }); } @@ -2882,6 +2888,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, t.hide(toStage == STAGE_TYPE_MAIN ? mSideStage.mRootLeash : mMainStage.mRootLeash); t.setPosition(toStage == STAGE_TYPE_MAIN ? mMainStage.mRootLeash : mSideStage.mRootLeash, 0, 0); + } else { + for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) { + finishT.hide(dismissingTasks.valueAt(i)); + } } if (toStage == STAGE_TYPE_UNDEFINED) { @@ -2891,7 +2901,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } // Hide divider and dim layer on transition finished. - setDividerVisibility(false, finishT); + setDividerVisibility(false, t); finishT.hide(mMainStage.mDimLayer); finishT.hide(mSideStage.mDimLayer); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index c964df1452e0..1d879a130848 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -16,6 +16,7 @@ package com.android.wm.shell.startingsurface; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.graphics.Color.WHITE; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -77,6 +78,13 @@ public class TaskSnapshotWindow { @NonNull Runnable clearWindowHandler) { final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; final int taskId = runningTaskInfo.taskId; + + // if we're in PIP we don't want to create the snapshot + if (runningTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "did not create taskSnapshot due to being in PIP"); + return null; + } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId); @@ -206,8 +214,7 @@ public class TaskSnapshotWindow { @Override public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, InsetsState insetsState, - boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId, - boolean dragResizing) { + boolean forceLayout, int displayId, int seqId, boolean dragResizing) { final TaskSnapshotWindow snapshot = mOuter.get(); if (snapshot == null) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index a28ce55e8c94..d9edde16a863 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -25,6 +25,7 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.util.TransitionUtil.isOpeningType; @@ -500,6 +501,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } } + mPipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA); mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, finishCB); // Dispatch the rest of the transition normally. This will most-likely be taken by 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 d8a88770072d..75659960bc32 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 @@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.fixScale; +import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; @@ -728,11 +729,15 @@ public class Transitions implements RemoteCallable<Transitions>, final int changeSize = info.getChanges().size(); boolean taskChange = false; boolean transferStartingWindow = false; + int noAnimationBehindStartingWindow = 0; boolean allOccluded = changeSize > 0; for (int i = changeSize - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); taskChange |= change.getTaskInfo() != null; transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT); + if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)) { + noAnimationBehindStartingWindow++; + } if (!change.hasFlags(FLAG_IS_OCCLUDED)) { allOccluded = false; } @@ -740,9 +745,11 @@ public class Transitions implements RemoteCallable<Transitions>, // There does not need animation when: // A. Transfer starting window. Apply transfer starting window directly if there is no other // task change. Since this is an activity->activity situation, we can detect it by selecting - // transitions with only 2 changes where neither are tasks and one is a starting-window - // recipient. - if (!taskChange && transferStartingWindow && changeSize == 2 + // transitions with only 2 changes where + // 1. neither are tasks, and + // 2. one is a starting-window recipient, or all change is behind starting window. + if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize) + && changeSize == 2 // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all // changes are underneath another change. || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt index 27eaa40ee49b..fd56a6e49d3e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.splitscreen +package com.android.wm.shell.flicker import android.app.Instrumentation import android.graphics.Point @@ -40,8 +40,6 @@ import com.android.server.wm.flicker.helpers.NotificationAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary -import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME -import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME import org.junit.Assert.assertNotNull internal object SplitScreenUtils { @@ -114,13 +112,12 @@ internal object SplitScreenUtils { } fun enterSplitViaIntent( - wmHelper: WindowManagerStateHelper, - primaryApp: StandardAppHelper, - secondaryApp: StandardAppHelper + wmHelper: WindowManagerStateHelper, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper ) { val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true") - primaryApp.launchViaIntent(wmHelper, null, null, - stringExtras) + primaryApp.launchViaIntent(wmHelper, null, null, stringExtras) waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } @@ -326,14 +323,14 @@ internal object SplitScreenUtils { dividerBar.drag( Point( if (dragToRight) { - displayBounds.width * 4 / 5 + displayBounds.right } else { - displayBounds.width * 1 / 5 + displayBounds.left }, if (dragToBottom) { - displayBounds.height * 4 / 5 + displayBounds.bottom } else { - displayBounds.height * 1 / 5 + displayBounds.top } ) ) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt new file mode 100644 index 000000000000..6fe88cacbbc7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.appcompat + +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.RequiresDevice +import android.tools.common.flicker.assertions.FlickerTest +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.datatypes.Rect +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import 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 to letterboxed app from launcher + * + * To run this test: `atest WMShellFlickerTestsOther:QuickSwitchLauncherToLetterboxAppTest` + * + * Actions: + * ``` + * Launch a letterboxed app + * Navigate home to show launcher + * Swipe right from the bottom of the screen to quick switch back to the app + * ``` + */ + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : + BaseAppCompat(flicker) { + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + + tapl.setExpectedRotation(flicker.scenario.startRotation.value) + + letterboxApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper + .StateSyncBuilder() + .withHomeActivityVisible() + .withWindowSurfaceDisappeared(letterboxApp) + .waitForAndVerify() + + startDisplayBounds = + wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found") + } + transitions { + tapl.workspace.quickSwitchToPreviousApp() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(letterboxApp) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() + } + teardown { letterboxApp.exit(wmHelper) } + } + + /** + * Checks that [letterboxApp] is the top window at the end of the transition once we have fully + * quick switched from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun endsWithAppBeingOnTop() { + flicker.assertWmEnd { this.isAppWindowOnTop(letterboxApp) } + } + + /** Checks that the transition starts with the home activity being tagged as visible. */ + @Postsubmit + @Test + fun startsWithHomeActivityFlaggedVisible() { + flicker.assertWmStart { this.isHomeActivityVisible() } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] windows + * filling/covering exactly display size + */ + @Postsubmit + @Test + fun startsWithLauncherWindowsCoverFullScreen() { + flicker.assertWmStart { + this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] layers + * filling/covering exactly the display size. + */ + @Postsubmit + @Test + fun startsWithLauncherLayersCoverFullScreen() { + flicker.assertLayersStart { + this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] being the top + * window. + */ + @Postsubmit + @Test + fun startsWithLauncherBeingOnTop() { + flicker.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) } + } + + /** + * Checks that the transition ends with the home activity being flagged as not visible. By this + * point we should have quick switched away from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun endsWithHomeActivityFlaggedInvisible() { + flicker.assertWmEnd { this.isHomeActivityInvisible() } + } + + /** + * Checks that [letterboxApp]'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 appWindowBecomesAndStaysVisible() { + flicker.assertWm { + this.isAppWindowInvisible(letterboxApp) + .then() + .isAppWindowVisible(letterboxApp) } + } + + /** + * Checks that [letterboxApp]'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 appLayerBecomesAndStaysVisible() { + flicker.assertLayers { this.isInvisible(letterboxApp).then().isVisible(letterboxApp) } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] 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 launcherWindowBecomesAndStaysInvisible() { + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] 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 launcherLayerBecomesAndStaysInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isInvisible(ComponentNameMatcher.LAUNCHER) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] window is visible at least until the app + * window is visible. Ensures that at any point, either the launcher or [letterboxApp] windows + * are at least partially visible. + */ + @Postsubmit + @Test + fun appWindowIsVisibleOnceLauncherWindowIsInvisible() { + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(letterboxApp) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] layer is visible at least until the app layer + * is visible. Ensures that at any point, either the launcher or [letterboxApp] layers are at + * least partially visible. + */ + @Postsubmit + @Test + fun appLayerIsVisibleOnceLauncherLayerIsInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(letterboxApp) + } + } + + /** + * Checks that the [ComponentNameMatcher.LETTERBOX] layer is visible as soon as the + * [letterboxApp] layer is visible at the end of the transition once we have fully quick + * switched from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun appAndLetterboxLayersBothVisibleOnceLauncherIsInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(letterboxApp) + .isVisible(ComponentNameMatcher.LETTERBOX) } + } + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + companion object { + /** {@inheritDoc} */ + private var startDisplayBounds = Rect.EMPTY + + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return LegacyFlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), + supportedRotations = listOf(Rotation.ROTATION_90) + ) + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt index 36bbafb4c05e..8a85374d0712 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt @@ -16,12 +16,9 @@ package com.android.wm.shell.flicker.pip -import android.app.Instrumentation -import android.os.SystemClock import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.Rotation -import android.tools.device.apphelpers.StandardAppHelper import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest @@ -29,13 +26,9 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.filters.RequiresDevice -import androidx.test.uiautomator.By -import androidx.test.uiautomator.BySelector -import androidx.test.uiautomator.UiObject2 -import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -73,13 +66,11 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) : AutoEnterPipOnGoToHomeTest(flicker) { private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) /** Second app used to enter split screen mode */ - protected val secondAppForSplitScreen = getSplitScreenApp(instrumentation) - fun getSplitScreenApp(instrumentation: Instrumentation): StandardAppHelper = - SimpleAppHelper( - instrumentation, - ActivityOptions.SplitScreen.Primary.LABEL, - ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() - ) + private val secondAppForSplitScreen = SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Primary.LABEL, + ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() + ) /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit @@ -88,14 +79,7 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) : secondAppForSplitScreen.launchViaIntent(wmHelper) pipApp.launchViaIntent(wmHelper) tapl.goHome() - enterSplitScreen() - // wait until split screen is established - wmHelper - .StateSyncBuilder() - .withWindowSurfaceAppeared(pipApp) - .withWindowSurfaceAppeared(secondAppForSplitScreen) - .withSplitDividerVisible() - .waitForAndVerify() + SplitScreenUtils.enterSplit(wmHelper, tapl, device, pipApp, secondAppForSplitScreen) pipApp.enableAutoEnterForPipActivity() } teardown { @@ -107,46 +91,6 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) : transitions { tapl.goHome() } } - // TODO(b/285400227) merge the code in a common utility - this is copied from SplitScreenUtils - private val TIMEOUT_MS = 3_000L - private val overviewSnapshotSelector: BySelector - get() = By.res(LAUNCHER_UI_PACKAGE_NAME, "snapshot") - private fun enterSplitScreen() { - // Note: The initial split position in landscape is different between tablet and phone. - // In landscape, tablet will let the first app split to right side, and phone will - // split to left side. - if (tapl.isTablet) { - // TAPL's currentTask on tablet is sometimes not what we expected if the overview - // contains more than 3 task views. We need to use uiautomator directly to find the - // second task to split. - tapl.workspace.switchToOverview().overviewActions.clickSplit() - val snapshots = - tapl.device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS) - if (snapshots == null || snapshots.size < 1) { - error("Fail to find a overview snapshot to split.") - } - - // Find the second task in the upper right corner in split select mode by sorting - // 'left' in descending order and 'top' in ascending order. - snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> - t2.getVisibleBounds().left - t1.getVisibleBounds().left - } - snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> - t1.getVisibleBounds().top - t2.getVisibleBounds().top - } - snapshots[0].click() - } else { - tapl.workspace - .switchToOverview() - .currentTask - .tapMenu() - .tapSplitMenuItem() - .currentTask - .open() - } - SystemClock.sleep(TIMEOUT_MS) - } - @Presubmit @Test override fun pipOverlayLayerAppearThenDisappear() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt new file mode 100644 index 000000000000..76ad6b9bc49c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class CopyContentInSplit +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val textEditApp = SplitScreenUtils.getIme(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) + } + + @Test + open fun copyContentInSplit() { + SplitScreenUtils.copyContentInSplit(instrumentation, device, primaryApp, textEditApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt new file mode 100644 index 000000000000..25182b40a300 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class DismissSplitScreenByDivider +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun dismissSplitScreenByDivider() { + if (tapl.isTablet) { + SplitScreenUtils.dragDividerToDismissSplit( + device, + wmHelper, + dragToRight = false, + dragToBottom = true + ) + } else { + SplitScreenUtils.dragDividerToDismissSplit( + device, + wmHelper, + dragToRight = true, + dragToBottom = true + ) + } + wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify() + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt new file mode 100644 index 000000000000..000b628b5ff6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class DismissSplitScreenByGoHome +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun dismissSplitScreenByGoHome() { + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt new file mode 100644 index 000000000000..dd9ff3c7f64f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class DragDividerToResize +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun dragDividerToResize() { + SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt new file mode 100644 index 000000000000..4bbb9aa07911 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromAllApps +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + tapl.goHome() + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromAllApps() { + tapl.launchedAppState.taskbar + .openAllApps() + .getAppIcon(secondaryApp.appName) + .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt new file mode 100644 index 000000000000..a2b75267b662 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromNotification +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + // Send a notification + sendNotificationApp.launchViaIntent(wmHelper) + sendNotificationApp.postNotification(wmHelper) + tapl.goHome() + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromNotification() { + SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + sendNotificationApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt new file mode 100644 index 000000000000..1ccd8133c234 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromShortcut +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + Assume.assumeTrue(tapl.isTablet) + + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + tapl.goHome() + SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName) + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromShortcut() { + tapl.launchedAppState.taskbar + .getAppIcon(secondaryApp.appName) + .openDeepShortcutMenu() + .getMenuItem("Split Screen Secondary Activity") + .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + + // TODO: Do we want this check in here? Add to the other tests? + // flicker.splitScreenEntered( + // primaryApp, + // secondaryApp, + // fromOtherApp = false, + // appExistAtStart = false + // ) + } + + @After + fun teardwon() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt new file mode 100644 index 000000000000..664786b9e523 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromTaskbar +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + tapl.goHome() + SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName) + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromTaskbar() { + tapl.launchedAppState.taskbar + .getAppIcon(secondaryApp.appName) + .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt new file mode 100644 index 000000000000..88fd0841b174 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenFromOverview +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + primaryApp.launchViaIntent(wmHelper) + secondaryApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withHomeActivityVisible() + .waitForAndVerify() + } + + @Test + open fun enterSplitScreenFromOverview() { + SplitScreenUtils.splitFromOverview(tapl, device) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt new file mode 100644 index 000000000000..83a18e8d0b49 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.graphics.Point +import android.os.SystemClock +import android.platform.test.rule.NavigationModeRule +import android.platform.test.rule.PressHomeRule +import android.platform.test.rule.UnlockScreenRule +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.common.traces.component.IComponentMatcher +import android.tools.common.traces.component.IComponentNameMatcher +import android.tools.device.apphelpers.MessagingAppHelper +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.flicker.rules.ChangeDisplayOrientationRule +import android.tools.device.flicker.rules.LaunchAppRule +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent +import android.view.InputDevice +import android.view.MotionEvent +import android.view.ViewConfiguration +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.ImeAppHelper +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.helpers.NotificationAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME +import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME +import org.junit.Assert.assertNotNull +import org.junit.rules.RuleChain + +object SplitScreenUtils { + private const val TIMEOUT_MS = 3_000L + private const val DRAG_DURATION_MS = 1_000L + private const val NOTIFICATION_SCROLLER = "notification_stack_scroller" + private const val DIVIDER_BAR = "docked_divider_handle" + private const val OVERVIEW_SNAPSHOT = "snapshot" + private const val GESTURE_STEP_MS = 16L + private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L + private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#") + + private val notificationScrollerSelector: BySelector + get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER) + private val notificationContentSelector: BySelector + get() = By.text("Flicker Test Notification") + private val dividerBarSelector: BySelector + get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR) + private val overviewSnapshotSelector: BySelector + get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT) + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + + fun testSetupRule(navigationMode: () -> NavBar, rotation: () -> Rotation): RuleChain { + return RuleChain.outerRule(UnlockScreenRule()) + .around( + NavigationModeRule( + navigationMode().value, + /* changeNavigationModeAfterTest */ false + ) + ) + .around( + LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false) + ) + .around(RemoveAllTasksButHomeRule()) + .around( + ChangeDisplayOrientationRule( + rotation(), + resetOrientationAfterTest = false, + clearCacheAfterParsing = false + ) + ) + .around(PressHomeRule()) + } + + fun getPrimary(instrumentation: Instrumentation): StandardAppHelper = + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Primary.LABEL, + ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() + ) + + fun getSecondary(instrumentation: Instrumentation): StandardAppHelper = + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Secondary.LABEL, + ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent() + ) + + fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper = + NonResizeableAppHelper(instrumentation) + + fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper = + NotificationAppHelper(instrumentation) + + fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation) + + fun waitForSplitComplete( + wmHelper: WindowManagerStateHelper, + primaryApp: IComponentMatcher, + secondaryApp: IComponentMatcher, + ) { + wmHelper + .StateSyncBuilder() + .withWindowSurfaceAppeared(primaryApp) + .withWindowSurfaceAppeared(secondaryApp) + .withSplitDividerVisible() + .waitForAndVerify() + } + + fun enterSplit( + wmHelper: WindowManagerStateHelper, + tapl: LauncherInstrumentation, + device: UiDevice, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper + ) { + primaryApp.launchViaIntent(wmHelper) + secondaryApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + splitFromOverview(tapl, device) + waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) { + // Note: The initial split position in landscape is different between tablet and phone. + // In landscape, tablet will let the first app split to right side, and phone will + // split to left side. + if (tapl.isTablet) { + // TAPL's currentTask on tablet is sometimes not what we expected if the overview + // contains more than 3 task views. We need to use uiautomator directly to find the + // second task to split. + tapl.workspace.switchToOverview().overviewActions.clickSplit() + val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS) + if (snapshots == null || snapshots.size < 1) { + error("Fail to find a overview snapshot to split.") + } + + // Find the second task in the upper right corner in split select mode by sorting + // 'left' in descending order and 'top' in ascending order. + snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> + t2.getVisibleBounds().left - t1.getVisibleBounds().left + } + snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> + t1.getVisibleBounds().top - t2.getVisibleBounds().top + } + snapshots[0].click() + } else { + tapl.workspace + .switchToOverview() + .currentTask + .tapMenu() + .tapSplitMenuItem() + .currentTask + .open() + } + SystemClock.sleep(TIMEOUT_MS) + } + + fun enterSplitViaIntent( + wmHelper: WindowManagerStateHelper, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper + ) { + val stringExtras = + mapOf(ActivityOptions.SplitScreen.Primary.EXTRA_LAUNCH_ADJACENT to "true") + primaryApp.launchViaIntent(wmHelper, null, null, stringExtras) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + fun dragFromNotificationToSplit( + instrumentation: Instrumentation, + device: UiDevice, + wmHelper: WindowManagerStateHelper + ) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + + // Pull down the notifications + device.swipe( + displayBounds.centerX(), + 5, + displayBounds.centerX(), + displayBounds.bottom, + 50 /* steps */ + ) + SystemClock.sleep(TIMEOUT_MS) + + // Find the target notification + val notificationScroller = + device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS) + ?: error("Unable to find view $notificationScrollerSelector") + var notificationContent = notificationScroller.findObject(notificationContentSelector) + + while (notificationContent == null) { + device.swipe( + displayBounds.centerX(), + displayBounds.centerY(), + displayBounds.centerX(), + displayBounds.centerY() - 150, + 20 /* steps */ + ) + notificationContent = notificationScroller.findObject(notificationContentSelector) + } + + // Drag to split + val dragStart = notificationContent.visibleCenter + val dragMiddle = Point(dragStart.x + 50, dragStart.y) + val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4) + val downTime = SystemClock.uptimeMillis() + + touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart) + // It needs a horizontal movement to trigger the drag + touchMove( + instrumentation, + downTime, + SystemClock.uptimeMillis(), + DRAG_DURATION_MS, + dragStart, + dragMiddle + ) + touchMove( + instrumentation, + downTime, + SystemClock.uptimeMillis(), + DRAG_DURATION_MS, + dragMiddle, + dragEnd + ) + // Wait for a while to start splitting + SystemClock.sleep(TIMEOUT_MS) + touch( + instrumentation, + MotionEvent.ACTION_UP, + downTime, + SystemClock.uptimeMillis(), + GESTURE_STEP_MS, + dragEnd + ) + SystemClock.sleep(TIMEOUT_MS) + } + + fun touch( + instrumentation: Instrumentation, + action: Int, + downTime: Long, + eventTime: Long, + duration: Long, + point: Point + ) { + val motionEvent = + MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0) + motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN + instrumentation.uiAutomation.injectInputEvent(motionEvent, true) + motionEvent.recycle() + SystemClock.sleep(duration) + } + + fun touchMove( + instrumentation: Instrumentation, + downTime: Long, + eventTime: Long, + duration: Long, + from: Point, + to: Point + ) { + val steps: Long = duration / GESTURE_STEP_MS + var currentTime = eventTime + var currentX = from.x.toFloat() + var currentY = from.y.toFloat() + val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat() + val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat() + + for (i in 1..steps) { + val motionMove = + MotionEvent.obtain( + downTime, + currentTime, + MotionEvent.ACTION_MOVE, + currentX, + currentY, + 0 + ) + motionMove.source = InputDevice.SOURCE_TOUCHSCREEN + instrumentation.uiAutomation.injectInputEvent(motionMove, true) + motionMove.recycle() + + currentTime += GESTURE_STEP_MS + if (i == steps - 1) { + currentX = to.x.toFloat() + currentY = to.y.toFloat() + } else { + currentX += stepX + currentY += stepY + } + SystemClock.sleep(GESTURE_STEP_MS) + } + } + + fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) { + tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0)) + val allApps = tapl.workspace.switchToAllApps() + allApps.freeze() + try { + allApps.getAppIcon(appName).dragToHotseat(0) + } finally { + allApps.unfreeze() + } + } + + fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200) + + wmHelper + .StateSyncBuilder() + .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER) + .waitForAndVerify() + } + + fun dragDividerToDismissSplit( + device: UiDevice, + wmHelper: WindowManagerStateHelper, + dragToRight: Boolean, + dragToBottom: Boolean + ) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + dividerBar.drag( + Point( + if (dragToRight) { + displayBounds.width * 4 / 5 + } else { + displayBounds.width * 1 / 5 + }, + if (dragToBottom) { + displayBounds.height * 4 / 5 + } else { + displayBounds.height * 1 / 5 + } + ) + ) + } + + fun doubleTapDividerToSwitch(device: UiDevice) { + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + val interval = + (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2 + dividerBar.click() + SystemClock.sleep(interval.toLong()) + dividerBar.click() + } + + fun copyContentInSplit( + instrumentation: Instrumentation, + device: UiDevice, + sourceApp: IComponentNameMatcher, + destinationApp: IComponentNameMatcher, + ) { + // Copy text from sourceApp + val textView = + device.wait( + Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")), + TIMEOUT_MS + ) + assertNotNull("Unable to find the TextView", textView) + textView.click(LONG_PRESS_TIME_MS) + + val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS) + assertNotNull("Unable to find the copy button", copyBtn) + copyBtn.click() + + // Paste text to destinationApp + val editText = + device.wait( + Until.findObject(By.res(destinationApp.packageName, "plain_text_input")), + TIMEOUT_MS + ) + assertNotNull("Unable to find the EditText", editText) + editText.click(LONG_PRESS_TIME_MS) + + val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS) + assertNotNull("Unable to find the paste button", pasteBtn) + pasteBtn.click() + + // Verify text + if (!textView.text.contentEquals(editText.text)) { + error("Fail to copy content in split") + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt new file mode 100644 index 000000000000..e5501f4c6cd2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.graphics.Point +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.helpers.WindowUtils +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchAppByDoubleTapDivider +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + tapl.workspace.switchToOverview().dismissAllTasks() + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun switchAppByDoubleTapDivider() { + SplitScreenUtils.doubleTapDividerToSwitch(device) + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + + waitForLayersToSwitch(wmHelper) + waitForWindowsToSwitch(wmHelper) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } + + private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) { + wmHelper + .StateSyncBuilder() + .add("appWindowsSwitched") { + val primaryAppWindow = + it.wmState.visibleWindows.firstOrNull { window -> + primaryApp.windowMatchesAnyOf(window) + } + ?: return@add false + val secondaryAppWindow = + it.wmState.visibleWindows.firstOrNull { window -> + secondaryApp.windowMatchesAnyOf(window) + } + ?: return@add false + + if (isLandscape(rotation)) { + return@add if (isTablet()) { + secondaryAppWindow.frame.right <= primaryAppWindow.frame.left + } else { + primaryAppWindow.frame.right <= secondaryAppWindow.frame.left + } + } else { + return@add if (isTablet()) { + primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top + } else { + primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top + } + } + } + .waitForAndVerify() + } + + private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) { + wmHelper + .StateSyncBuilder() + .add("appLayersSwitched") { + val primaryAppLayer = + it.layerState.visibleLayers.firstOrNull { window -> + primaryApp.layerMatchesAnyOf(window) + } + ?: return@add false + val secondaryAppLayer = + it.layerState.visibleLayers.firstOrNull { window -> + secondaryApp.layerMatchesAnyOf(window) + } + ?: return@add false + + val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false + val secondaryVisibleRegion = + secondaryAppLayer.visibleRegion?.bounds ?: return@add false + + if (isLandscape(rotation)) { + return@add if (isTablet()) { + secondaryVisibleRegion.right <= primaryVisibleRegion.left + } else { + primaryVisibleRegion.right <= secondaryVisibleRegion.left + } + } else { + return@add if (isTablet()) { + primaryVisibleRegion.bottom <= secondaryVisibleRegion.top + } else { + primaryVisibleRegion.bottom <= secondaryVisibleRegion.top + } + } + } + .waitForAndVerify() + } + + private fun isLandscape(rotation: Rotation): Boolean { + val displayBounds = WindowUtils.getDisplayBounds(rotation) + return displayBounds.width > displayBounds.height + } + + private fun isTablet(): Boolean { + val sizeDp: Point = device.displaySizeDp + val LARGE_SCREEN_DP_THRESHOLD = 600 + return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt new file mode 100644 index 000000000000..b3f1e87380e4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBackToSplitFromAnotherApp +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + thirdApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify() + } + + @Test + open fun switchBackToSplitFromAnotherApp() { + tapl.launchedAppState.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt new file mode 100644 index 000000000000..d1121162c267 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBackToSplitFromHome +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @Test + open fun switchBackToSplitFromHome() { + tapl.workspace.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt new file mode 100644 index 000000000000..9ab924ca46f6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBackToSplitFromRecent +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @Test + open fun switchBackToSplitFromRecent() { + tapl.workspace.switchToOverview().currentTask.open() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt new file mode 100644 index 000000000000..b694dfa7f384 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBetweenSplitPairs +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val thirdApp = SplitScreenUtils.getIme(instrumentation) + private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp) + SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp) + } + + @Test + open fun switchBetweenSplitPairs() { + tapl.launchedAppState.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + thirdApp.exit(wmHelper) + fourthApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt new file mode 100644 index 000000000000..f78b7881735a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class UnlockKeyguardToSplitScreen { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = + SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { Rotation.ROTATION_0 }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(Rotation.ROTATION_0.value) + + SplitScreenUtils.enterSplitViaIntent(wmHelper, primaryApp, secondaryApp) + } + + @Test + open fun unlockKeyguardToSplitScreen() { + device.sleep() + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + device.wakeUp() + device.pressMenu() + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt index 580b153421a4..d3434a5b18e5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt @@ -21,6 +21,7 @@ import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import com.android.server.wm.flicker.helpers.setRotation import com.android.wm.shell.flicker.BaseBenchmarkTest +import com.android.wm.shell.flicker.SplitScreenUtils abstract class SplitScreenBase(flicker: LegacyFlickerTest) : BaseBenchmarkTest(flicker) { protected val context: Context = instrumentation.context diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt index d1ca9eac198d..9c68aa488d65 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt @@ -25,7 +25,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils 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/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt index 73acb1f0cc47..21ac7839c2f4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt @@ -25,7 +25,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenDismissed import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils 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/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt index 86ffd2af6748..931bff6f97e5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt @@ -25,7 +25,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenDismissed import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils 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/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt index dfde3b669813..7fa2c0bba2be 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt @@ -24,7 +24,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt index d13e4134a961..952051f62a92 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt @@ -26,7 +26,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt index 1d4166922b13..1de1c0c5e91e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt @@ -26,7 +26,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt index b4bafa79cd48..929c7eab3105 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt @@ -26,7 +26,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt index da44ecdb9304..9f829c9dc46e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt @@ -26,7 +26,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt index af06d6da2518..1d5518f319d8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt @@ -25,7 +25,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils 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/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt index 23156b5d1628..a7fb93e9b645 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt @@ -28,7 +28,7 @@ import android.tools.device.helpers.WindowUtils import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils 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/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt index 2d810d3e2631..8358aff00213 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt @@ -26,7 +26,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils 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/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt index f6df1e42d1b7..b63c7659c894 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt @@ -26,7 +26,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils 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/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt index ba46bdcdad21..ce5a409b2756 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt @@ -26,7 +26,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils 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/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt index 0d871e500688..9821bfac7a74 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt @@ -24,7 +24,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -64,8 +64,7 @@ open class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerT thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit @Test open fun cujCompleted() {} + @PlatinumTest(focusArea = "sysui") @Presubmit @Test open fun cujCompleted() {} companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt index 7952b7125a34..4fc4627093db 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt @@ -23,7 +23,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl index 2231ce14eea6..e46d34e81483 100644 --- a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl +++ b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl @@ -17,9 +17,22 @@ package android.media.projection; import android.media.projection.MediaProjectionInfo; +import android.view.ContentRecordingSession; /** {@hide} */ oneway interface IMediaProjectionWatcherCallback { void onStart(in MediaProjectionInfo info); void onStop(in MediaProjectionInfo info); + /** + * Called when the {@link ContentRecordingSession} was set for the current media + * projection. + * + * @param info always present and contains information about the media projection host. + * @param session the recording session for the current media projection. Can be + * {@code null} when the recording will stop. + */ + void onRecordingSessionSet( + in MediaProjectionInfo info, + in @nullable ContentRecordingSession session + ); } diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java index 5703c429c32b..5a68c53b8f68 100644 --- a/media/java/android/media/projection/MediaProjectionManager.java +++ b/media/java/android/media/projection/MediaProjectionManager.java @@ -29,6 +29,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.util.ArrayMap; import android.util.Log; +import android.view.ContentRecordingSession; import android.view.Surface; import java.util.Map; @@ -300,7 +301,22 @@ public final class MediaProjectionManager { /** @hide */ public static abstract class Callback { public abstract void onStart(MediaProjectionInfo info); + public abstract void onStop(MediaProjectionInfo info); + + /** + * Called when the {@link ContentRecordingSession} was set for the current media + * projection. + * + * @param info always present and contains information about the media projection host. + * @param session the recording session for the current media projection. Can be + * {@code null} when the recording will stop. + */ + public void onRecordingSessionSet( + @NonNull MediaProjectionInfo info, + @Nullable ContentRecordingSession session + ) { + } } /** @hide */ @@ -335,5 +351,13 @@ public final class MediaProjectionManager { } }); } + + @Override + public void onRecordingSessionSet( + @NonNull final MediaProjectionInfo info, + @Nullable final ContentRecordingSession session + ) { + mHandler.post(() -> mCallback.onRecordingSessionSet(info, session)); + } } } diff --git a/packages/SettingsLib/res/layout/dialog_with_icon.xml b/packages/SettingsLib/res/layout/dialog_with_icon.xml index 2f30508ca504..3586dcb2909c 100644 --- a/packages/SettingsLib/res/layout/dialog_with_icon.xml +++ b/packages/SettingsLib/res/layout/dialog_with_icon.xml @@ -33,13 +33,13 @@ android:importantForAccessibility="no"/> <TextView android:id="@+id/dialog_with_icon_title" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" style="@style/DialogWithIconTitle"/> <TextView android:id="@+id/dialog_with_icon_message" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" style="@style/TextAppearanceSmall"/> diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index b5e4fa38d244..af06d7304160 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -243,7 +243,9 @@ public class RestrictedSwitchPreference extends SwitchPreference { return mHelper != null ? mHelper.packageName : null; } - public void updateState(@NonNull String packageName, int uid, boolean isEnabled) { + /** Updates enabled state based on associated package. */ + public void updateState( + @NonNull String packageName, int uid, boolean isEnableAllowed, boolean isEnabled) { mHelper.updatePackageDetails(packageName, uid); if (mAppOpsManager == null) { mAppOpsManager = getContext().getSystemService(AppOpsManager.class); @@ -254,7 +256,9 @@ public class RestrictedSwitchPreference extends SwitchPreference { final boolean ecmEnabled = getContext().getResources().getBoolean( com.android.internal.R.bool.config_enhancedConfirmationModeEnabled); final boolean appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED; - if (isEnabled) { + if (!isEnableAllowed && !isEnabled) { + setEnabled(false); + } else if (isEnabled) { setEnabled(true); } else if (appOpsAllowed && isDisabledByAppOps()) { setEnabled(true); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 2e6bb535a8f0..f522fd13c9f8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -583,7 +583,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> */ public void setName(String name) { // Prevent getName() to be set to null if setName(null) is called - if (name == null || TextUtils.equals(name, getName())) { + if (TextUtils.isEmpty(name) || TextUtils.equals(name, getName())) { return; } mDevice.setAlias(name); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java index cd6609ec463e..963bd9daa975 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java @@ -15,6 +15,8 @@ */ package com.android.settingslib.media; +import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; + import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Context; @@ -22,6 +24,7 @@ import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.media.RouteListingPreference; import com.android.settingslib.R; import com.android.settingslib.bluetooth.BluetoothUtils; @@ -39,7 +42,13 @@ public class BluetoothMediaDevice extends MediaDevice { BluetoothMediaDevice(Context context, CachedBluetoothDevice device, MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) { - super(context, routerManager, info, packageName, null); + this(context, device, routerManager, info, packageName, null); + } + + BluetoothMediaDevice(Context context, CachedBluetoothDevice device, + MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName, + RouteListingPreference.Item item) { + super(context, routerManager, info, packageName, item); mCachedDevice = device; mAudioManager = context.getSystemService(AudioManager.class); initDeviceRecord(); @@ -58,6 +67,12 @@ public class BluetoothMediaDevice extends MediaDevice { } @Override + public int getSelectionBehavior() { + // We don't allow apps to override the selection behavior of system routes. + return SELECTION_BEHAVIOR_TRANSFER; + } + + @Override public Drawable getIcon() { return BluetoothUtils.isAdvancedUntetheredDevice(mCachedDevice.getDevice()) ? mContext.getDrawable(R.drawable.ic_earbuds_advanced) diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index f2abf8727155..1728e405fa29 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -91,6 +91,7 @@ public class InfoMediaManager extends MediaManager { MediaRouter2Manager mRouterManager; @VisibleForTesting String mPackageName; + boolean mIsScanning = false; private MediaDevice mCurrentConnectedDevice; private LocalBluetoothManager mBluetoothManager; @@ -110,22 +111,29 @@ public class InfoMediaManager extends MediaManager { @Override public void startScan() { - mMediaDevices.clear(); - mRouterManager.registerCallback(mExecutor, mMediaRouterCallback); - mRouterManager.registerScanRequest(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE - && !TextUtils.isEmpty(mPackageName)) { - RouteListingPreference routeListingPreference = - mRouterManager.getRouteListingPreference(mPackageName); - Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, mPreferenceItemMap); + if (!mIsScanning) { + mMediaDevices.clear(); + mRouterManager.registerCallback(mExecutor, mMediaRouterCallback); + mRouterManager.registerScanRequest(); + mIsScanning = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE + && !TextUtils.isEmpty(mPackageName)) { + RouteListingPreference routeListingPreference = + mRouterManager.getRouteListingPreference(mPackageName); + Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, + mPreferenceItemMap); + } + refreshDevices(); } - refreshDevices(); } @Override public void stopScan() { - mRouterManager.unregisterCallback(mMediaRouterCallback); - mRouterManager.unregisterScanRequest(); + if (mIsScanning) { + mRouterManager.unregisterCallback(mMediaRouterCallback); + mRouterManager.unregisterScanRequest(); + mIsScanning = false; + } } /** @@ -560,8 +568,10 @@ public class InfoMediaManager extends MediaManager { case TYPE_HDMI: case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: - mediaDevice = - new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName); + mediaDevice = mPreferenceItemMap.containsKey(route.getId()) ? new PhoneMediaDevice( + mContext, mRouterManager, route, mPackageName, + mPreferenceItemMap.get(route.getId())) : new PhoneMediaDevice(mContext, + mRouterManager, route, mPackageName); break; case TYPE_HEARING_AID: case TYPE_BLUETOOTH_A2DP: @@ -571,8 +581,11 @@ public class InfoMediaManager extends MediaManager { final CachedBluetoothDevice cachedDevice = mBluetoothManager.getCachedDeviceManager().findDevice(device); if (cachedDevice != null) { - mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager, - route, mPackageName); + mediaDevice = mPreferenceItemMap.containsKey(route.getId()) + ? new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager, + route, mPackageName, mPreferenceItemMap.get(route.getId())) + : new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager, + route, mPackageName); } break; case TYPE_REMOTE_AUDIO_VIDEO_RECEIVER: @@ -701,20 +714,19 @@ public class InfoMediaManager extends MediaManager { List<MediaRoute2Info> selectedRouteInfos, List<MediaRoute2Info> infolist, List<RouteListingPreference.Item> preferenceRouteListing) { final List<MediaRoute2Info> sortedInfoList = new ArrayList<>(selectedRouteInfos); + infolist.removeAll(selectedRouteInfos); + sortedInfoList.addAll(infolist.stream().filter( + MediaRoute2Info::isSystemRoute).collect(Collectors.toList())); for (RouteListingPreference.Item item : preferenceRouteListing) { for (MediaRoute2Info info : infolist) { if (item.getRouteId().equals(info.getId()) - && !selectedRouteInfos.contains(info)) { + && !selectedRouteInfos.contains(info) + && !info.isSystemRoute()) { sortedInfoList.add(info); break; } } } - if (sortedInfoList.size() != infolist.size()) { - infolist.removeAll(sortedInfoList); - sortedInfoList.addAll(infolist.stream().filter( - MediaRoute2Info::isSystemRoute).collect(Collectors.toList())); - } return sortedInfoList; } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 34519c993d27..accd88c2bfe3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -24,10 +24,13 @@ import static android.media.MediaRoute2Info.TYPE_USB_HEADSET; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; +import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; + import android.content.Context; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.media.RouteListingPreference; import androidx.annotation.VisibleForTesting; @@ -51,7 +54,12 @@ public class PhoneMediaDevice extends MediaDevice { PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) { - super(context, routerManager, info, packageName, null); + this(context, routerManager, info, packageName, null); + } + + PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info, + String packageName, RouteListingPreference.Item item) { + super(context, routerManager, info, packageName, item); mDeviceIconUtil = new DeviceIconUtil(); initDeviceRecord(); } @@ -86,6 +94,12 @@ public class PhoneMediaDevice extends MediaDevice { } @Override + public int getSelectionBehavior() { + // We don't allow apps to override the selection behavior of system routes. + return SELECTION_BEHAVIOR_TRANSFER; + } + + @Override public String getSummary() { return mSummary; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index 6444f3bd4341..4b61ff1177bd 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -1015,6 +1015,13 @@ public class CachedBluetoothDeviceTest { } @Test + public void setName_setDeviceNameIsEmpty() { + mCachedDevice.setName(""); + + verify(mDevice, never()).setAlias(any()); + } + + @Test public void getProfileConnectionState_nullProfile_returnDisconnected() { assertThat(mCachedDevice.getProfileConnectionState(null)).isEqualTo( BluetoothProfile.STATE_DISCONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 39780f3a96ed..7b8815e7c05c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -114,6 +114,23 @@ public class InfoMediaManagerTest { } @Test + public void stopScan_notStartFirst_notCallsUnregister() { + mInfoMediaManager.mRouterManager = mRouterManager; + mInfoMediaManager.stopScan(); + + verify(mRouterManager, never()).unregisterScanRequest(); + } + + @Test + public void stopScan_startFirst_callsUnregister() { + mInfoMediaManager.mRouterManager = mRouterManager; + mInfoMediaManager.startScan(); + mInfoMediaManager.stopScan(); + + verify(mRouterManager).unregisterScanRequest(); + } + + @Test public void onRouteAdded_getAvailableRoutes_shouldAddMediaDevice() { final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>(); final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class); @@ -327,11 +344,12 @@ public class InfoMediaManagerTest { routeListingPreference); mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated(); - assertThat(mInfoMediaManager.mMediaDevices).hasSize(3); + assertThat(mInfoMediaManager.mMediaDevices).hasSize(4); assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID); - assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_4); - assertThat(mInfoMediaManager.mMediaDevices.get(1).isSuggestedDevice()).isTrue(); - assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_3); + assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_1); + assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_4); + assertThat(mInfoMediaManager.mMediaDevices.get(2).isSuggestedDevice()).isTrue(); + assertThat(mInfoMediaManager.mMediaDevices.get(3).getId()).isEqualTo(TEST_ID_3); } @Test @@ -405,8 +423,13 @@ public class InfoMediaManagerTest { when(availableInfo3.getClientPackageName()).thenReturn(packageName); availableRoutes.add(availableInfo3); - when(mRouterManager.getAvailableRoutes(packageName)).thenReturn( - availableRoutes); + final MediaRoute2Info availableInfo4 = mock(MediaRoute2Info.class); + when(availableInfo4.getId()).thenReturn(TEST_ID_1); + when(availableInfo4.isSystemRoute()).thenReturn(true); + when(availableInfo4.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + availableRoutes.add(availableInfo4); + + when(mRouterManager.getAvailableRoutes(packageName)).thenReturn(availableRoutes); return availableRoutes; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java index c058a61a3e9e..f22e090fe7df 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java @@ -19,6 +19,9 @@ import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP; import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; +import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP; + +import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; import static com.google.common.truth.Truth.assertThat; @@ -32,6 +35,7 @@ import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; import android.media.NearbyDevice; +import android.media.RouteListingPreference; import android.os.Parcel; import com.android.settingslib.bluetooth.A2dpProfile; @@ -110,6 +114,8 @@ public class MediaDeviceTest { @Mock private MediaRouter2Manager mMediaRouter2Manager; + private RouteListingPreference.Item mItem; + private BluetoothMediaDevice mBluetoothMediaDevice1; private BluetoothMediaDevice mBluetoothMediaDevice2; private BluetoothMediaDevice mBluetoothMediaDevice3; @@ -497,4 +503,21 @@ public class MediaDeviceTest { assertThat(mBluetoothMediaDevice1.getFeatures().size()).isEqualTo(0); } + + @Test + public void getSelectionBehavior_setItemWithSelectionBehaviorOnSystemRoute_returnTransfer() { + mItem = new RouteListingPreference.Item.Builder(DEVICE_ADDRESS_1) + .setSelectionBehavior(SELECTION_BEHAVIOR_GO_TO_APP) + .build(); + mBluetoothMediaDevice1 = new BluetoothMediaDevice(mContext, mCachedDevice1, + mMediaRouter2Manager, null /* MediaRoute2Info */, TEST_PACKAGE_NAME, mItem); + mPhoneMediaDevice = + new PhoneMediaDevice(mContext, mMediaRouter2Manager, mPhoneRouteInfo, + TEST_PACKAGE_NAME, mItem); + + assertThat(mBluetoothMediaDevice1.getSelectionBehavior()).isEqualTo( + SELECTION_BEHAVIOR_TRANSFER); + assertThat(mPhoneMediaDevice.getSelectionBehavior()).isEqualTo( + SELECTION_BEHAVIOR_TRANSFER); + } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index dd8eb3b3f3fb..c740423c39af 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -345,6 +345,8 @@ public class SettingsBackupTest { Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS, Settings.Global.MOBILE_DATA, // Candidate for backup? Settings.Global.MOBILE_DATA_ALWAYS_ON, + Settings.Global.DSRM_DURATION_MILLIS, + Settings.Global.DSRM_ENABLED_ACTIONS, Settings.Global.MODE_RINGER, Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, Settings.Global.MULTI_SIM_SMS_PROMPT, diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt index d4a81f9c765d..ac1ef1509415 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/Expandable.kt @@ -70,8 +70,10 @@ import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp -import androidx.lifecycle.ViewTreeLifecycleOwner -import androidx.lifecycle.ViewTreeViewModelStoreOwner +import androidx.lifecycle.findViewTreeLifecycleOwner +import androidx.lifecycle.findViewTreeViewModelStoreOwner +import androidx.lifecycle.setViewTreeLifecycleOwner +import androidx.lifecycle.setViewTreeViewModelStoreOwner import com.android.systemui.animation.Expandable import com.android.systemui.animation.LaunchAnimator import kotlin.math.max @@ -368,13 +370,10 @@ private fun AnimatedContentInOverlay( context, overlay, ) - ViewTreeLifecycleOwner.set( - overlayViewGroup, - ViewTreeLifecycleOwner.get(composeViewRoot), - ) - ViewTreeViewModelStoreOwner.set( - overlayViewGroup, - ViewTreeViewModelStoreOwner.get(composeViewRoot), + + overlayViewGroup.setViewTreeLifecycleOwner(composeViewRoot.findViewTreeLifecycleOwner()) + overlayViewGroup.setViewTreeViewModelStoreOwner( + composeViewRoot.findViewTreeViewModelStoreOwner() ) ViewTreeSavedStateRegistryOwner.set( overlayViewGroup, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index 75bf2813a321..13acde206247 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -121,8 +121,8 @@ fun FooterActions( } } - val backgroundColor = colorAttr(R.attr.underSurfaceColor) - val contentColor = LocalAndroidColorScheme.current.deprecated.textColorPrimary + val backgroundColor = colorAttr(R.attr.underSurface) + val contentColor = LocalAndroidColorScheme.current.onSurface val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius) val backgroundModifier = remember( @@ -268,7 +268,7 @@ private fun NumberButton( val interactionSource = remember { MutableInteractionSource() } Expandable( - color = colorAttr(R.attr.offStateColor), + color = colorAttr(R.attr.shadeInactive), shape = CircleShape, onClick = onClick, interactionSource = interactionSource, @@ -287,7 +287,7 @@ private fun NumberButton( number.toString(), modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.bodyLarge, - color = LocalAndroidColorScheme.current.deprecated.textColorPrimary, + color = colorAttr(R.attr.onShadeInactiveVariant), // TODO(b/242040009): This should only use a standard text style instead and // should not override the text size. fontSize = 18.sp, @@ -305,7 +305,7 @@ private fun NumberButton( @Composable private fun NewChangesDot(modifier: Modifier = Modifier) { val contentDescription = stringResource(R.string.fgs_dot_content_description) - val color = LocalAndroidColorScheme.current.deprecated.colorAccentTertiary + val color = LocalAndroidColorScheme.current.tertiary Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) { drawCircle(color) @@ -323,10 +323,9 @@ private fun TextButton( ) { Expandable( shape = CircleShape, - color = colorAttr(R.attr.underSurfaceColor), - contentColor = LocalAndroidColorScheme.current.deprecated.textColorSecondary, - borderStroke = - BorderStroke(1.dp, LocalAndroidColorScheme.current.deprecated.colorBackground), + color = colorAttr(R.attr.underSurface), + contentColor = LocalAndroidColorScheme.current.onSurfaceVariant, + borderStroke = BorderStroke(1.dp, colorAttr(R.attr.onShadeActive)), modifier = modifier.padding(horizontal = 4.dp), onClick = onClick, ) { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 648ef03895cd..d2084047583b 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -34,7 +34,7 @@ import com.android.systemui.animation.GlyphCallback import com.android.systemui.animation.TextAnimator import com.android.systemui.customization.R import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.DEBUG import java.io.PrintWriter import java.util.Calendar import java.util.Locale diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index 14434655fc92..12f7452fe913 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -24,11 +24,11 @@ import android.provider.Settings import android.util.Log import androidx.annotation.OpenForTesting import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogMessage import com.android.systemui.log.LogMessageImpl -import com.android.systemui.log.MessageInitializer -import com.android.systemui.log.MessagePrinter +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogMessage +import com.android.systemui.log.core.MessageInitializer +import com.android.systemui.log.core.MessagePrinter import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.ClockId import com.android.systemui.plugins.ClockMetadata diff --git a/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt index 6fc525369e8b..a4f4e134733f 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt @@ -16,6 +16,7 @@ package com.android.systemui.log +import com.android.systemui.log.core.LogLevel import com.google.errorprone.annotations.CompileTimeConstant class ConstantStringsLoggerImpl(val buffer: LogBuffer, val tag: String) : ConstantStringsLogger { diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt index 2007e7606ab8..e0051f59469d 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt @@ -19,6 +19,11 @@ package com.android.systemui.log import android.os.Trace import android.util.Log import com.android.systemui.common.buffer.RingBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogMessage +import com.android.systemui.log.core.MessageBuffer +import com.android.systemui.log.core.MessageInitializer +import com.android.systemui.log.core.MessagePrinter import com.google.errorprone.annotations.CompileTimeConstant import java.io.PrintWriter import java.util.concurrent.ArrayBlockingQueue @@ -73,7 +78,7 @@ constructor( private val maxSize: Int, private val logcatEchoTracker: LogcatEchoTracker, private val systrace: Boolean = true, -) { +) : MessageBuffer { private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() } private val echoMessageQueue: BlockingQueue<LogMessage>? = @@ -174,11 +179,11 @@ constructor( * store any relevant data on the message and then call [commit]. */ @Synchronized - fun obtain( + override fun obtain( tag: String, level: LogLevel, messagePrinter: MessagePrinter, - exception: Throwable? = null, + exception: Throwable?, ): LogMessage { if (!mutable) { return FROZEN_MESSAGE @@ -195,7 +200,7 @@ constructor( * have finished filling in its data fields. The message will be echoed to logcat if necessary. */ @Synchronized - fun commit(message: LogMessage) { + override fun commit(message: LogMessage) { if (!mutable) { return } @@ -292,11 +297,5 @@ constructor( } } -/** - * A function that will be called immediately to store relevant data on the log message. The value - * of `this` will be the LogMessage to be initialized. - */ -typealias MessageInitializer = LogMessage.() -> Unit - private const val TAG = "LogBuffer" private val FROZEN_MESSAGE = LogMessageImpl.create() diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt index 5e10f783850f..33cc199e7131 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt @@ -16,6 +16,10 @@ package com.android.systemui.log +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogMessage +import com.android.systemui.log.core.MessagePrinter + /** Recyclable implementation of [LogMessage]. */ data class LogMessageImpl( override var level: LogLevel, diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt index 55f3a738e4f1..ae717df50fce 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt @@ -16,6 +16,8 @@ package com.android.systemui.log +import com.android.systemui.log.core.LogLevel + /** Keeps track of which [LogBuffer] messages should also appear in logcat. */ interface LogcatEchoTracker { /** Whether [bufferName] should echo messages of [level] or higher to logcat. */ diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt index d0ad28f04670..9ff48cabc6f4 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt @@ -23,6 +23,7 @@ import android.os.Handler import android.os.Looper import android.os.Trace import android.provider.Settings +import com.android.systemui.log.core.LogLevel /** * Version of [LogcatEchoTracker] for debuggable builds diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt index 56966773d1b0..044d97f92b50 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt @@ -16,6 +16,8 @@ package com.android.systemui.log +import com.android.systemui.log.core.LogLevel + /** Production version of [LogcatEchoTracker] that isn't configurable. */ class LogcatEchoTrackerProd : LogcatEchoTracker { override val logInBackgroundThread = false diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogLevel.kt index 7d9647a13149..d30d8e9fe003 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogLevel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.log +package com.android.systemui.log.core import android.util.Log diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogMessage.kt index 8c3988b027fe..3bd6473738c7 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogMessage.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.log +package com.android.systemui.log.core import android.icu.text.SimpleDateFormat import java.io.PrintWriter @@ -67,6 +67,12 @@ interface LogMessage { } /** + * A function that will be called immediately to store relevant data on the log message. The value + * of `this` will be the LogMessage to be initialized. + */ +typealias MessageInitializer = LogMessage.() -> Unit + +/** * A function that will be called if and when the message needs to be dumped to logcat or a bug * report. It should read the data stored by the initializer and convert it to a human-readable * string. The value of `this` will be the LogMessage to be printed. **IMPORTANT:** The printer diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt new file mode 100644 index 000000000000..5729ab270487 --- /dev/null +++ b/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.core + +import com.google.errorprone.annotations.CompileTimeConstant + +/** Logs messages to the [MessageBuffer] with [tag]. */ +open class Logger(val buffer: MessageBuffer, val tag: String) { + /** + * Logs a message to the buffer. + * + * The actual string of the log message is not constructed until it is needed. To accomplish + * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is + * obtained and is passed to the [messageInitializer]. The initializer stores any relevant data + * on the message's fields. The message is then inserted into the buffer where it waits until it + * is either pushed out by newer messages or it needs to printed. If and when this latter moment + * occurs, the [messagePrinter] function is called on the message. It reads whatever data the + * initializer stored and converts it to a human-readable log message. + * + * @param level Which level to log the message at, both to the buffer and to logcat if it's + * echoed. In general, a module should split most of its logs into either INFO or DEBUG level. + * INFO level should be reserved for information that other parts of the system might care + * about, leaving the specifics of code's day-to-day operations to DEBUG. + * @param messagePrinter A function that will be called if and when the message needs to be + * dumped to logcat or a bug report. It should read the data stored by the initializer and + * convert it to a human-readable string. The value of `this` will be the [LogMessage] to be + * printed. **IMPORTANT:** The printer should ONLY ever reference fields on the [LogMessage] + * and NEVER any variables in its enclosing scope. Otherwise, the runtime will need to + * allocate a new instance of the printer for each call, thwarting our attempts at avoiding + * any sort of allocation. + * @param exception Provide any exception that need to be logged. This is saved as + * [LogMessage.exception] + * @param messageInitializer A function that will be called immediately to store relevant data + * on the log message. The value of `this` will be the [LogMessage] to be initialized. + */ + @JvmOverloads + inline fun log( + level: LogLevel, + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) { + val message = buffer.obtain(tag, level, messagePrinter, exception) + messageInitializer(message) + buffer.commit(message) + } + + /** + * Logs a compile-time string constant [message] to the log buffer. Use sparingly. + * + * This is for simpler use-cases where [message] is a compile time string constant. For + * use-cases where the log message is built during runtime, use the [log] overloaded method that + * takes in an initializer and a message printer. + * + * Buffers are limited by the number of entries, so logging more frequently will limit the time + * window that the [MessageBuffer] covers in a bug report. Richer logs, on the other hand, make + * a bug report more actionable, so using the [log] with a [MessagePrinter] to add more details + * to every log may do more to improve overall logging than adding more logs with this method. + */ + @JvmOverloads + fun log( + level: LogLevel, + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(level, { str1!! }, exception) { str1 = message } + + /** + * Logs a message to the buffer at [LogLevel.VERBOSE]. + * + * @see log + */ + @JvmOverloads + inline fun v( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.VERBOSE, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.VERBOSE]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun v( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.VERBOSE, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.DEBUG]. + * + * @see log + */ + @JvmOverloads + inline fun d( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.DEBUG, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.DEBUG]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun d( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.DEBUG, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.INFO]. + * + * @see log + */ + @JvmOverloads + inline fun i( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.INFO, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.INFO]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun i( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.INFO, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.WARNING]. + * + * @see log + */ + @JvmOverloads + inline fun w( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.WARNING, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.WARNING]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun w( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.WARNING, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.ERROR]. + * + * @see log + */ + @JvmOverloads + inline fun e( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.ERROR, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.ERROR]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun e( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.ERROR, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.WTF]. + * + * @see log + */ + @JvmOverloads + inline fun wtf( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.WTF, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.WTF]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun wtf( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.WTF, message, exception) +} diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt new file mode 100644 index 000000000000..bb91633c4d87 --- /dev/null +++ b/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.core + +/** + * [MessageBuffer] is an interface that represents a buffer of log messages, and provides methods to + * [obtain] a log message and [commit] it to the buffer. + */ +interface MessageBuffer { + /** + * Obtains the next [LogMessage] from the buffer. + * + * After calling [obtain], the caller must store any relevant data on the message and then call + * [commit]. + */ + fun obtain( + tag: String, + level: LogLevel, + messagePrinter: MessagePrinter, + exception: Throwable? = null, + ): LogMessage + + /** + * After acquiring a log message via [obtain], call this method to signal to the buffer that + * data fields have been filled. + */ + fun commit(message: LogMessage) +} diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java index 1811c02d549d..64c0f99f4ba7 100644 --- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java +++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java @@ -128,6 +128,16 @@ public interface BcSmartspaceDataPlugin extends Plugin { void setDozeAmount(float amount); /** + * Set if dozing is true or false + */ + default void setDozing(boolean dozing) {} + + /** + * Set if split shade enabled + */ + default void setSplitShadeEnabled(boolean enabled) {} + + /** * Set the current keyguard bypass enabled status. */ default void setKeyguardBypassEnabled(boolean enabled) {} diff --git a/packages/SystemUI/res-keyguard/color/shade_disabled.xml b/packages/SystemUI/res-keyguard/color/shade_disabled.xml new file mode 100644 index 000000000000..241f20385eb4 --- /dev/null +++ b/packages/SystemUI/res-keyguard/color/shade_disabled.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral1_500" android:lStar="4" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml index a7ffe9ca256f..c09607d19bdd 100644 --- a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml +++ b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml @@ -26,7 +26,7 @@ android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.QS.SecurityFooter" android:layout_gravity="center" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?attr/onShadeInactiveVariant" android:textSize="18sp"/> <ImageView android:id="@+id/new_dot" diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml index 6fe7d39f748a..1c31f1da0681 100644 --- a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml +++ b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml @@ -33,7 +33,7 @@ android:layout_marginEnd="12dp" android:contentDescription="@null" android:src="@drawable/ic_info_outline" - android:tint="?android:attr/textColorSecondary" /> + android:tint="?attr/onSurfaceVariant" /> <TextView android:id="@+id/text" @@ -43,7 +43,7 @@ android:maxLines="1" android:ellipsize="end" android:textAppearance="@style/TextAppearance.QS.SecurityFooter" - android:textColor="?android:attr/textColorSecondary"/> + android:textColor="?attr/onSurfaceVariant"/> <ImageView android:id="@+id/new_dot" @@ -62,5 +62,5 @@ android:contentDescription="@null" android:src="@*android:drawable/ic_chevron_end" android:autoMirrored="true" - android:tint="?android:attr/textColorSecondary" /> + android:tint="?attr/onSurfaceVariant" /> </com.android.systemui.animation.view.LaunchableLinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml new file mode 100644 index 000000000000..360ef2672e75 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<com.android.systemui.biometrics.UdfpsKeyguardView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/udfps_animation_view" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <!-- Add fingerprint views here. See udfps_keyguard_view_internal.xml. --> + +</com.android.systemui.biometrics.UdfpsKeyguardView> diff --git a/packages/SystemUI/res/drawable/brightness_mirror_background.xml b/packages/SystemUI/res/drawable/brightness_mirror_background.xml index 209510365fe0..b5c181bd896c 100644 --- a/packages/SystemUI/res/drawable/brightness_mirror_background.xml +++ b/packages/SystemUI/res/drawable/brightness_mirror_background.xml @@ -15,6 +15,6 @@ ~ limitations under the License --> <shape xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="?attr/underSurfaceColor" /> + <solid android:color="?attr/underSurface" /> <corners android:radius="@dimen/rounded_slider_background_rounded_corner" /> </shape> diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml index 569ee76586c2..95c7778c0e76 100644 --- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml +++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml @@ -24,7 +24,7 @@ <shape> <size android:height="@dimen/rounded_slider_track_width" /> <corners android:radius="@dimen/rounded_slider_track_corner_radius" /> - <solid android:color="?attr/offStateColor" /> + <solid android:color="?attr/shadeInactive" /> </shape> </inset> </item> diff --git a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml index 4d9188c40822..2ea90c717863 100644 --- a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml +++ b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml @@ -22,7 +22,7 @@ android:height="@dimen/rounded_slider_height"> <shape> <size android:height="@dimen/rounded_slider_height" /> - <solid android:color="?priv-android:attr/colorAccentPrimary" /> + <solid android:color="?attr/shadeActive" /> <corners android:radius="@dimen/rounded_slider_corner_radius"/> </shape> </item> @@ -34,7 +34,7 @@ android:right="@dimen/rounded_slider_icon_inset"> <com.android.systemui.util.AlphaTintDrawableWrapper android:drawable="@drawable/ic_brightness" - android:tint="?android:attr/textColorPrimaryInverse" + android:tint="?attr/onShadeActive" /> </item> </layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/fgs_dot.xml b/packages/SystemUI/res/drawable/fgs_dot.xml index 3669e1d3c374..0881d7c5c2b5 100644 --- a/packages/SystemUI/res/drawable/fgs_dot.xml +++ b/packages/SystemUI/res/drawable/fgs_dot.xml @@ -19,5 +19,5 @@ android:shape="oval" android:width="12dp" android:height="12dp"> - <solid android:color="?androidprv:attr/colorAccentTertiary" /> + <solid android:color="?attr/tertiary" /> </shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml b/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml index ea0aafd321e1..e138d094f869 100644 --- a/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml +++ b/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml @@ -15,7 +15,7 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android"> <shape> - <solid android:color="?attr/underSurfaceColor"/> + <solid android:color="?attr/underSurface"/> <corners android:radius="?android:attr/dialogCornerRadius" /> </shape> </inset> diff --git a/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml b/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml index ef950fe67ad2..f1a24aa7af9d 100644 --- a/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml +++ b/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml @@ -15,6 +15,6 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android"> <shape> - <solid android:color="?attr/underSurfaceColor"/> + <solid android:color="?attr/underSurface"/> </shape> </inset> diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml index 14cb1de9fa2d..c4e45bf2c223 100644 --- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml @@ -28,7 +28,7 @@ <item> <shape android:shape="rectangle"> <corners android:radius="?android:attr/buttonCornerRadius"/> - <solid android:color="?androidprv:attr/colorAccentPrimary"/> + <solid android:color="?androidprv:attr/materialColorPrimary"/> <padding android:left="@dimen/dialog_button_horizontal_padding" android:top="@dimen/dialog_button_vertical_padding" android:right="@dimen/dialog_button_horizontal_padding" diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml index 0544b871fa06..1590daa8b7f9 100644 --- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml @@ -26,7 +26,7 @@ <item> <shape android:shape="rectangle"> <corners android:radius="18dp"/> - <solid android:color="?androidprv:attr/colorAccentPrimary"/> + <solid android:color="?androidprv:attr/materialColorPrimaryFixed"/> </shape> </item> </ripple> diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml index a47299d6f854..b0dc6523e971 100644 --- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml @@ -29,7 +29,7 @@ <shape android:shape="rectangle"> <corners android:radius="?android:attr/buttonCornerRadius"/> <solid android:color="@android:color/transparent"/> - <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant" + <stroke android:color="?androidprv:attr/materialColorPrimary" android:width="1dp" /> <padding android:left="@dimen/dialog_button_horizontal_padding" diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml index c8c36b0081c0..4a5d4af96497 100644 --- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml +++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml @@ -28,7 +28,7 @@ </item> <item> <shape android:shape="rectangle"> - <solid android:color="?attr/offStateColor"/> + <solid android:color="?attr/shadeInactive"/> <corners android:radius="@dimen/qs_footer_action_corner_radius"/> </shape> </item> diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml index 6a365000a21c..a8c034986425 100644 --- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml +++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml @@ -28,7 +28,7 @@ </item> <item> <shape android:shape="rectangle"> - <solid android:color="?android:attr/colorAccent"/> + <solid android:color="?attr/shadeActive"/> <corners android:radius="@dimen/qs_footer_action_corner_radius"/> </shape> </item> diff --git a/packages/SystemUI/res/drawable/qs_footer_actions_background.xml b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml index c9517cd905dc..a7e8762a2593 100644 --- a/packages/SystemUI/res/drawable/qs_footer_actions_background.xml +++ b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml @@ -15,7 +15,7 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android"> <shape> - <solid android:color="?attr/underSurfaceColor"/> + <solid android:color="?attr/underSurface"/> <corners android:topLeftRadius="@dimen/qs_corner_radius" android:topRightRadius="@dimen/qs_corner_radius"/> </shape> diff --git a/packages/SystemUI/res/drawable/qs_security_footer_background.xml b/packages/SystemUI/res/drawable/qs_security_footer_background.xml index 381af503d47c..0b0055b1f020 100644 --- a/packages/SystemUI/res/drawable/qs_security_footer_background.xml +++ b/packages/SystemUI/res/drawable/qs_security_footer_background.xml @@ -29,7 +29,7 @@ <item> <shape android:shape="rectangle"> <stroke android:width="1dp" - android:color="?android:attr/colorBackground"/> + android:color="?attr/shadeInactive"/> <corners android:radius="@dimen/qs_security_footer_corner_radius"/> </shape> </item> diff --git a/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml index 88f13b451bbe..ca7df86d8296 100644 --- a/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml +++ b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml @@ -42,7 +42,7 @@ android:layout_marginBottom="16dp" android:scaleType="fitCenter" android:src="@null" - android:tint="?androidprv:attr/colorAccentPrimaryVariant" + android:tint="?androidprv:attr/materialColorPrimary" /> <TextView diff --git a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml index 7105721aff70..21e0d2c0b8d7 100644 --- a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml +++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml @@ -164,7 +164,6 @@ /> <ImageView android:id="@+id/media_output_item_end_click_icon" - android:src="@drawable/media_output_status_edit_session" android:layout_width="24dp" android:layout_height="24dp" android:focusable="false" diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index 745cfc6c1655..b8f4c0f212c3 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -53,6 +53,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center_vertical" + android:tint="?attr/shadeActive" android:visibility="gone" /> <FrameLayout @@ -70,7 +71,7 @@ android:focusable="true" android:padding="@dimen/qs_footer_icon_padding" android:src="@*android:drawable/ic_mode_edit" - android:tint="?android:attr/textColorPrimary" /> + android:tint="?attr/onSurfaceVariant" /> </FrameLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml index c124aea01afc..974cad32f937 100644 --- a/packages/SystemUI/res/layout/qs_tile_label.xml +++ b/packages/SystemUI/res/layout/qs_tile_label.xml @@ -54,6 +54,6 @@ android:focusable="false" android:importantForAccessibility="no" android:textAppearance="@style/TextAppearance.QS.TileLabel.Secondary" - android:textColor="?android:attr/textColorSecondary"/> + android:textColor="?attr/onShadeInactive"/> </com.android.systemui.qs.tileimpl.IgnorableChildLinearLayout> diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml index bbf3adfb8c67..f6ce70d4d032 100644 --- a/packages/SystemUI/res/layout/screen_record_dialog.xml +++ b/packages/SystemUI/res/layout/screen_record_dialog.xml @@ -15,6 +15,7 @@ limitations under the License. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> @@ -47,7 +48,7 @@ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceLarge" + android:textAppearance="@style/TextAppearance.Dialog.Title" android:fontFamily="@*android:string/config_headlineFontFamily" android:text="@string/screenrecord_permission_dialog_title" android:layout_marginTop="22dp" @@ -56,8 +57,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/screenrecord_permission_dialog_warning_entire_screen" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textColor="?android:textColorSecondary" + android:textAppearance="@style/TextAppearance.Dialog.Body.Message" android:gravity="center" android:layout_marginBottom="20dp"/> @@ -81,6 +81,7 @@ android:minHeight="48dp" android:layout_weight="1" android:popupBackground="@drawable/screenrecord_spinner_background" + android:textColor="?androidprv:attr/materialColorOnSurface" android:dropDownWidth="274dp" android:prompt="@string/screenrecord_audio_label"/> <Switch @@ -117,7 +118,7 @@ android:text="@string/screenrecord_taps_label" android:textAppearance="?android:attr/textAppearanceMedium" android:fontFamily="@*android:string/config_headlineFontFamily" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?androidprv:attr/materialColorOnSurface" android:importantForAccessibility="no"/> <Switch android:layout_width="wrap_content" diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml index ab522a388735..9af46c5b739c 100644 --- a/packages/SystemUI/res/layout/screen_share_dialog.xml +++ b/packages/SystemUI/res/layout/screen_share_dialog.xml @@ -36,14 +36,13 @@ android:layout_width="@dimen/screenrecord_logo_size" android:layout_height="@dimen/screenrecord_logo_size" android:src="@drawable/ic_media_projection_permission" - android:tint="?androidprv:attr/colorAccentPrimaryVariant" + android:tint="?androidprv:attr/materialColorPrimary" android:importantForAccessibility="no"/> <TextView android:id="@+id/screen_share_dialog_title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceLarge" - android:fontFamily="@*android:string/config_headlineFontFamily" + android:textAppearance="@style/TextAppearance.Dialog.Title" android:layout_marginTop="@dimen/screenrecord_title_margin_top" android:gravity="center"/> <Spinner @@ -64,8 +63,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/screenrecord_permission_dialog_warning_entire_screen" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textColor="?android:textColorSecondary" + style="@style/TextAppearance.Dialog.Body.Message" android:gravity="start" android:lineHeight="@dimen/screenrecord_warning_line_height"/> diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml index 00af7f4e10e3..530d752732c1 100644 --- a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml +++ b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml @@ -16,8 +16,7 @@ --> <com.android.systemui.biometrics.UdfpsKeyguardViewLegacy xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/udfps_animation_view" + android:id="@+id/udfps_animation_view_legacy" android:layout_width="match_parent" android:layout_height="match_parent"> diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 3a1d1a8cbf9d..d693631080af 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -118,8 +118,25 @@ <attr name="wallpaperTextColorSecondary" format="reference|color" /> <attr name="wallpaperTextColorAccent" format="reference|color" /> <attr name="backgroundProtectedStyle" format="reference" /> - <attr name="offStateColor" format="reference|color" /> - <attr name="underSurfaceColor" format="reference|color" /> + + <!-- color attribute tokens for QS --> + <attr name="isQsTheme" format="boolean" /> + <attr name="underSurface" format="reference|color"/> + <attr name="shadeActive" format="reference|color" /> + <attr name="onShadeActive" format="reference|color" /> + <attr name="onShadeActiveVariant" format="reference|color" /> + <attr name="shadeInactive" format="reference|color" /> + <attr name="onShadeInactive" format="reference|color" /> + <attr name="onShadeInactiveVariant" format="reference|color" /> + <attr name="shadeDisabled" format="reference|color" /> + <attr name="surfaceBright" format="reference|color" /> + <attr name="scHigh" format="reference|color" /> + <attr name="tertiary" format="reference|color" /> + <attr name="onSurface" format="reference|color" /> + <attr name="onSurfaceVariant" format="reference|color" /> + <attr name="outline" format="reference|color" /> + <attr name="primary" format="reference|color" /> + <declare-styleable name="SmartReplyView"> <attr name="spacing" format="dimension" /> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index cb5342a0d66b..fd74c7eae361 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -83,7 +83,7 @@ <style name="TextAppearance.QS"> <item name="android:textStyle">normal</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">?attr/onShadeInactive</item> <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> </style> @@ -93,7 +93,7 @@ <style name="TextAppearance.QS.DetailItemSecondary"> <item name="android:textSize">@dimen/qs_tile_text_size</item> - <item name="android:textColor">?android:attr/colorAccent</item> + <item name="android:textColor">?attr/shadeActive</item> </style> <style name="TextAppearance.QS.Introduction"> @@ -117,11 +117,11 @@ <style name="TextAppearance.QS.DataUsage.Usage"> <item name="android:textSize">@dimen/qs_data_usage_usage_text_size</item> - <item name="android:textColor">?android:attr/colorAccent</item> + <item name="android:textColor">?attr/shadeActive</item> </style> <style name="TextAppearance.QS.DataUsage.Secondary"> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?attr/onShadeInactiveVariant</item> </style> <style name="TextAppearance.QS.TileLabel"> @@ -137,31 +137,31 @@ <style name="TextAppearance.QS.UserSwitcher"> <item name="android:textSize">@dimen/qs_tile_text_size</item> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item> </style> <!-- This is hard coded to be sans-serif-condensed to match the icons --> <style name="TextAppearance.QS.Status"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">?attr/onSurface</item> <item name="android:textSize">14sp</item> <item name="android:letterSpacing">0.01</item> </style> <style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status"> <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?attr/onSurface</item> </style> <style name="TextAppearance.QS.Status.Carriers" /> <style name="TextAppearance.QS.Status.Carriers.NoCarrierText"> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?attr/onSurfaceVariant</item> </style> <style name="TextAppearance.QS.Status.Build"> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?attr/onSurfaceVariant</item> </style> <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/> @@ -278,10 +278,10 @@ <style name="DeviceManagementDialogTitle"> <item name="android:gravity">center</item> - <item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item> + <item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item> </style> - <style name="TextAppearance.DeviceManagementDialog.Content" parent="@*android:style/TextAppearance.DeviceDefault.Subhead"/> + <style name="TextAppearance.DeviceManagementDialog.Content" parent="@style/TextAppearance.Dialog.Body.Message"/> <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI"> <item name="android:layout_width">match_parent</item> @@ -371,14 +371,30 @@ </style> <style name="Theme.SystemUI.QuickSettings" parent="@*android:style/Theme.DeviceDefault"> + <item name="isQsTheme">true</item> <item name="lightIconTheme">@style/QSIconTheme</item> <item name="darkIconTheme">@style/QSIconTheme</item> <item name="android:colorError">@*android:color/error_color_material_dark</item> <item name="android:windowIsFloating">true</item> <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item> - <item name="offStateColor">@color/material_dynamic_neutral20</item> - <item name="underSurfaceColor">@color/material_dynamic_neutral0</item> - <item name="android:colorBackground">@color/material_dynamic_neutral10</item> + + <item name="surfaceBright">?androidprv:attr/materialColorSurfaceBright</item> + <item name="android:colorBackground">?attr/surfaceBright</item> + <item name="scHigh">?androidprv:attr/materialColorSurfaceContainerHigh</item> + <item name="primary">?androidprv:attr/materialColorPrimary</item> + <item name="tertiary">?androidprv:attr/materialColorTertiary</item> + <item name="onSurface">?androidprv:attr/materialColorOnSurface</item> + <item name="onSurfaceVariant">?androidprv:attr/materialColorOnSurfaceVariant</item> + <item name="outline">?androidprv:attr/materialColorOutline</item> + + <item name="shadeActive">@color/material_dynamic_primary90</item> + <item name="onShadeActive">@color/material_dynamic_primary10</item> + <item name="onShadeActiveVariant">@color/material_dynamic_primary30</item> + <item name="shadeInactive">@color/material_dynamic_neutral20</item> + <item name="onShadeInactive">@color/material_dynamic_neutral90</item> + <item name="onShadeInactiveVariant">@color/material_dynamic_neutral_variant80</item> + <item name="shadeDisabled">@color/shade_disabled</item> + <item name="underSurface">@color/material_dynamic_neutral0</item> <item name="android:itemTextAppearance">@style/Control.MenuItem</item> </style> @@ -390,8 +406,15 @@ <item name="android:windowBackground">@android:color/transparent</item> </style> - <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog"> + <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@style/Theme.SystemUI.Dialog.QuickSettings"> + </style> + + <!-- Parent style overrides style in the dot inheritance --> + <style name="Theme.SystemUI.Dialog.QuickSettings" parent="@style/Theme.SystemUI.QuickSettings"> <item name="android:dialogCornerRadius">@dimen/notification_corner_radius</item> + <item name="android:buttonBarPositiveButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item> + <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item> + <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item> </style> <!-- Overridden by values-television/styles.xml with tv-specific settings --> @@ -406,7 +429,7 @@ <item name="android:buttonBarPositiveButtonStyle">@style/Widget.Dialog.Button</item> <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.BorderButton</item> <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item> - <item name="android:colorBackground">?androidprv:attr/colorSurface</item> + <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceBright</item> <item name="android:alertDialogStyle">@style/ScrollableAlertDialogStyle</item> <item name="android:buttonBarStyle">@style/ButtonBarStyle</item> <item name="android:buttonBarButtonStyle">@style/Widget.Dialog.Button.Large</item> @@ -605,11 +628,11 @@ <item name="android:letterSpacing">0.01</item> <item name="android:lineHeight">20sp</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?attr/onSurfaceVariant</item> </style> <style name="QSCustomizeToolbar" parent="@*android:style/Widget.DeviceDefault.Toolbar"> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">?attr/onSurface</item> <item name="android:elevation">10dp</item> </style> @@ -1055,7 +1078,7 @@ </style> <style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large"> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> <item name="android:textSize">@dimen/dialog_title_text_size</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:lineHeight">32sp</item> @@ -1064,7 +1087,7 @@ </style> <style name="TextAppearance.Dialog.Body" parent="@android:style/TextAppearance.DeviceDefault.Medium"> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item> <item name="android:textSize">14sp</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:lineHeight">20sp</item> @@ -1092,7 +1115,7 @@ <style name="Widget.Dialog.Button"> <item name="android:buttonCornerRadius">28dp</item> <item name="android:background">@drawable/qs_dialog_btn_filled</item> - <item name="android:textColor">?androidprv:attr/textColorOnAccent</item> + <item name="android:textColor">?androidprv:attr/materialColorOnPrimary</item> <item name="android:textSize">14sp</item> <item name="android:lineHeight">20sp</item> <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> @@ -1102,12 +1125,18 @@ <style name="Widget.Dialog.Button.BorderButton"> <item name="android:background">@drawable/qs_dialog_btn_outline</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> </style> <style name="Widget.Dialog.Button.Large"> <item name="android:background">@drawable/qs_dialog_btn_filled_large</item> <item name="android:minHeight">56dp</item> + <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryFixed</item> + </style> + + <style name="Widget.Dialog.Button.QuickSettings"> + <item name="android:textColor">?attr/primary</item> + <item name="android:background">?android:attr/selectableItemBackground</item> </style> <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault"> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 2e6c485336f3..751a3f8458bd 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -104,8 +104,7 @@ public class Utilities { * @return updated set of flags from InputMethodService based off {@param oldHints} * Leaves original hints unmodified */ - public static int calculateBackDispositionHints(int oldHints, - @InputMethodService.BackDispositionMode int backDisposition, + public static int calculateBackDispositionHints(int oldHints, int backDisposition, boolean imeShown, boolean showImeSwitcher) { int hints = oldHints; switch (backDisposition) { 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 3c447a8eb6d8..ca064efd4f76 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 @@ -324,7 +324,7 @@ public class QuickStepContract { * Returns whether the specified sysui state is such that the back gesture should be * disabled. */ - public static boolean isBackGestureDisabled(int sysuiStateFlags) { + public static boolean isBackGestureDisabled(int sysuiStateFlags, boolean forTrackpad) { // Always allow when the bouncer/global actions/voice session is showing (even on top of // the keyguard) if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0 @@ -335,16 +335,23 @@ public class QuickStepContract { if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) { sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN; } + + return (sysuiStateFlags & getBackGestureDisabledMask(forTrackpad)) != 0; + } + + private static int getBackGestureDisabledMask(boolean forTrackpad) { // Disable when in immersive, or the notifications are interactive - int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; + int disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; + if (!forTrackpad) { + disableFlags |= SYSUI_STATE_NAV_BAR_HIDDEN; + } // EdgeBackGestureHandler ignores Back gesture when SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED. // To allow Shade to respond to Back, we're bypassing this check (behind a flag). if (!ALLOW_BACK_GESTURE_IN_SHADE) { disableFlags |= SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; } - - return (sysuiStateFlags & disableFlags) != 0; + return disableFlags; } /** diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 84a2c25999a0..91937af6f540 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -42,7 +42,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.KeyguardLargeClockLog import com.android.systemui.log.dagger.KeyguardSmallClockLog import com.android.systemui.plugins.ClockController diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 360f755623f7..4793b4f37eb3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -29,7 +29,7 @@ import com.android.app.animation.Interpolators; import com.android.keyguard.dagger.KeyguardStatusViewScope; import com.android.systemui.R; import com.android.systemui.log.LogBuffer; -import com.android.systemui.log.LogLevel; +import com.android.systemui.log.core.LogLevel; import com.android.systemui.plugins.ClockController; import com.android.systemui.shared.clocks.DefaultClockController; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 62a2f90a0378..6c983762f9a1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -40,7 +40,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.log.LogBuffer; -import com.android.systemui.log.LogLevel; +import com.android.systemui.log.core.LogLevel; import com.android.systemui.log.dagger.KeyguardClockLog; import com.android.systemui.plugins.ClockController; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -352,6 +352,13 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } /** + * Set if the split shade is enabled + */ + public void setSplitShadeEnabled(boolean splitShadeEnabled) { + mSmartspaceController.setSplitShadeEnabled(splitShadeEnabled); + } + + /** * Set which clock should be displayed on the keyguard. The other one will be automatically * hidden. */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 6f596843bf9f..3e16d559742d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -43,15 +43,6 @@ public class KeyguardPinViewController private long mPinLength; private boolean mDisabledAutoConfirmation; - /** - * Responsible for identifying if PIN hinting is to be enabled or not - */ - private boolean mIsPinHinting; - - /** - * Responsible for identifying if auto confirm is enabled or not in Settings - */ - private boolean mIsAutoPinConfirmEnabledInSettings; protected KeyguardPinViewController(KeyguardPINView view, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -72,9 +63,6 @@ public class KeyguardPinViewController mFeatureFlags = featureFlags; mBackspaceKey = view.findViewById(R.id.delete_button); mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser()); - mIsPinHinting = mPinLength == DEFAULT_PIN_LENGTH; - mIsAutoPinConfirmEnabledInSettings = mLockPatternUtils.isAutoPinConfirmEnabled( - KeyguardUpdateMonitor.getCurrentUser()); } @Override @@ -94,7 +82,7 @@ public class KeyguardPinViewController protected void onUserInput() { super.onUserInput(); - if (mIsAutoPinConfirmEnabledInSettings) { + if (isAutoPinConfirmEnabledInSettings()) { updateAutoConfirmationState(); if (mPasswordEntry.getText().length() == mPinLength && mOkButton.getVisibility() == View.INVISIBLE) { @@ -142,7 +130,7 @@ public class KeyguardPinViewController * Updates the visibility of the OK button for auto confirm feature */ private void updateOKButtonVisibility() { - if (mIsPinHinting && !mDisabledAutoConfirmation) { + if (isAutoPinConfirmEnabledInSettings() && !mDisabledAutoConfirmation) { mOkButton.setVisibility(View.INVISIBLE); } else { mOkButton.setVisibility(View.VISIBLE); @@ -154,9 +142,10 @@ public class KeyguardPinViewController * Visibility changes are only for auto confirmation configuration. */ private void updateBackSpaceVisibility() { + boolean isAutoConfirmation = isAutoPinConfirmEnabledInSettings(); mBackspaceKey.setTransparentMode(/* isTransparentMode= */ - mIsAutoPinConfirmEnabledInSettings && !mDisabledAutoConfirmation); - if (mIsAutoPinConfirmEnabledInSettings) { + isAutoConfirmation && !mDisabledAutoConfirmation); + if (isAutoConfirmation) { if (mPasswordEntry.getText().length() > 0 || mDisabledAutoConfirmation) { mBackspaceKey.setVisibility(View.VISIBLE); @@ -166,8 +155,24 @@ public class KeyguardPinViewController } } /** Updates whether to use pin hinting or not. */ - private void updatePinHinting() { - mPasswordEntry.setIsPinHinting(mIsAutoPinConfirmEnabledInSettings && mIsPinHinting + void updatePinHinting() { + mPasswordEntry.setIsPinHinting(isAutoPinConfirmEnabledInSettings() && isPinHinting() && !mDisabledAutoConfirmation); } + + /** + * Responsible for identifying if PIN hinting is to be enabled or not + */ + private boolean isPinHinting() { + return mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser()) + == DEFAULT_PIN_LENGTH; + } + + /** + * Responsible for identifying if auto confirm is enabled or not in Settings + */ + private boolean isAutoPinConfirmEnabledInSettings() { + //Checks if user has enabled the auto confirm in Settings + return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser()); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 880f242c5938..458ca2b17c6d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -544,7 +544,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) { if (mCancelAction != null) { mCancelAction.run(); - mCancelAction = null; } mDismissAction = action; mCancelAction = cancelAction; @@ -782,9 +781,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard case SimPuk: // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); - if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled( - KeyguardUpdateMonitor.getCurrentUser())) { - finish = true; + boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled( + KeyguardUpdateMonitor.getCurrentUser()); + if (securityMode == SecurityMode.None || isLockscreenDisabled) { + finish = isLockscreenDisabled; eventSubtype = BOUNCER_DISMISS_SIM; uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM; } else { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 00500d617766..6854c97c3415 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -323,6 +323,13 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } /** + * Set if the split shade is enabled + */ + public void setSplitShadeEnabled(boolean enabled) { + mKeyguardClockSwitchController.setSplitShadeEnabled(enabled); + } + + /** * Updates the alignment of the KeyguardStatusView and animates the transition if requested. */ public void updateAlignment( @@ -350,6 +357,9 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); + /* This transition blocks any layout changes while running. For that reason + * special logic with setting visibility was added to {@link BcSmartspaceView#setDozing} + * for split shade to avoid jump of the media object. */ ChangeBounds transition = new ChangeBounds(); if (splitShadeEnabled) { // Excluding media from the transition on split-shade, as it doesn't transition diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index c4ea45dbbec7..518baec6b089 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -303,6 +303,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final ComponentName FALLBACK_HOME_COMPONENT = new ComponentName( "com.android.settings", "com.android.settings.FallbackHome"); + private static final List<Integer> ABSENT_SIM_STATE_LIST = Arrays.asList( + TelephonyManager.SIM_STATE_ABSENT, + TelephonyManager.SIM_STATE_UNKNOWN, + TelephonyManager.SIM_STATE_NOT_READY); + private final Context mContext; private final UserTracker mUserTracker; private final KeyguardUpdateMonitorLogger mLogger; @@ -3742,8 +3747,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLogger.logSimState(subId, slotId, state); boolean becameAbsent = false; - if (!SubscriptionManager.isValidSubscriptionId(subId) - && state != TelephonyManager.SIM_STATE_UNKNOWN) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { mLogger.w("invalid subId in handleSimStateChange()"); /* Only handle No SIM(ABSENT) and Card Error(CARD_IO_ERROR) due to * handleServiceStateChange() handle other case */ @@ -3761,11 +3765,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } else if (state == TelephonyManager.SIM_STATE_CARD_IO_ERROR) { updateTelephonyCapable(true); - } else { - return; } } + becameAbsent |= ABSENT_SIM_STATE_LIST.contains(state); + SimData data = mSimDatas.get(subId); final boolean changed; if (data == null) { @@ -3778,7 +3782,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab data.subId = subId; data.slotId = slotId; } - if ((changed || becameAbsent) || state == TelephonyManager.SIM_STATE_UNKNOWN) { + if ((changed || becameAbsent)) { for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index 61af7228fe0d..71f78c32317c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -23,7 +23,7 @@ import android.view.View; import com.android.app.animation.Interpolators; import com.android.systemui.log.LogBuffer; -import com.android.systemui.log.LogLevel; +import com.android.systemui.log.core.LogLevel; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index d1fffaa926ea..76b073ef73af 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -32,7 +32,6 @@ import android.widget.ImageView; import androidx.annotation.IntDef; import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.settingslib.Utils; @@ -126,7 +125,6 @@ public class LockIconView extends FrameLayout implements Dumpable { /** * Set the location of the lock icon. */ - @VisibleForTesting public void setCenterLocation(@NonNull Point center, float radius, int drawablePadding) { mLockIconCenter = center; mRadius = radius; diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 239a0cc01c45..e255f5c2ded6 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -60,6 +60,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.TransitionStep; @@ -387,15 +388,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private void updateLockIconLocation() { final float scaleFactor = mAuthController.getScaleFactor(); final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor); - if (mUdfpsSupported) { - mView.setCenterLocation(mAuthController.getUdfpsLocation(), - mAuthController.getUdfpsRadius(), scaledPadding); - } else { - mView.setCenterLocation( - new Point((int) mWidthPixels / 2, - (int) (mHeightPixels - - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))), + if (!mFeatureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) { + if (mUdfpsSupported) { + mView.setCenterLocation(mAuthController.getUdfpsLocation(), + mAuthController.getUdfpsRadius(), scaledPadding); + } else { + mView.setCenterLocation( + new Point((int) mWidthPixels / 2, + (int) (mHeightPixels + - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))), sLockIconRadiusPx * scaleFactor, scaledPadding); + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt index e7295ef9052c..54738c62646a 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt @@ -18,7 +18,7 @@ package com.android.keyguard.logging import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.BiometricLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt index c00b2c612fa2..cfad614a460d 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt @@ -19,9 +19,9 @@ package com.android.keyguard.logging import android.hardware.biometrics.BiometricSourceType import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.log.dagger.BiometricLog import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE diff --git a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt index 19787154bec4..d02b72f37795 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt @@ -19,7 +19,7 @@ package com.android.keyguard.logging import androidx.annotation.IntDef import com.android.keyguard.CarrierTextManager.CarrierTextCallbackInfo import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.CarrierTextManagerLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt index 8b925b1bfb54..bddf3b07dbb5 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt @@ -19,7 +19,7 @@ package com.android.keyguard.logging import com.android.systemui.biometrics.AuthRippleController import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.KeyguardLog import com.android.systemui.statusbar.KeyguardIndicationController import com.google.errorprone.annotations.CompileTimeConstant diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index be6d753b72bd..eec5b3ed8ef4 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -33,12 +33,12 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.TrustGrantFlags import com.android.settingslib.fuelgauge.BatteryStatus import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -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.LogLevel.VERBOSE -import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.VERBOSE +import com.android.systemui.log.core.LogLevel.WARNING import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt index cb764a8f94aa..57b5db15de67 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt @@ -21,8 +21,8 @@ import com.android.systemui.keyguard.shared.model.ActiveUnlockModel import com.android.systemui.keyguard.shared.model.TrustManagedModel import com.android.systemui.keyguard.shared.model.TrustModel import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt index 670c1fa45e5c..c46558532372 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt @@ -56,9 +56,14 @@ class ScreenDecorHwcLayer( ) : DisplayCutoutBaseView(context) { val colorMode: Int private val useInvertedAlphaColor: Boolean - private val color: Int + private var color: Int = Color.BLACK + set(value) { + field = value + paint.color = value + } + private val bgColor: Int - private val cornerFilter: ColorFilter + private var cornerFilter: ColorFilter private val cornerBgFilter: ColorFilter private val clearPaint: Paint @JvmField val transparentRect: Rect = Rect() @@ -109,10 +114,16 @@ class ScreenDecorHwcLayer( override fun onAttachedToWindow() { super.onAttachedToWindow() parent.requestTransparentRegion(this) + updateColors() + } + + private fun updateColors() { if (!debug) { viewRootImpl.setDisplayDecoration(true) } + cornerFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) + if (useInvertedAlphaColor) { paint.set(clearPaint) } else { @@ -121,6 +132,21 @@ class ScreenDecorHwcLayer( } } + fun setDebugColor(color: Int) { + if (!debug) { + return + } + + if (this.color == color) { + return + } + + this.color = color + + updateColors() + invalidate() + } + override fun onUpdate() { parent.requestTransparentRegion(this) } @@ -367,7 +393,7 @@ class ScreenDecorHwcLayer( /** * Update the rounded corner drawables. */ - fun updateRoundedCornerDrawable(top: Drawable, bottom: Drawable) { + fun updateRoundedCornerDrawable(top: Drawable?, bottom: Drawable?) { roundedCornerDrawableTop = top roundedCornerDrawableBottom = bottom updateRoundedCornerDrawableBounds() diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 67d4a2e25051..de7a66900355 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -69,9 +69,9 @@ import com.android.internal.util.Preconditions; import com.android.settingslib.Utils; import com.android.systemui.biometrics.AuthController; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.decor.CutoutDecorProviderFactory; import com.android.systemui.decor.DebugRoundedCornerDelegate; +import com.android.systemui.decor.DebugRoundedCornerModel; import com.android.systemui.decor.DecorProvider; import com.android.systemui.decor.DecorProviderFactory; import com.android.systemui.decor.DecorProviderKt; @@ -80,10 +80,12 @@ import com.android.systemui.decor.OverlayWindow; import com.android.systemui.decor.PrivacyDotDecorProviderFactory; import com.android.systemui.decor.RoundedCornerDecorProviderFactory; import com.android.systemui.decor.RoundedCornerResDelegateImpl; +import com.android.systemui.decor.ScreenDecorCommand; import com.android.systemui.log.ScreenDecorationsLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.ThreadFactory; @@ -95,7 +97,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.concurrent.Executor; import javax.inject.Inject; @@ -130,7 +131,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { @VisibleForTesting protected boolean mIsRegistered; private final Context mContext; - private final Executor mMainExecutor; + private final CommandRegistry mCommandRegistry; private final SecureSettings mSecureSettings; @VisibleForTesting DisplayTracker.Callback mDisplayListener; @@ -313,8 +314,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { @Inject public ScreenDecorations(Context context, - @Main Executor mainExecutor, SecureSettings secureSettings, + CommandRegistry commandRegistry, UserTracker userTracker, DisplayTracker displayTracker, PrivacyDotViewController dotViewController, @@ -324,8 +325,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { ScreenDecorationsLogger logger, AuthController authController) { mContext = context; - mMainExecutor = mainExecutor; mSecureSettings = secureSettings; + mCommandRegistry = commandRegistry; mUserTracker = userTracker; mDisplayTracker = displayTracker; mDotViewController = dotViewController; @@ -350,6 +351,45 @@ public class ScreenDecorations implements CoreStartable, Dumpable { } }; + private final ScreenDecorCommand.Callback mScreenDecorCommandCallback = (cmd, pw) -> { + // If we are exiting debug mode, we can set it (false) and bail, otherwise we will + // ensure that debug mode is set + if (cmd.getDebug() != null && !cmd.getDebug()) { + setDebug(false); + return; + } else { + // setDebug is idempotent + setDebug(true); + } + + if (cmd.getColor() != null) { + mDebugColor = cmd.getColor(); + mExecutor.execute(() -> { + if (mScreenDecorHwcLayer != null) { + mScreenDecorHwcLayer.setDebugColor(cmd.getColor()); + } + updateColorInversionDefault(); + }); + } + + DebugRoundedCornerModel roundedTop = null; + DebugRoundedCornerModel roundedBottom = null; + if (cmd.getRoundedTop() != null) { + roundedTop = cmd.getRoundedTop().toRoundedCornerDebugModel(); + } + if (cmd.getRoundedBottom() != null) { + roundedBottom = cmd.getRoundedBottom().toRoundedCornerDebugModel(); + } + if (roundedTop != null || roundedBottom != null) { + mDebugRoundedCornerDelegate.applyNewDebugCorners(roundedTop, roundedBottom); + mExecutor.execute(() -> { + removeAllOverlays(); + removeHwcOverlay(); + setupDecorations(); + }); + } + }; + @Override public void start() { if (DEBUG_DISABLE_SCREEN_DECORATIONS) { @@ -361,6 +401,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { mExecutor.execute(this::startOnScreenDecorationsThread); mDotViewController.setUiExecutor(mExecutor); mAuthController.addCallback(mAuthControllerCallback); + mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME, + () -> new ScreenDecorCommand(mScreenDecorCommandCallback)); } /** @@ -1228,7 +1270,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { bottomDrawable = mDebugRoundedCornerDelegate.getBottomRoundedDrawable(); } - if (topDrawable == null || bottomDrawable == null) { + if (topDrawable == null && bottomDrawable == null) { return; } mScreenDecorHwcLayer.updateRoundedCornerDrawable(topDrawable, bottomDrawable); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 6f0f6331ef50..946ddba6a341 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -37,7 +37,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.WakefulnessLifecycle -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LiftReveal diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index dc9ba8719717..ebff0b05a52d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -85,6 +85,8 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; +import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter; +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -153,6 +155,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull private final SystemUIDialogManager mDialogManager; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; + @NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels; @NonNull private final VibratorHelper mVibrator; @NonNull private final FeatureFlags mFeatureFlags; @NonNull private final FalsingManager mFalsingManager; @@ -272,7 +275,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { (view, event, fromUdfpsView) -> onTouch(requestId, event, fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags, mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils, - mUdfpsKeyguardAccessibilityDelegate))); + mUdfpsKeyguardAccessibilityDelegate, + mUdfpsKeyguardViewModels))); } @Override @@ -591,6 +595,13 @@ public class UdfpsController implements DozeReceiver, Dumpable { // Pilfer if valid overlap, don't allow following events to reach keyguard shouldPilfer = true; + + // Touch is a valid UDFPS touch. Inform the falsing manager so that the touch + // isn't counted against the falsing algorithm as an accidental touch. + // We do this on the DOWN event instead of CANCEL/UP because the CANCEL/UP events + // get sent too late to this receiver (after the actual cancel/up motions occur), + // and therefore wouldn't end up being used as part of the falsing algo. + mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); break; case UP: @@ -610,7 +621,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { data.getTime(), data.getGestureStart(), mStatusBarStateController.isDozing()); - mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); break; case UNCHANGED: @@ -784,7 +794,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { private boolean shouldTryToDismissKeyguard() { return mOverlay != null && mOverlay.getAnimationViewController() - instanceof UdfpsKeyguardViewControllerLegacy + instanceof UdfpsKeyguardViewControllerAdapter && mKeyguardStateController.canDismissLockScreen() && !mAttemptedToDismissKeyguard; } @@ -829,7 +839,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull InputManager inputManager, @NonNull UdfpsUtils udfpsUtils, @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, - @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate) { + @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate, + @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) { mContext = context; mExecution = execution; mVibrator = vibrator; @@ -895,6 +906,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { return Unit.INSTANCE; }); mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; + mUdfpsKeyguardViewModels = udfpsKeyguardViewModelsProvider; final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController(); mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index e5421471931f..d6ef94d18e71 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -46,15 +46,19 @@ import android.view.accessibility.AccessibilityManager.TouchExplorationStateChan import androidx.annotation.LayoutRes import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor -import com.android.settingslib.udfps.UdfpsUtils import com.android.settingslib.udfps.UdfpsOverlayParams +import com.android.settingslib.udfps.UdfpsUtils import com.android.systemui.R import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS +import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -64,6 +68,8 @@ import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.settings.SecureSettings +import kotlinx.coroutines.ExperimentalCoroutinesApi +import javax.inject.Provider private const val TAG = "UdfpsControllerOverlay" @@ -75,6 +81,7 @@ const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui" * request. This state can persist across configuration changes via the [show] and [hide] * methods. */ +@ExperimentalCoroutinesApi @UiThread class UdfpsControllerOverlay @JvmOverloads constructor( private val context: Context, @@ -105,6 +112,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, private val udfpsUtils: UdfpsUtils, private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, + private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>, ) { /** The view, when [isShowing], or null. */ var overlayView: UdfpsView? = null @@ -243,27 +251,40 @@ class UdfpsControllerOverlay @JvmOverloads constructor( ) } REASON_AUTH_KEYGUARD -> { - UdfpsKeyguardViewControllerLegacy( - view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) { - updateSensorLocation(sensorBounds) - }, - statusBarStateController, - shadeExpansionStateManager, - statusBarKeyguardViewManager, - keyguardUpdateMonitor, - dumpManager, - transitionController, - configurationController, - keyguardStateController, - unlockedScreenOffAnimationController, - dialogManager, - controller, - activityLaunchAnimator, - featureFlags, - primaryBouncerInteractor, - alternateBouncerInteractor, - udfpsKeyguardAccessibilityDelegate, - ) + if (featureFlags.isEnabled(REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + udfpsKeyguardViewModels.get().setSensorBounds(sensorBounds) + UdfpsKeyguardViewController( + view.addUdfpsView(R.layout.udfps_keyguard_view), + statusBarStateController, + shadeExpansionStateManager, + dialogManager, + dumpManager, + alternateBouncerInteractor, + udfpsKeyguardViewModels.get(), + ) + } else { + UdfpsKeyguardViewControllerLegacy( + view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) { + updateSensorLocation(sensorBounds) + }, + statusBarStateController, + shadeExpansionStateManager, + statusBarKeyguardViewManager, + keyguardUpdateMonitor, + dumpManager, + transitionController, + configurationController, + keyguardStateController, + unlockedScreenOffAnimationController, + dialogManager, + controller, + activityLaunchAnimator, + featureFlags, + primaryBouncerInteractor, + alternateBouncerInteractor, + udfpsKeyguardAccessibilityDelegate, + ) + } } REASON_AUTH_BP -> { // note: empty controller, currently shows no visual affordance @@ -415,7 +436,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( } private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean { - if (animation !is UdfpsKeyguardViewControllerLegacy) { + if (animation !is UdfpsKeyguardViewControllerAdapter) { // always rotate view if we're not on the keyguard return true } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt new file mode 100644 index 000000000000..8cc15dadffd2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.content.Context +import android.util.AttributeSet +import kotlinx.coroutines.ExperimentalCoroutinesApi + +/** View corresponding with udfps_keyguard_view.xml */ +@ExperimentalCoroutinesApi +class UdfpsKeyguardView( + context: Context, + attrs: AttributeSet?, +) : + UdfpsAnimationView( + context, + attrs, + ) { + private val fingerprintDrawablePlaceHolder = UdfpsFpDrawable(context) + private var visible = false + + override fun calculateAlpha(): Int { + return if (mPauseAuth) { + 0 + } else 255 // ViewModels handle animating alpha values + } + + override fun getDrawable(): UdfpsDrawable { + return fingerprintDrawablePlaceHolder + } + + fun useExpandedOverlay(useExpandedOverlay: Boolean) { + mUseExpandedOverlay = useExpandedOverlay + } + + fun isVisible(): Boolean { + return visible + } + + fun setVisible(isVisible: Boolean) { + visible = isVisible + isPauseAuth = !visible + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index 9bafeeca24a8..15bd73193687 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -34,6 +34,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionListener @@ -80,7 +81,8 @@ constructor( shadeExpansionStateManager, systemUIDialogManager, dumpManager, - ) { + ), + UdfpsKeyguardViewControllerAdapter { private val useExpandedOverlay: Boolean = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) private var showingUdfpsBouncer = false diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt index 39199d194cc9..2102a1f72be2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt @@ -17,10 +17,10 @@ package com.android.systemui.biometrics import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.VERBOSE -import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.VERBOSE +import com.android.systemui.log.core.LogLevel.WARNING import com.android.systemui.log.dagger.UdfpsLog import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt new file mode 100644 index 000000000000..2a9f3eafc776 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.ui.controller + +import com.android.systemui.biometrics.UdfpsAnimationViewController +import com.android.systemui.biometrics.UdfpsKeyguardView +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.statusbar.phone.SystemUIDialogManager +import kotlinx.coroutines.ExperimentalCoroutinesApi + +/** Class that coordinates non-HBM animations during keyguard authentication. */ +@ExperimentalCoroutinesApi +open class UdfpsKeyguardViewController( + val view: UdfpsKeyguardView, + statusBarStateController: StatusBarStateController, + shadeExpansionStateManager: ShadeExpansionStateManager, + systemUIDialogManager: SystemUIDialogManager, + dumpManager: DumpManager, + private val alternateBouncerInteractor: AlternateBouncerInteractor, + udfpsKeyguardViewModels: UdfpsKeyguardViewModels, +) : + UdfpsAnimationViewController<UdfpsKeyguardView>( + view, + statusBarStateController, + shadeExpansionStateManager, + systemUIDialogManager, + dumpManager, + ), + UdfpsKeyguardViewControllerAdapter { + override val tag: String + get() = TAG + + init { + udfpsKeyguardViewModels.bindViews(view) + } + + public override fun onViewAttached() { + super.onViewAttached() + alternateBouncerInteractor.setAlternateBouncerUIAvailable(true) + } + + public override fun onViewDetached() { + super.onViewDetached() + alternateBouncerInteractor.setAlternateBouncerUIAvailable(false) + } + + override fun shouldPauseAuth(): Boolean { + return !view.isVisible() + } + + override fun listenForTouchesOutsideView(): Boolean { + return true + } + + companion object { + private const val TAG = "UdfpsKeyguardViewController" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt index 96af42bfda22..2a457ebd62f7 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.bluetooth import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.BluetoothLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt index 5b3a982ab5e2..068f32986cf1 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt @@ -21,10 +21,10 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogMessage +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogMessage import com.android.systemui.log.dagger.BroadcastDispatcherLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt index d629e3ea365e..8e41974b9acc 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -154,7 +154,7 @@ open class ControlsEditingActivity @Inject constructor( private fun bindViews() { setContentView(R.layout.controls_management) - getLifecycle().addObserver( + lifecycle.addObserver( ControlsAnimations.observerForAnimations( requireViewById<ViewGroup>(R.id.controls_management_root), window, diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index d3ffc9585335..d3aa449b9cad 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -268,7 +268,7 @@ open class ControlsFavoritingActivity @Inject constructor( private fun bindViews() { setContentView(R.layout.controls_management) - getLifecycle().addObserver( + lifecycle.addObserver( ControlsAnimations.observerForAnimations( requireViewById<ViewGroup>(R.id.controls_management_root), window, diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index fb19ac99d19e..d5b8693af42d 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -91,7 +91,7 @@ open class ControlsProviderSelectorActivity @Inject constructor( setContentView(R.layout.controls_management) - getLifecycle().addObserver( + lifecycle.addObserver( ControlsAnimations.observerForAnimations( requireViewById<ViewGroup>(R.id.controls_management_root), window, diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt index 4a22e4eecc2b..557dcf4accc7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt @@ -72,7 +72,7 @@ open class ControlsActivity @Inject constructor( setContentView(R.layout.controls_fullscreen) - getLifecycle().addObserver( + lifecycle.addObserver( ControlsAnimations.observerForAnimations( requireViewById(R.id.control_detail_root), window, diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 76002d3f9693..40db63d609ba 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -49,6 +49,7 @@ import com.android.systemui.settings.dagger.MultiUserUtilsModule import com.android.systemui.shortcut.ShortcutKeyDispatcher import com.android.systemui.statusbar.notification.InstantAppNotifier import com.android.systemui.statusbar.phone.KeyguardLiftController +import com.android.systemui.statusbar.phone.LockscreenWallpaper import com.android.systemui.stylus.StylusUsiPowerStartable import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import com.android.systemui.theme.ThemeOverlayController @@ -301,4 +302,9 @@ abstract class SystemUICoreStartableModule { @IntoMap @ClassKey(KeyguardViewConfigurator::class) abstract fun bindKeyguardViewConfigurator(impl: KeyguardViewConfigurator): CoreStartable + + @Binds + @IntoMap + @ClassKey(LockscreenWallpaper::class) + abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 8f3c3d6e1dd5..ead24ae71cc6 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -54,6 +54,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FlagsModule; import com.android.systemui.keyboard.KeyboardModule; +import com.android.systemui.keyguard.ui.view.layout.LockscreenLayoutModule; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.log.dagger.MonitorLog; import com.android.systemui.log.table.TableLogBuffer; @@ -178,6 +179,7 @@ import javax.inject.Named; GarbageMonitorModule.class, KeyboardModule.class, LetterboxModule.class, + LockscreenLayoutModule.class, LogModule.class, MediaProjectionModule.class, MotionToolModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt index 4069bc7d73d0..557168731b23 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt @@ -77,16 +77,30 @@ class DebugRoundedCornerDelegate : RoundedCornerResDelegate { } fun applyNewDebugCorners( - topCorner: DebugRoundedCornerModel, - bottomCorner: DebugRoundedCornerModel, + topCorner: DebugRoundedCornerModel?, + bottomCorner: DebugRoundedCornerModel?, ) { - hasTop = true - topRoundedDrawable = topCorner.toPathDrawable(paint) - topRoundedSize = topCorner.size() + topCorner?.let { + hasTop = true + topRoundedDrawable = it.toPathDrawable(paint) + topRoundedSize = it.size() + } + ?: { + hasTop = false + topRoundedDrawable = null + topRoundedSize = Size(0, 0) + } - hasBottom = true - bottomRoundedDrawable = bottomCorner.toPathDrawable(paint) - bottomRoundedSize = bottomCorner.size() + bottomCorner?.let { + hasBottom = true + bottomRoundedDrawable = it.toPathDrawable(paint) + bottomRoundedSize = it.size() + } + ?: { + hasBottom = false + bottomRoundedDrawable = null + bottomRoundedSize = Size(0, 0) + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt b/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt new file mode 100644 index 000000000000..fa1d898de850 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.decor + +import android.graphics.Color +import android.graphics.Path +import android.util.PathParser +import com.android.systemui.statusbar.commandline.ParseableCommand +import com.android.systemui.statusbar.commandline.Type +import com.android.systemui.statusbar.commandline.map +import java.io.PrintWriter + +/** Debug screen-decor command to be handled by the SystemUI command line interface */ +class ScreenDecorCommand( + private val callback: Callback, +) : ParseableCommand(SCREEN_DECOR_CMD_NAME) { + val debug: Boolean? by + param( + longName = "debug", + description = + "Enter or exits debug mode. Effectively makes the corners visible and allows " + + "for overriding the path data for the anti-aliasing corner paths and display " + + "cutout.", + valueParser = Type.Boolean, + ) + + val color: Int? by + param( + longName = "color", + shortName = "c", + description = + "Set a specific color for the debug assets. See Color#parseString() for " + + "accepted inputs.", + valueParser = Type.String.map { it.toColorIntOrNull() } + ) + + val roundedTop: RoundedCornerSubCommand? by subCommand(RoundedCornerSubCommand("rounded-top")) + + val roundedBottom: RoundedCornerSubCommand? by + subCommand(RoundedCornerSubCommand("rounded-bottom")) + + override fun execute(pw: PrintWriter) { + callback.onExecute(this, pw) + } + + override fun toString(): String { + return "ScreenDecorCommand(" + + "debug=$debug, " + + "color=$color, " + + "roundedTop=$roundedTop, " + + "roundedBottom=$roundedBottom)" + } + + /** For use in ScreenDecorations.java, define a Callback */ + interface Callback { + fun onExecute(cmd: ScreenDecorCommand, pw: PrintWriter) + } + + companion object { + const val SCREEN_DECOR_CMD_NAME = "screen-decor" + } +} + +/** + * Defines a subcommand suitable for `rounded-top` and `rounded-bottom`. They both have the same + * API. + */ +class RoundedCornerSubCommand(name: String) : ParseableCommand(name) { + val height by + param( + longName = "height", + description = "The height of a corner, in pixels.", + valueParser = Type.Int, + ) + .required() + + val width by + param( + longName = "width", + description = + "The width of the corner, in pixels. Likely should be equal to the height.", + valueParser = Type.Int, + ) + .required() + + val pathData by + param( + longName = "path-data", + shortName = "d", + description = + "PathParser-compatible path string to be rendered as the corner drawable. " + + "This path should be a closed arc oriented as the top-left corner " + + "of the device", + valueParser = Type.String.map { it.toPathOrNull() } + ) + .required() + + val viewportHeight: Float? by + param( + longName = "viewport-height", + description = + "The height of the viewport for the given path string. " + + "If null, the corner height will be used.", + valueParser = Type.Float, + ) + + val scaleY: Float + get() = viewportHeight?.let { height.toFloat() / it } ?: 1.0f + + val viewportWidth: Float? by + param( + longName = "viewport-width", + description = + "The width of the viewport for the given path string. " + + "If null, the corner width will be used.", + valueParser = Type.Float, + ) + + val scaleX: Float + get() = viewportWidth?.let { width.toFloat() / it } ?: 1.0f + + override fun execute(pw: PrintWriter) { + // Not needed for a subcommand + } + + override fun toString(): String { + return "RoundedCornerSubCommand(" + + "height=$height," + + " width=$width," + + " pathData='$pathData'," + + " viewportHeight=$viewportHeight," + + " viewportWidth=$viewportWidth)" + } + + fun toRoundedCornerDebugModel(): DebugRoundedCornerModel = + DebugRoundedCornerModel( + path = pathData, + width = width, + height = height, + scaleX = scaleX, + scaleY = scaleY, + ) +} + +fun String.toPathOrNull(): Path? = + try { + PathParser.createPathFromPathData(this) + } catch (e: Exception) { + null + } + +fun String.toColorIntOrNull(): Int? = + try { + Color.parseColor(this) + } catch (e: Exception) { + null + } diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt index c962e5155697..bcfeeb9eaadd 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt @@ -23,7 +23,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.DisplayMetricsRepoLog import com.android.systemui.statusbar.policy.ConfigurationController import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index 536978009f71..75b8e513c14a 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -20,9 +20,9 @@ import android.view.Display import com.android.systemui.doze.DozeLog.Reason import com.android.systemui.doze.DozeLog.reasonToString import com.android.systemui.log.LogBuffer -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.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.log.dagger.DozeLog import com.android.systemui.statusbar.policy.DevicePostureController import com.google.errorprone.annotations.CompileTimeConstant diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt index fdb765130157..0e224060a36f 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt @@ -17,7 +17,7 @@ package com.android.systemui.dreams import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.DreamLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt index 83253563e969..003d2c73fa06 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayLifecycleOwner.kt @@ -28,7 +28,8 @@ import javax.inject.Inject class DreamOverlayLifecycleOwner @Inject constructor() : LifecycleOwner { val registry: LifecycleRegistry = LifecycleRegistry(this) - override fun getLifecycle(): Lifecycle { - return registry - } + override val lifecycle: Lifecycle + get() { + return registry + } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index db5f5465d56d..6abe951d7f35 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -91,6 +91,15 @@ object Flags { val NOTIFICATION_SHELF_REFACTOR = unreleasedFlag(271161129, "notification_shelf_refactor") + // TODO(b/288326013): Tracking Bug + @JvmField + val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION = + unreleasedFlag( + 288326013, + "notification_async_hybrid_view_inflation", + teamfood = false + ) + @JvmField val ANIMATED_NOTIFICATION_SHADE_INSETS = releasedFlag(270682168, "animated_notification_shade_insets") @@ -251,11 +260,6 @@ object Flags { @JvmField val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area", teamfood = true) - /** Migrate the lock icon view to the new keyguard root view. */ - // TODO(b/286552209): Tracking bug. - @JvmField - val MIGRATE_LOCK_ICON = unreleasedFlag(238, "migrate_lock_icon") - /** Whether to listen for fingerprint authentication over keyguard occluding activities. */ // TODO(b/283260512): Tracking bug. @JvmField @@ -270,6 +274,11 @@ object Flags { @JvmField val TRANSIT_CLOCK = unreleasedFlag(239, "lockscreen_custom_transit_clock") + /** Migrate the lock icon view to the new keyguard root view. */ + // TODO(b/286552209): Tracking bug. + @JvmField + val MIGRATE_LOCK_ICON = unreleasedFlag(240, "migrate_lock_icon") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @@ -308,7 +317,7 @@ object Flags { ) @JvmField - val QS_PIPELINE_NEW_HOST = releasedFlag(504, "qs_pipeline_new_host") + val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = true) // TODO(b/278068252): Tracking Bug @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index e8881a482765..f59ad90d86ff 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -26,6 +26,8 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager +import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommandListener import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.statusbar.KeyguardIndicationController @@ -42,6 +44,8 @@ constructor( private val notificationShadeWindowView: NotificationShadeWindowView, private val featureFlags: FeatureFlags, private val indicationController: KeyguardIndicationController, + private val keyguardLayoutManager: KeyguardLayoutManager, + private val keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener, ) : CoreStartable { private var indicationAreaHandle: DisposableHandle? = null @@ -51,6 +55,8 @@ constructor( notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup bindIndicationArea(notificationPanel) bindLockIconView(notificationPanel) + keyguardLayoutManager.layoutViews() + keyguardLayoutManagerCommandListener.start() } fun bindIndicationArea(legacyParent: ViewGroup) { @@ -59,7 +65,7 @@ constructor( // At startup, 2 views with the ID `R.id.keyguard_indication_area` will be available. // Disable one of them if (featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) { - legacyParent.requireViewById<View>(R.id.keyguard_indication_area).let { + legacyParent.findViewById<View>(R.id.keyguard_indication_area)?.let { legacyParent.removeView(it) } } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 155e0231e9d2..468d7606933e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -140,6 +140,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.flags.SystemPropertiesHelper; import com.android.systemui.keyguard.dagger.KeyguardModule; +import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; @@ -545,6 +546,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private CentralSurfaces mCentralSurfaces; + private IRemoteAnimationFinishedCallback mUnoccludeFromDreamFinishedCallback; + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = new DeviceConfig.OnPropertiesChangedListener() { @Override @@ -582,17 +585,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onUserSwitching(int userId) { if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId)); - // Note that the mLockPatternUtils user has already been updated from setCurrentUser. - // We need to force a reset of the views, since lockNow (called by - // ActivityManagerService) will not reconstruct the keyguard if it is already showing. synchronized (KeyguardViewMediator.this) { resetKeyguardDonePendingLocked(); - if (mLockPatternUtils.isLockScreenDisabled(userId)) { - // If we are switching to a user that has keyguard disabled, dismiss keyguard. - dismiss(null /* callback */, null /* message */); - } else { - resetStateLocked(); - } + dismiss(null /* callback */, null /* message */); adjustStatusBarLocked(); } } @@ -600,16 +595,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onUserSwitchComplete(int userId) { if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); - if (userId != UserHandle.USER_SYSTEM) { - UserInfo info = UserManager.get(mContext).getUserInfo(userId); - // Don't try to dismiss if the user has Pin/Pattern/Password set - if (info == null || mLockPatternUtils.isSecure(userId)) { - return; - } else if (info.isGuest() || info.isDemo()) { - // If we just switched to a guest, try to dismiss keyguard. - dismiss(null /* callback */, null /* message */); - } - } + // We are calling dismiss again and with a delay as there are race conditions + // in some scenarios caused by async layout listeners + mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500); } @Override @@ -649,6 +637,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, switch (simState) { case TelephonyManager.SIM_STATE_NOT_READY: case TelephonyManager.SIM_STATE_ABSENT: + case TelephonyManager.SIM_STATE_UNKNOWN: + mPendingPinLock = false; // only force lock screen in case of missing sim if user hasn't // gone through setup wizard synchronized (KeyguardViewMediator.this) { @@ -713,9 +703,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } } break; - case TelephonyManager.SIM_STATE_UNKNOWN: - mPendingPinLock = false; - break; default: if (DEBUG_SIM_STATES) Log.v(TAG, "Unspecific state: " + simState); break; @@ -1177,6 +1164,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, getRemoteSurfaceAlphaApplier().accept(0.0f); mDreamingToLockscreenTransitionViewModel.get() .startTransition(); + mUnoccludeFromDreamFinishedCallback = finishedCallback; return; } @@ -1256,6 +1244,19 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, }; } + private Consumer<TransitionStep> getFinishedCallbackConsumer() { + return (TransitionStep step) -> { + if (mUnoccludeFromDreamFinishedCallback == null) return; + try { + mUnoccludeFromDreamFinishedCallback.onAnimationFinished(); + mUnoccludeFromDreamFinishedCallback = null; + } catch (RemoteException e) { + Log.e(TAG, "Wasn't able to callback", e); + } + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION); + }; + } + private DeviceConfigProxy mDeviceConfig; private DozeParameters mDozeParameters; @@ -1521,6 +1522,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, collectFlow(viewRootImpl.getView(), mDreamingToLockscreenTransitionViewModel.get().getDreamOverlayAlpha(), getRemoteSurfaceAlphaApplier(), mMainDispatcher); + collectFlow(viewRootImpl.getView(), + mDreamingToLockscreenTransitionViewModel.get().getTransitionEnded(), + getFinishedCallbackConsumer(), mMainDispatcher); } } // Most services aren't available until the system reaches the ready state, so we @@ -2383,58 +2387,72 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) { @Override public void handleMessage(Message msg) { + String message = ""; switch (msg.what) { case SHOW: + message = "SHOW"; handleShow((Bundle) msg.obj); break; case HIDE: + message = "HIDE"; handleHide(); break; case RESET: + message = "RESET"; handleReset(msg.arg1 != 0); break; case VERIFY_UNLOCK: + message = "VERIFY_UNLOCK"; Trace.beginSection("KeyguardViewMediator#handleMessage VERIFY_UNLOCK"); handleVerifyUnlock(); Trace.endSection(); break; case NOTIFY_STARTED_GOING_TO_SLEEP: + message = "NOTIFY_STARTED_GOING_TO_SLEEP"; handleNotifyStartedGoingToSleep(); break; case NOTIFY_FINISHED_GOING_TO_SLEEP: + message = "NOTIFY_FINISHED_GOING_TO_SLEEP"; handleNotifyFinishedGoingToSleep(); break; case NOTIFY_STARTED_WAKING_UP: + message = "NOTIFY_STARTED_WAKING_UP"; Trace.beginSection( "KeyguardViewMediator#handleMessage NOTIFY_STARTED_WAKING_UP"); handleNotifyStartedWakingUp(); Trace.endSection(); break; case KEYGUARD_DONE: + message = "KEYGUARD_DONE"; Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE"); handleKeyguardDone(); Trace.endSection(); break; case KEYGUARD_DONE_DRAWING: + message = "KEYGUARD_DONE_DRAWING"; Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE_DRAWING"); handleKeyguardDoneDrawing(); Trace.endSection(); break; case SET_OCCLUDED: + message = "SET_OCCLUDED"; Trace.beginSection("KeyguardViewMediator#handleMessage SET_OCCLUDED"); handleSetOccluded(msg.arg1 != 0, msg.arg2 != 0); Trace.endSection(); break; case KEYGUARD_TIMEOUT: + message = "KEYGUARD_TIMEOUT"; synchronized (KeyguardViewMediator.this) { doKeyguardLocked((Bundle) msg.obj); } break; case DISMISS: - final DismissMessage message = (DismissMessage) msg.obj; - handleDismiss(message.getCallback(), message.getMessage()); + message = "DISMISS"; + final DismissMessage dismissMsg = (DismissMessage) msg.obj; + handleDismiss(dismissMsg.getCallback(), dismissMsg.getMessage()); break; case START_KEYGUARD_EXIT_ANIM: + message = "START_KEYGUARD_EXIT_ANIM"; Trace.beginSection( "KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM"); synchronized (KeyguardViewMediator.this) { @@ -2452,21 +2470,25 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Trace.endSection(); break; case CANCEL_KEYGUARD_EXIT_ANIM: + message = "CANCEL_KEYGUARD_EXIT_ANIM"; Trace.beginSection( "KeyguardViewMediator#handleMessage CANCEL_KEYGUARD_EXIT_ANIM"); handleCancelKeyguardExitAnimation(); Trace.endSection(); break; case KEYGUARD_DONE_PENDING_TIMEOUT: + message = "KEYGUARD_DONE_PENDING_TIMEOUT"; Trace.beginSection("KeyguardViewMediator#handleMessage" + " KEYGUARD_DONE_PENDING_TIMEOUT"); Log.w(TAG, "Timeout while waiting for activity drawn!"); Trace.endSection(); break; case SYSTEM_READY: + message = "SYSTEM_READY"; handleSystemReady(); break; } + Log.d(TAG, "KeyguardViewMediator queue processing message: " + message); } }; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index b7963340228b..ed1bf3ef2753 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -167,9 +167,10 @@ constructor( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.LOCKSCREEN, animator = - getDefaultAnimatorForTransitionsToState(KeyguardState.LOCKSCREEN).apply { - duration = 0 - } + getDefaultAnimatorForTransitionsToState( + KeyguardState.LOCKSCREEN + ) + .apply { duration = 0 } ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index a8147d0c2cf6..ff0db34ca06d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -156,7 +156,12 @@ constructor( override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { return ValueAnimator().apply { - interpolator = Interpolators.LINEAR + interpolator = + when (toState) { + KeyguardState.ALTERNATE_BOUNCER -> Interpolators.FAST_OUT_SLOW_IN + else -> Interpolators.LINEAR + } + duration = when (toState) { KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index 7c5641fcda9c..4f7abd4a2174 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.keyguard.logging.KeyguardLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.log.LogLevel.VERBOSE +import com.android.systemui.log.core.LogLevel.VERBOSE import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index df7c79ff4264..45bf20d3ec44 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.GONE @@ -51,11 +52,35 @@ constructor( /** (any)->GONE transition information */ val anyStateToGoneTransition: Flow<TransitionStep> = - repository.transitions.filter { step -> step.to == KeyguardState.GONE } + repository.transitions.filter { step -> step.to == GONE } /** (any)->AOD transition information */ val anyStateToAodTransition: Flow<TransitionStep> = - repository.transitions.filter { step -> step.to == KeyguardState.AOD } + repository.transitions.filter { step -> step.to == AOD } + + /** DREAMING->(any) transition information. */ + val fromDreamingTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.from == DREAMING } + + /** (any)->Lockscreen transition information */ + val anyStateToLockscreenTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == LOCKSCREEN } + + /** (any)->Occluded transition information */ + val anyStateToOccludedTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == OCCLUDED } + + /** (any)->PrimaryBouncer transition information */ + val anyStateToPrimaryBouncerTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == PRIMARY_BOUNCER } + + /** (any)->Dreaming transition information */ + val anyStateToDreamingTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == DREAMING } + + /** (any)->AlternateBouncer transition information */ + val anyStateToAlternateBouncerTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == ALTERNATE_BOUNCER } /** AOD->LOCKSCREEN transition information. */ val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN) @@ -64,6 +89,9 @@ constructor( val dreamingToLockscreenTransition: Flow<TransitionStep> = repository.transition(DREAMING, LOCKSCREEN) + /** GONE->AOD transition information. */ + val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD) + /** GONE->DREAMING transition information. */ val goneToDreamingTransition: Flow<TransitionStep> = repository.transition(GONE, DREAMING) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index ae6fc9e6e6dc..0dda625a8b41 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -44,9 +44,9 @@ sealed class TransitionInteractor( abstract fun start() fun startTransitionTo( - toState: KeyguardState, - animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState), - resetIfCancelled: Boolean = false + toState: KeyguardState, + animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState), + resetIfCancelled: Boolean = false ): UUID? { if ( fromState != transitionInteractor.startedKeyguardState.value && diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt new file mode 100644 index 000000000000..bba0e37d8ed0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.animation.FloatEvaluator +import android.animation.IntEvaluator +import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine + +/** Encapsulates business logic for transitions between UDFPS states on the keyguard. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class UdfpsKeyguardInteractor +@Inject +constructor( + configRepo: ConfigurationRepository, + burnInInteractor: BurnInInteractor, + keyguardInteractor: KeyguardInteractor, +) { + private val intEvaluator = IntEvaluator() + private val floatEvaluator = FloatEvaluator() + + val dozeAmount = keyguardInteractor.dozeAmount + val scaleForResolution = configRepo.scaleForResolution + + /** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */ + val burnInOffsets: Flow<BurnInOffsets> = + combine( + keyguardInteractor.dozeAmount, + burnInInteractor.udfpsBurnInXOffset, + burnInInteractor.udfpsBurnInYOffset, + burnInInteractor.udfpsBurnInProgress + ) { dozeAmount, fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress -> + BurnInOffsets( + intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInX), + intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInY), + floatEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInProgress), + ) + } +} + +data class BurnInOffsets( + val burnInXOffset: Int, // current x burn in offset based on the aodTransitionAmount + val burnInYOffset: Int, // current y burn in offset based on the aodTransitionAmount + val burnInProgress: Float, // current progress based on the aodTransitionAmount +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt new file mode 100644 index 000000000000..ebf1beb132f0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.keyguard.ui.adapter + +/** + * Temporary adapter class while + * [com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController] is being refactored + * before [com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy] is removed. + * + * TODO (b/278719514): Delete once udfps keyguard view is fully refactored. + */ +interface UdfpsKeyguardViewControllerAdapter diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt new file mode 100644 index 000000000000..728dd3911663 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.binder + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.airbnb.lottie.LottieAnimationView +import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +object UdfpsAodFingerprintViewBinder { + + /** + * Drives UI for the UDFPS aod fingerprint view. See [UdfpsFingerprintViewBinder] and + * [UdfpsBackgroundViewBinder]. + */ + @JvmStatic + fun bind( + view: LottieAnimationView, + viewModel: UdfpsAodViewModel, + ) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.burnInOffsets.collect { burnInOffsets -> + view.progress = burnInOffsets.burnInProgress + view.translationX = burnInOffsets.burnInXOffset.toFloat() + view.translationY = burnInOffsets.burnInYOffset.toFloat() + } + } + + launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } } + + launch { + viewModel.padding.collect { padding -> + view.setPadding(padding, padding, padding, padding) + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt new file mode 100644 index 000000000000..26ef4685d286 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.binder + +import android.content.res.ColorStateList +import android.widget.ImageView +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +object UdfpsBackgroundViewBinder { + + /** + * Drives UI for the udfps background view. See [UdfpsAodFingerprintViewBinder] and + * [UdfpsFingerprintViewBinder]. + */ + @JvmStatic + fun bind( + view: ImageView, + viewModel: BackgroundViewModel, + ) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.transition.collect { + view.alpha = it.alpha + view.scaleX = it.scale + view.scaleY = it.scale + view.imageTintList = ColorStateList.valueOf(it.color) + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt new file mode 100644 index 000000000000..0ab8e52fb6c7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.binder + +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.airbnb.lottie.LottieAnimationView +import com.airbnb.lottie.LottieProperty +import com.airbnb.lottie.model.KeyPath +import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +object UdfpsFingerprintViewBinder { + private var udfpsIconColor = 0 + + /** + * Drives UI for the UDFPS fingerprint view when it's NOT on aod. See + * [UdfpsAodFingerprintViewBinder] and [UdfpsBackgroundViewBinder]. + */ + @JvmStatic + fun bind( + view: LottieAnimationView, + viewModel: FingerprintViewModel, + ) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.transition.collect { + view.alpha = it.alpha + view.scaleX = it.scale + view.scaleY = it.scale + if (udfpsIconColor != (it.color)) { + udfpsIconColor = it.color + view.invalidate() + } + } + } + + launch { + viewModel.burnInOffsets.collect { burnInOffsets -> + view.translationX = burnInOffsets.burnInXOffset.toFloat() + view.translationY = burnInOffsets.burnInYOffset.toFloat() + } + } + + launch { + viewModel.dozeAmount.collect { dozeAmount -> + // Lottie progress represents: aod=0 to lockscreen=1 + view.progress = 1f - dozeAmount + } + } + + launch { + viewModel.padding.collect { padding -> + view.setPadding(padding, padding, padding, padding) + } + } + } + } + + // Add a callback that updates the color to `udfpsIconColor` whenever invalidate is called + view.addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER) { + PorterDuffColorFilter(udfpsIconColor, PorterDuff.Mode.SRC_ATOP) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt new file mode 100644 index 000000000000..b568a9af9bb8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.biometrics.ui.binder + +import android.view.View +import com.android.systemui.R +import com.android.systemui.keyguard.ui.binder.UdfpsAodFingerprintViewBinder +import com.android.systemui.keyguard.ui.binder.UdfpsBackgroundViewBinder +import com.android.systemui.keyguard.ui.binder.UdfpsFingerprintViewBinder +import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +object UdfpsKeyguardInternalViewBinder { + + @JvmStatic + fun bind( + view: View, + viewModel: UdfpsKeyguardInternalViewModel, + aodViewModel: UdfpsAodViewModel, + fingerprintViewModel: FingerprintViewModel, + backgroundViewModel: BackgroundViewModel, + ) { + view.accessibilityDelegate = viewModel.accessibilityDelegate + + // bind child views + UdfpsAodFingerprintViewBinder.bind(view.findViewById(R.id.udfps_aod_fp), aodViewModel) + UdfpsFingerprintViewBinder.bind( + view.findViewById(R.id.udfps_lockscreen_fp), + fingerprintViewModel + ) + UdfpsBackgroundViewBinder.bind( + view.findViewById(R.id.udfps_keyguard_fp_bg), + backgroundViewModel + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt new file mode 100644 index 000000000000..667abaea0b24 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.binder + +import android.graphics.RectF +import android.view.View +import android.widget.FrameLayout +import androidx.asynclayoutinflater.view.AsyncLayoutInflater +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.R +import com.android.systemui.biometrics.UdfpsKeyguardView +import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder +import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +object UdfpsKeyguardViewBinder { + /** + * Drives UI for the keyguard UDFPS view. Inflates child views on a background thread. For view + * binders for its child views, see [UdfpsFingerprintViewBinder], [UdfpsBackgroundViewBinder] & + * [UdfpsAodFingerprintViewBinder]. + */ + @JvmStatic + fun bind( + view: UdfpsKeyguardView, + viewModel: UdfpsKeyguardViewModel, + udfpsKeyguardInternalViewModel: UdfpsKeyguardInternalViewModel, + aodViewModel: UdfpsAodViewModel, + fingerprintViewModel: FingerprintViewModel, + backgroundViewModel: BackgroundViewModel, + ) { + view.useExpandedOverlay(viewModel.useExpandedOverlay()) + + val layoutInflaterFinishListener = + AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent -> + UdfpsKeyguardInternalViewBinder.bind( + inflatedInternalView, + udfpsKeyguardInternalViewModel, + aodViewModel, + fingerprintViewModel, + backgroundViewModel, + ) + if (viewModel.useExpandedOverlay()) { + val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams + lp.width = viewModel.sensorBounds.width() + lp.height = viewModel.sensorBounds.height() + val relativeToView = + getBoundsRelativeToView( + inflatedInternalView, + RectF(viewModel.sensorBounds), + ) + lp.setMarginsRelative( + relativeToView.left.toInt(), + relativeToView.top.toInt(), + relativeToView.right.toInt(), + relativeToView.bottom.toInt(), + ) + parent!!.addView(inflatedInternalView, lp) + } else { + parent!!.addView(inflatedInternalView) + } + } + val inflater = AsyncLayoutInflater(view.context) + inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener) + + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { + launch { + combine(aodViewModel.isVisible, fingerprintViewModel.visible) { + isAodVisible, + isFingerprintVisible -> + isAodVisible || isFingerprintVisible + } + .collect { view.setVisible(it) } + } + } + } + } + + /** + * Converts coordinates of RectF relative to the screen to coordinates relative to this view. + * + * @param bounds RectF based off screen coordinates in current orientation + */ + private fun getBoundsRelativeToView(view: View, bounds: RectF): RectF { + val pos: IntArray = view.locationOnScreen + return RectF( + bounds.left - pos[0], + bounds.top - pos[1], + bounds.right - pos[0], + bounds.bottom - pos[1] + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt index a62f383ed704..0077f2d68c7e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt @@ -19,10 +19,7 @@ package com.android.systemui.keyguard.ui.view import android.content.Context import android.util.AttributeSet -import android.view.Gravity -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.widget.FrameLayout +import androidx.constraintlayout.widget.ConstraintLayout import com.android.keyguard.LockIconView import com.android.systemui.R @@ -31,7 +28,7 @@ class KeyguardRootView( context: Context, private val attrs: AttributeSet?, ) : - FrameLayout( + ConstraintLayout( context, attrs, ) { @@ -43,31 +40,11 @@ class KeyguardRootView( private fun addIndicationTextArea() { val view = KeyguardIndicationArea(context, attrs) - addView( - view, - FrameLayout.LayoutParams( - MATCH_PARENT, - WRAP_CONTENT, - ) - .apply { - gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL - bottomMargin = R.dimen.keyguard_indication_margin_bottom.dp() - } - ) + addView(view) } private fun addLockIconView() { val view = LockIconView(context, attrs).apply { id = R.id.lock_icon_view } - addView( - view, - LayoutParams( - WRAP_CONTENT, - WRAP_CONTENT, - ) - ) - } - - private fun Int.dp(): Int { - return context.resources.getDimensionPixelSize(this) + addView(view) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt new file mode 100644 index 000000000000..baaeb60f364e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import android.content.Context +import android.graphics.Point +import android.graphics.Rect +import android.util.DisplayMetrics +import android.view.View +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.view.WindowManager +import androidx.annotation.VisibleForTesting +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.BOTTOM +import androidx.constraintlayout.widget.ConstraintSet.END +import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID +import androidx.constraintlayout.widget.ConstraintSet.START +import androidx.constraintlayout.widget.ConstraintSet.TOP +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.R +import com.android.systemui.biometrics.AuthController +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import javax.inject.Inject + +/** + * Positions elements of the lockscreen to the default position. + * + * This will be the most common use case for phones in portrait mode. + */ +@SysUISingleton +class DefaultLockscreenLayout +@Inject +constructor( + private val authController: AuthController, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val windowManager: WindowManager, + private val context: Context, +) : LockscreenLayout { + override val id: String = DEFAULT + + override fun layoutIndicationArea(rootView: KeyguardRootView) { + val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return + + rootView.getConstraintSet().apply { + constrainWidth(indicationArea.id, MATCH_PARENT) + constrainHeight(indicationArea.id, WRAP_CONTENT) + connect( + indicationArea.id, + BOTTOM, + PARENT_ID, + BOTTOM, + R.dimen.keyguard_indication_margin_bottom.dp() + ) + connect(indicationArea.id, START, PARENT_ID, START) + connect(indicationArea.id, END, PARENT_ID, END) + applyTo(rootView) + } + } + + override fun layoutLockIcon(rootView: KeyguardRootView) { + val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported + val scaleFactor: Float = authController.scaleFactor + val mBottomPaddingPx = R.dimen.lock_icon_margin_bottom.dp() + val mDefaultPaddingPx = R.dimen.lock_icon_padding.dp() + val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt() + val bounds = windowManager.currentWindowMetrics.bounds + val widthPixels = bounds.right.toFloat() + val heightPixels = bounds.bottom.toFloat() + val defaultDensity = + DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / + DisplayMetrics.DENSITY_DEFAULT.toFloat() + val lockIconRadiusPx = (defaultDensity * 36).toInt() + + if (isUdfpsSupported) { + authController.udfpsLocation?.let { udfpsLocation -> + centerLockIcon(udfpsLocation, authController.udfpsRadius, scaledPadding, rootView) + } + } else { + centerLockIcon( + Point( + (widthPixels / 2).toInt(), + (heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt() + ), + lockIconRadiusPx * scaleFactor, + scaledPadding, + rootView + ) + } + } + + @VisibleForTesting + internal fun centerLockIcon( + center: Point, + radius: Float, + drawablePadding: Int, + rootView: KeyguardRootView, + ) { + val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return + val lockIcon = lockIconView.findViewById<View>(R.id.lock_icon) ?: return + lockIcon.setPadding(drawablePadding, drawablePadding, drawablePadding, drawablePadding) + + val sensorRect = + Rect().apply { + set( + center.x - radius.toInt(), + center.y - radius.toInt(), + center.x + radius.toInt(), + center.y + radius.toInt(), + ) + } + + rootView.getConstraintSet().apply { + constrainWidth(lockIconView.id, sensorRect.right - sensorRect.left) + constrainHeight(lockIconView.id, sensorRect.bottom - sensorRect.top) + connect(lockIconView.id, TOP, PARENT_ID, TOP, sensorRect.top) + connect(lockIconView.id, START, PARENT_ID, START, sensorRect.left) + applyTo(rootView) + } + } + + private fun Int.dp(): Int { + return context.resources.getDimensionPixelSize(this) + } + + private fun ConstraintLayout.getConstraintSet(): ConstraintSet { + val cs = ConstraintSet() + cs.clone(this) + return cs + } + + companion object { + const val DEFAULT = "default" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt new file mode 100644 index 000000000000..9bc630265a3f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import android.content.res.Configuration +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT +import com.android.systemui.statusbar.policy.ConfigurationController +import javax.inject.Inject + +/** + * Manages layout changes for the lockscreen. + * + * To add a layout, add an entry to the map with a unique id and call #transitionToLayout(string). + */ +@SysUISingleton +class KeyguardLayoutManager +@Inject +constructor( + configurationController: ConfigurationController, + layouts: Set<@JvmSuppressWildcards LockscreenLayout>, + private val keyguardRootView: KeyguardRootView, +) { + internal val layoutIdMap: Map<String, LockscreenLayout> = layouts.associateBy { it.id } + private var layout: LockscreenLayout? = layoutIdMap[DEFAULT] + + init { + configurationController.addCallback( + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + layoutViews() + } + } + ) + } + + /** + * Transitions to a layout. + * + * @param layoutId + * @return whether the transition has succeeded. + */ + fun transitionToLayout(layoutId: String): Boolean { + layout = layoutIdMap[layoutId] ?: return false + layoutViews() + return true + } + + fun layoutViews() { + layout?.layoutViews(keyguardRootView) + } + + companion object { + const val TAG = "KeyguardLayoutManager" + } +} + +interface LockscreenLayout { + val id: String + + fun layoutViews(rootView: KeyguardRootView) { + // Clear constraints. + ConstraintSet() + .apply { + clone(rootView) + knownIds.forEach { getConstraint(it).layout.copyFrom(ConstraintSet.Layout()) } + } + .applyTo(rootView) + layoutIndicationArea(rootView) + layoutLockIcon(rootView) + } + fun layoutIndicationArea(rootView: KeyguardRootView) + fun layoutLockIcon(rootView: KeyguardRootView) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt new file mode 100644 index 000000000000..b351ea8923f6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.view.layout + +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import java.io.PrintWriter +import javax.inject.Inject + +/** Uses $ adb shell cmd statusbar layout <LayoutId> */ +class KeyguardLayoutManagerCommandListener +@Inject +constructor( + private val commandRegistry: CommandRegistry, + private val keyguardLayoutManager: KeyguardLayoutManager +) { + private val layoutCommand = KeyguardLayoutManagerCommand() + + fun start() { + commandRegistry.registerCommand(COMMAND) { layoutCommand } + } + + internal inner class KeyguardLayoutManagerCommand : Command { + override fun execute(pw: PrintWriter, args: List<String>) { + val arg = args.getOrNull(0) + if (arg == null || arg.lowercase() == "help") { + help(pw) + return + } + + if (keyguardLayoutManager.transitionToLayout(arg)) { + pw.println("Transition succeeded!") + } else { + pw.println("Invalid argument! To see available layout ids, run:") + pw.println("$ adb shell cmd statusbar layout help") + } + } + + override fun help(pw: PrintWriter) { + pw.println("Usage: $ adb shell cmd statusbar layout <layoutId>") + pw.println("Existing Layout Ids: ") + keyguardLayoutManager.layoutIdMap.forEach { entry -> pw.println("${entry.key}") } + } + } + + companion object { + internal const val COMMAND = "layout" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt new file mode 100644 index 000000000000..00f93e33f370 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet + +@Module +abstract class LockscreenLayoutModule { + @Binds + @IntoSet + abstract fun bindDefaultLayout( + defaultLockscreenLayout: DefaultLockscreenLayout + ): LockscreenLayout +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index 9ca4bd62b6fe..e24d326850e0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -48,7 +48,7 @@ constructor( ) val transitionEnded = - keyguardTransitionInteractor.dreamingToLockscreenTransition.filter { step -> + keyguardTransitionInteractor.fromDreamingTransition.filter { step -> step.transitionState == TransitionState.FINISHED || step.transitionState == TransitionState.CANCELED } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt new file mode 100644 index 000000000000..667c2f1bd998 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import android.content.Context +import com.android.systemui.R +import com.android.systemui.keyguard.domain.interactor.BurnInOffsets +import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import javax.inject.Inject +import kotlin.math.roundToInt +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** View-model for UDFPS AOD view. */ +@ExperimentalCoroutinesApi +class UdfpsAodViewModel +@Inject +constructor( + val interactor: UdfpsKeyguardInteractor, + val context: Context, +) { + val alpha: Flow<Float> = interactor.dozeAmount + val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets + val isVisible: Flow<Boolean> = alpha.map { it != 0f } + + // Padding between the fingerprint icon and its bounding box in pixels. + val padding: Flow<Int> = + interactor.scaleForResolution.map { scale -> + (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale) + .roundToInt() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt new file mode 100644 index 000000000000..d894a1139eeb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.biometrics.UdfpsKeyguardAccessibilityDelegate +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +class UdfpsKeyguardInternalViewModel +@Inject +constructor(val accessibilityDelegate: UdfpsKeyguardAccessibilityDelegate) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt new file mode 100644 index 000000000000..929f27f4fea3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import android.graphics.Rect +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +class UdfpsKeyguardViewModel +@Inject +constructor( + private val featureFlags: FeatureFlags, +) { + var sensorBounds: Rect = Rect() + + fun useExpandedOverlay(): Boolean { + return featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt new file mode 100644 index 000000000000..098b481491de --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt @@ -0,0 +1,36 @@ +package com.android.systemui.keyguard.ui.viewmodel + +import android.graphics.Rect +import com.android.systemui.biometrics.UdfpsKeyguardView +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.ui.binder.UdfpsKeyguardViewBinder +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +@SysUISingleton +class UdfpsKeyguardViewModels +@Inject +constructor( + private val viewModel: UdfpsKeyguardViewModel, + private val internalViewModel: UdfpsKeyguardInternalViewModel, + private val aodViewModel: UdfpsAodViewModel, + private val lockscreenFingerprintViewModel: FingerprintViewModel, + private val lockscreenBackgroundViewModel: BackgroundViewModel, +) { + + fun setSensorBounds(sensorBounds: Rect) { + viewModel.sensorBounds = sensorBounds + } + + fun bindViews(view: UdfpsKeyguardView) { + UdfpsKeyguardViewBinder.bind( + view, + viewModel, + internalViewModel, + aodViewModel, + lockscreenFingerprintViewModel, + lockscreenBackgroundViewModel + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt new file mode 100644 index 000000000000..fd4b666a80fd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import android.content.Context +import androidx.annotation.ColorInt +import com.android.settingslib.Utils.getColorAttrDefaultColor +import com.android.systemui.R +import com.android.systemui.keyguard.domain.interactor.BurnInOffsets +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import javax.inject.Inject +import kotlin.math.roundToInt +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** View-model for UDFPS lockscreen views. */ +@ExperimentalCoroutinesApi +open class UdfpsLockscreenViewModel( + context: Context, + lockscreenColorResId: Int, + alternateBouncerColorResId: Int, + transitionInteractor: KeyguardTransitionInteractor, +) { + private val toLockscreen: Flow<TransitionViewModel> = + transitionInteractor.anyStateToLockscreenTransition.map { + TransitionViewModel( + alpha = + if (it.from == KeyguardState.AOD) { + it.value // animate + } else { + 1f + }, + scale = 1f, + color = getColorAttrDefaultColor(context, lockscreenColorResId), + ) + } + + private val toAlternateBouncer: Flow<TransitionViewModel> = + transitionInteractor.anyStateToAlternateBouncerTransition.map { + TransitionViewModel( + alpha = 1f, + scale = + if (visibleInKeyguardState(it.from)) { + 1f + } else { + it.value + }, + color = getColorAttrDefaultColor(context, alternateBouncerColorResId), + ) + } + + private val fadeOut: Flow<TransitionViewModel> = + merge( + transitionInteractor.anyStateToGoneTransition, + transitionInteractor.anyStateToAodTransition, + transitionInteractor.anyStateToOccludedTransition, + transitionInteractor.anyStateToPrimaryBouncerTransition, + transitionInteractor.anyStateToDreamingTransition, + ) + .map { + TransitionViewModel( + alpha = + if (visibleInKeyguardState(it.from)) { + 1f - it.value + } else { + 0f + }, + scale = 1f, + color = + if (it.from == KeyguardState.ALTERNATE_BOUNCER) { + getColorAttrDefaultColor(context, alternateBouncerColorResId) + } else { + getColorAttrDefaultColor(context, lockscreenColorResId) + }, + ) + } + + private fun visibleInKeyguardState(state: KeyguardState): Boolean { + return when (state) { + KeyguardState.OFF, + KeyguardState.DOZING, + KeyguardState.DREAMING, + KeyguardState.AOD, + KeyguardState.PRIMARY_BOUNCER, + KeyguardState.GONE, + KeyguardState.OCCLUDED -> false + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER -> true + } + } + + val transition: Flow<TransitionViewModel> = + merge( + toAlternateBouncer, + toLockscreen, + fadeOut, + ) + val visible: Flow<Boolean> = transition.map { it.alpha != 0f } +} + +@ExperimentalCoroutinesApi +class FingerprintViewModel +@Inject +constructor( + val context: Context, + transitionInteractor: KeyguardTransitionInteractor, + interactor: UdfpsKeyguardInteractor, +) : + UdfpsLockscreenViewModel( + context, + android.R.attr.textColorPrimary, + com.android.internal.R.attr.materialColorOnPrimaryFixed, + transitionInteractor, + ) { + val dozeAmount: Flow<Float> = interactor.dozeAmount + val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets + + // Padding between the fingerprint icon and its bounding box in pixels. + val padding: Flow<Int> = + interactor.scaleForResolution.map { scale -> + (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale) + .roundToInt() + } +} + +@ExperimentalCoroutinesApi +class BackgroundViewModel +@Inject +constructor( + val context: Context, + transitionInteractor: KeyguardTransitionInteractor, +) : + UdfpsLockscreenViewModel( + context, + com.android.internal.R.attr.colorSurface, + com.android.internal.R.attr.materialColorPrimaryFixed, + transitionInteractor, + ) + +data class TransitionViewModel( + val alpha: Float, + val scale: Float, + @ColorInt val color: Int, +) diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt index 34a67403fc84..e06483990c90 100644 --- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt +++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt @@ -167,9 +167,10 @@ class ViewLifecycleOwner( registry.currentState = Lifecycle.State.DESTROYED } - override fun getLifecycle(): Lifecycle { - return registry - } + override val lifecycle: Lifecycle + get() { + return registry + } private fun updateState() { registry.currentState = diff --git a/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt b/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt index 3226865d1d82..d4b799f444e5 100644 --- a/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt @@ -18,6 +18,7 @@ package com.android.systemui.log import com.android.systemui.bouncer.shared.model.BouncerMessageModel import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.BouncerLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt index fefa1b29b576..68cdfb6d5865 100644 --- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt @@ -6,7 +6,7 @@ import com.android.keyguard.FaceAuthUiEvent import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.FaceAuthLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt index 27301e92eca2..150de26c12c7 100644 --- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt @@ -21,9 +21,9 @@ import android.graphics.Rect import android.graphics.RectF import androidx.core.graphics.toRectF import com.android.systemui.dagger.SysUISingleton -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.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.log.dagger.ScreenDecorationsLog import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt index 8d622ae1ca03..67a985eb44bc 100644 --- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt @@ -21,8 +21,8 @@ import android.os.Trace import com.android.systemui.Dumpable import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.log.LogLevel import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.log.core.LogLevel import com.android.systemui.plugins.log.TableLogBufferBase import com.android.systemui.util.time.SystemClock import java.io.PrintWriter diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt index e2e269de71a0..534241edb253 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.media.controls.pipeline import android.media.session.PlaybackState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.MediaTimeoutListenerLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt index 9e53d77dec99..888b9c7cc901 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.media.controls.resume import android.content.ComponentName import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.MediaBrowserLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt index 30ee147e302a..2883210805d3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt @@ -128,6 +128,15 @@ constructor( var visibilityChangedListener: ((Boolean) -> Unit)? = null + /** + * Whether the doze wake up animation is delayed and we are currently waiting for it to start. + */ + var isDozeWakeUpAnimationWaiting: Boolean = false + set(value) { + field = value + refreshMediaPosition() + } + /** single pane media container placed at the top of the notifications list */ var singlePaneContainer: MediaContainerView? = null private set @@ -221,7 +230,13 @@ constructor( // by the clock. This is not the case for single-line clock though. // For single shade, we don't need to do it, because media is a child of NSSL, which already // gets hidden on AOD. - return !statusBarStateController.isDozing + // Media also has to be hidden when waking up from dozing, and the doze wake up animation is + // delayed and waiting to be started. + // This is to stay in sync with the delaying of the horizontal alignment of the rest of the + // keyguard container, that is also delayed until the "wait" is over. + // If we show media during this waiting period, the shade will still be centered, and using + // the entire width of the screen, and making media show fully stretched. + return !statusBarStateController.isDozing && !isDozeWakeUpAnimationWaiting } private fun showMediaPlayer() { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt index 0ed24349bdf4..3dc00004e900 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.media.controls.ui import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.MediaCarouselControllerLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt index c781b7699b26..8f1595d7d7a2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.media.controls.ui import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.MediaViewLog import javax.inject.Inject 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 318cd99a06ed..26a7d048cf27 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -20,6 +20,7 @@ import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECT import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE; import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; +import android.annotation.DrawableRes; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.drawable.AnimatedVectorDrawable; @@ -181,27 +182,23 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mController.getSelectedMediaDevice(), device))); boolean isHost = device.isHostForOngoingSession() && isActiveWithOngoingSession; - if (isHost) { + if (isActiveWithOngoingSession) { mCurrentActivePosition = position; updateTitleIcon(R.drawable.media_output_icon_volume, mController.getColorItemContent()); mSubTitleText.setText(device.getSubtextString()); updateTwoLineLayoutContentAlpha(DEVICE_CONNECTED_ALPHA); - updateEndClickAreaAsSessionEditing(device); + updateEndClickAreaAsSessionEditing(device, + isHost ? R.drawable.media_output_status_edit_session + : R.drawable.ic_sound_bars_anim); setTwoLineLayout(device, null /* title */, true /* bFocused */, true /* showSeekBar */, false /* showProgressBar */, true /* showSubtitle */, false /* showStatus */, true /* showEndTouchArea */, false /* isFakeActive */); initSeekbar(device, isCurrentSeekbarInvisible); } else { - if (isActiveWithOngoingSession) { - //Selected device which has ongoing session, disable seekbar since we - //only allow volume control on Host + if (currentlyConnected) { mCurrentActivePosition = position; - } - boolean showSeekbar = - (!device.hasOngoingSession() && currentlyConnected); - if (showSeekbar) { updateTitleIcon(R.drawable.media_output_icon_volume, mController.getColorItemContent()); initSeekbar(device, isCurrentSeekbarInvisible); @@ -222,10 +219,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { updateClickActionBasedOnSelectionBehavior(device) ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA); setTwoLineLayout(device, currentlyConnected /* bFocused */, - showSeekbar /* showSeekBar */, + currentlyConnected /* showSeekBar */, false /* showProgressBar */, true /* showSubtitle */, deviceStatusIcon != null /* showStatus */, - isActiveWithOngoingSession /* isFakeActive */); + false /* isFakeActive */); } } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) { setUpDeviceIcon(device); @@ -267,25 +264,16 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { setSingleLineLayout(getItemTitle(device)); } else if (device.hasOngoingSession()) { mCurrentActivePosition = position; - if (device.isHostForOngoingSession()) { - updateTitleIcon(R.drawable.media_output_icon_volume, - mController.getColorItemContent()); - updateEndClickAreaAsSessionEditing(device); - mEndClickIcon.setVisibility(View.VISIBLE); - setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, - false /* showProgressBar */, false /* showCheckBox */, - true /* showEndTouchArea */); - initSeekbar(device, isCurrentSeekbarInvisible); - } else { - updateDeviceStatusIcon(mContext.getDrawable( - R.drawable.ic_sound_bars_anim)); - mStatusIcon.setVisibility(View.VISIBLE); - updateSingleLineLayoutContentAlpha( - updateClickActionBasedOnSelectionBehavior(device) - ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA); - setSingleLineLayout(getItemTitle(device)); - initFakeActiveDevice(); - } + updateTitleIcon(R.drawable.media_output_icon_volume, + mController.getColorItemContent()); + updateEndClickAreaAsSessionEditing(device, device.isHostForOngoingSession() + ? R.drawable.media_output_status_edit_session + : R.drawable.ic_sound_bars_anim); + mEndClickIcon.setVisibility(View.VISIBLE); + setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, + false /* showProgressBar */, false /* showCheckBox */, + true /* showEndTouchArea */); + initSeekbar(device, isCurrentSeekbarInvisible); } else if (mController.isCurrentConnectedDeviceRemote() && !mController.getSelectableMediaDevice().isEmpty()) { //If device is connected and there's other selectable devices, layout as @@ -362,7 +350,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mStatusIcon.setAlpha(alphaValue); } - private void updateEndClickAreaAsSessionEditing(MediaDevice device) { + private void updateEndClickAreaAsSessionEditing(MediaDevice device, @DrawableRes int id) { mEndClickIcon.setOnClickListener(null); mEndTouchArea.setOnClickListener(null); updateEndClickAreaColor(mController.getColorSeekbarProgress()); @@ -371,6 +359,11 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mEndClickIcon.setOnClickListener( v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v)); mEndTouchArea.setOnClickListener(v -> mEndClickIcon.performClick()); + Drawable drawable = mContext.getDrawable(id); + mEndClickIcon.setImageDrawable(drawable); + if (drawable instanceof AnimatedVectorDrawable) { + ((AnimatedVectorDrawable) drawable).start(); + } } public void updateEndClickAreaColor(int color) { diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt index bbcf259418c8..417168209b43 100644 --- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt @@ -3,7 +3,7 @@ package com.android.systemui.media.muteawait import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.dagger.MediaMuteAwaitLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject /** Log messages for [MediaMuteAwaitConnectionManager]. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt index 66399d580582..46c0132deff7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt @@ -3,7 +3,7 @@ package com.android.systemui.media.nearby import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.dagger.NearbyMediaDevicesLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject /** Log messages for [NearbyMediaDevicesManager]. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt index eeda102702d2..3c2226f6a240 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt @@ -17,7 +17,7 @@ package com.android.systemui.media.taptotransfer.common import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel /** A helper for logging media tap-to-transfer events. */ object MediaTttLoggerUtils { diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt index 206e5e3ee090..503afd3a675a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt @@ -20,7 +20,7 @@ import android.app.StatusBarManager import com.android.internal.logging.InstanceId import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java index 8d809908d78b..07846b56d784 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java +++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java @@ -114,7 +114,7 @@ public class SysUiState implements Dumpable { pw.print(" mSysUiStateFlags="); pw.println(mFlags); pw.println(" " + QuickStepContract.getSystemUiStateString(mFlags)); pw.print(" backGestureDisabled="); - pw.println(QuickStepContract.isBackGestureDisabled(mFlags)); + pw.println(QuickStepContract.isBackGestureDisabled(mFlags, false /* forTrackpad */)); pw.print(" assistantGestureDisabled="); pw.println(QuickStepContract.isAssistantGestureDisabled(mFlags)); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java index 99c591f25edb..8225c47d904b 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -462,7 +462,7 @@ public final class NavBarHelper implements * @return Whether the IME is shown on top of the screen given the {@code vis} flag of * {@link InputMethodService} and the keyguard states. */ - public boolean isImeShown(@InputMethodService.ImeWindowVisibility int vis) { + public boolean isImeShown(int vis) { View shadeWindowView = mNotificationShadeWindowController.getWindowRootView(); boolean isKeyguardShowing = mKeyguardStateController.isShowing(); boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow() diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 5bae1cba4ac4..682335e0b419 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -66,7 +66,6 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; -import android.inputmethodservice.InputMethodService; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -1048,9 +1047,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements // ----- CommandQueue Callbacks ----- @Override - public void setImeWindowStatus(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, + boolean showImeSwitcher) { if (displayId != mDisplayId) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index cecf043c572e..3b32313e76a0 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -338,9 +338,8 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } @Override - public void setImeWindowStatus(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, + boolean showImeSwitcher) { boolean imeShown = mNavBarHelper.isImeShown(vis); if (!imeShown) { // Count imperceptible changes as visible so we transition taskbar out quickly. diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 7b86d0a6ebce..a8af67a9fc97 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -1023,7 +1023,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY()); boolean isBackAllowedCommon = !mDisabledForQuickstep && mIsBackGestureAllowed && !mGestureBlockingActivityRunning - && !QuickStepContract.isBackGestureDisabled(mSysUiFlags) + && !QuickStepContract.isBackGestureDisabled(mSysUiFlags, + mIsTrackpadThreeFingerSwipe) && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev); if (mIsTrackpadThreeFingerSwipe) { // Trackpad back gestures don't have zones, so we don't need to check if the down @@ -1056,8 +1057,9 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack + " disp=%s, wl=%d, il=%d, wr=%d, ir=%d, excl=%s]", curTime, curTimeStr, mAllowGesture, mIsTrackpadThreeFingerSwipe, mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed, - QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisabledForQuickstep, - mGestureBlockingActivityRunning, mIsInPip, mDisplaySize, + QuickStepContract.isBackGestureDisabled(mSysUiFlags, + mIsTrackpadThreeFingerSwipe), + mDisabledForQuickstep, mGestureBlockingActivityRunning, mIsInPip, mDisplaySize, mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion)); } else if (mAllowGesture || mLogGesture) { if (!mThresholdCrossed) { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt index e56106d1c065..f934346d9775 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt @@ -20,8 +20,8 @@ import android.icu.text.SimpleDateFormat import android.permission.PermissionGroupUsage import com.android.systemui.log.dagger.PrivacyLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogMessage +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogMessage import com.android.systemui.privacy.PrivacyDialog import com.android.systemui.privacy.PrivacyItem import java.util.Locale diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt index ac6aabb2e5bd..6563e425190d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt @@ -2,7 +2,7 @@ package com.android.systemui.qs import com.android.systemui.log.dagger.QSFragmentDisableLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.disableflags.DisableFlagsLogger import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt index 6265b3c056e7..3432628e6d67 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.external import android.content.Context import android.graphics.drawable.Icon +import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.ViewGroup import android.widget.TextView @@ -66,7 +67,8 @@ class TileRequestDialog( } private fun createTileView(tileData: TileData): QSTileView { - val tile = QSTileViewImpl(context, QSIconViewImpl(context), true) + val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) + val tile = QSTileViewImpl(themedContext, QSIconViewImpl(themedContext), true) val state = QSTile.BooleanState().apply { label = tileData.label handlesLongClick = false diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt index 9c9ad33e4918..3c53d77c6beb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt @@ -244,8 +244,8 @@ class FooterActionsViewBinder @Inject constructor() { val backgroundResource = when (model.backgroundColor) { - R.attr.offStateColor -> R.drawable.qs_footer_action_circle - com.android.internal.R.attr.colorAccent -> R.drawable.qs_footer_action_circle_color + R.attr.shadeInactive -> R.drawable.qs_footer_action_circle + R.attr.shadeActive -> R.drawable.qs_footer_action_circle_color else -> error("Unsupported icon background resource ${model.backgroundColor}") } buttonView.setBackgroundResource(backgroundResource) diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index b3596a254b7d..32146b5b00e4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -145,8 +145,12 @@ class FooterActionsViewModel( R.drawable.ic_settings, ContentDescription.Resource(R.string.accessibility_quick_settings_settings) ), - iconTint = null, - backgroundColor = R.attr.offStateColor, + iconTint = + Utils.getColorAttrDefaultColor( + context, + R.attr.onShadeInactiveVariant, + ), + backgroundColor = R.attr.shadeInactive, this::onSettingsButtonClicked, ) @@ -162,9 +166,9 @@ class FooterActionsViewModel( iconTint = Utils.getColorAttrDefaultColor( context, - com.android.internal.R.attr.textColorOnAccent, + R.attr.onShadeActive, ), - backgroundColor = com.android.internal.R.attr.colorAccent, + backgroundColor = R.attr.shadeActive, this::onPowerButtonClicked, ) } else { @@ -264,7 +268,7 @@ class FooterActionsViewModel( ), ), iconTint = null, - backgroundColor = R.attr.offStateColor, + backgroundColor = R.attr.shadeInactive, onClick = this::onUserSwitcherClicked, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index c00a81cbf12b..39745c8cbeea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -24,9 +24,9 @@ import android.view.View import com.android.systemui.log.ConstantStringsLogger import com.android.systemui.log.ConstantStringsLoggerImpl import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.VERBOSE +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.VERBOSE import com.android.systemui.log.dagger.QSConfigLog import com.android.systemui.log.dagger.QSLog import com.android.systemui.plugins.qs.QSTile diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt index 498f403e8c7a..6e7e09959697 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt @@ -51,12 +51,26 @@ class InstalledTilesComponentRepositoryImpl @Inject constructor( @Application private val applicationContext: Context, - private val packageManager: PackageManager, @Background private val backgroundDispatcher: CoroutineDispatcher, ) : InstalledTilesComponentRepository { - override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> = - conflatedCallbackFlow { + override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> { + /* + * In order to query [PackageManager] for different users, this implementation will call + * [Context.createContextAsUser] and retrieve the [PackageManager] from that context. + */ + val packageManager = + if (applicationContext.userId == userId) { + applicationContext.packageManager + } else { + applicationContext + .createContextAsUser( + UserHandle.of(userId), + /* flags */ 0, + ) + .packageManager + } + return conflatedCallbackFlow { val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { @@ -74,12 +88,13 @@ constructor( awaitClose { applicationContext.unregisterReceiver(receiver) } } .onStart { emit(Unit) } - .map { reloadComponents(userId) } + .map { reloadComponents(userId, packageManager) } .distinctUntilChanged() .flowOn(backgroundDispatcher) + } @WorkerThread - private fun reloadComponents(userId: Int): Set<ComponentName> { + private fun reloadComponents(userId: Int, packageManager: PackageManager): Set<ComponentName> { return packageManager .queryIntentServicesAsUser(INTENT, FLAGS, userId) .mapNotNull { it.serviceInfo } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt index d400faa3091e..573cb7154c4f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs.pipeline.shared.logging import android.annotation.UserIdInt import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.qs.pipeline.dagger.QSAutoAddLog import com.android.systemui.qs.pipeline.dagger.QSTileListLog import com.android.systemui.qs.pipeline.shared.TileSpec diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index e54168162de6..7e45491adc83 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -248,13 +248,11 @@ public class QSIconViewImpl extends QSIconView { */ private static int getIconColorForState(Context context, QSTile.State state) { if (state.disabledByPolicy || state.state == Tile.STATE_UNAVAILABLE) { - return Utils.getColorAttrDefaultColor( - context, com.android.internal.R.attr.textColorTertiary); + return Utils.getColorAttrDefaultColor(context, R.attr.outline); } else if (state.state == Tile.STATE_INACTIVE) { - return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary); + return Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactiveVariant); } else if (state.state == Tile.STATE_ACTIVE) { - return Utils.getColorAttrDefaultColor(context, - com.android.internal.R.attr.textColorOnAccent); + return Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive); } else { Log.e("QSIconView", "Invalid state " + state); return 0; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 2a9e7d05c187..1ca2a961744b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -330,7 +330,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy final int eventId = mClickEventId++; mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state, eventId); - mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget(); + if (!mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) { + mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget(); + } } public LogMaker populate(LogMaker logMaker) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index b80668379e49..d81e4c229aa7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -29,6 +29,7 @@ import android.os.Trace import android.service.quicksettings.Tile import android.text.TextUtils import android.util.Log +import android.util.TypedValue import android.view.Gravity import android.view.LayoutInflater import android.view.View @@ -92,24 +93,21 @@ open class QSTileViewImpl @JvmOverloads constructor( updateHeight() } - private val colorActive = Utils.getColorAttrDefaultColor(context, - com.android.internal.R.attr.colorAccentPrimary) - private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.offStateColor) - private val colorUnavailable = Utils.applyAlpha(UNAVAILABLE_ALPHA, colorInactive) + private val colorActive = Utils.getColorAttrDefaultColor(context, R.attr.shadeActive) + private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.shadeInactive) + private val colorUnavailable = Utils.getColorAttrDefaultColor(context, R.attr.shadeDisabled) - private val colorLabelActive = - Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent) - private val colorLabelInactive = - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) + private val colorLabelActive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive) + private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive) private val colorLabelUnavailable = - Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary) + Utils.getColorAttrDefaultColor(context, R.attr.outline) private val colorSecondaryLabelActive = - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondaryInverse) + Utils.getColorAttrDefaultColor(context, R.attr.onShadeActiveVariant) private val colorSecondaryLabelInactive = - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary) + Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactiveVariant) private val colorSecondaryLabelUnavailable = - Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary) + Utils.getColorAttrDefaultColor(context, R.attr.outline) private lateinit var label: TextView protected lateinit var secondaryLabel: TextView @@ -151,6 +149,11 @@ open class QSTileViewImpl @JvmOverloads constructor( private val locInScreen = IntArray(2) init { + val typedValue = TypedValue() + if (!getContext().theme.resolveAttribute(R.attr.isQsTheme, typedValue, true)) { + throw IllegalStateException("QSViewImpl must be inflated with a theme that contains " + + "Theme.SystemUI.QuickSettings") + } setId(generateViewId()) orientation = LinearLayout.HORIZONTAL gravity = Gravity.CENTER_VERTICAL or Gravity.START diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index 9b5898f42279..2ad5429668d0 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt @@ -52,9 +52,8 @@ class SceneWindowRootView( setOnBackInvokedDispatcher(viewRootImpl.onBackInvokedDispatcher) } - override fun getLifecycle(): Lifecycle { - return this@repeatWhenAttached.lifecycle - } + override val lifecycle: Lifecycle = + this@repeatWhenAttached.lifecycle } ) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 2ef9e0772d93..d97db3b27c87 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -117,6 +117,7 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeLog; @@ -208,7 +209,6 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.TapAgainViewController; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; @@ -238,7 +238,7 @@ import kotlin.Unit; import kotlinx.coroutines.CoroutineDispatcher; -@CentralSurfacesComponent.CentralSurfacesScope +@SysUISingleton public final class NotificationPanelViewController implements ShadeSurface, Dumpable { public static final String TAG = NotificationPanelView.class.getSimpleName(); @@ -1175,6 +1175,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewComponentFactory.build(keyguardStatusView); mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController(); mKeyguardStatusViewController.init(); + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); updateClockAppearance(); if (mKeyguardUserSwitcherController != null) { @@ -1227,6 +1228,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void onSplitShadeEnabledChanged() { mShadeLog.logSplitShadeChanged(mSplitShadeEnabled); + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); // Reset any left over overscroll state. It is a rare corner case but can happen. mQsController.setOverScrollAmount(0); mScrimController.setNotificationsOverScrollAmount(0); @@ -1407,11 +1409,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardBottomArea = keyguardBottomArea; } - void setOpenCloseListener(OpenCloseListener openCloseListener) { + @Override + public void setOpenCloseListener(OpenCloseListener openCloseListener) { mOpenCloseListener = openCloseListener; } - void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) { + @Override + public void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) { mTrackingStartedListener = trackingStartedListener; } @@ -1623,6 +1627,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mWillPlayDelayedDozeAmountAnimation = willPlay; mWakeUpCoordinator.logDelayingClockWakeUpAnimation(willPlay); + mKeyguardMediaController.setDozeWakeUpAnimationWaiting(willPlay); // Once changing this value, see if we should move the clock. positionClockAndNotifications(); @@ -3378,11 +3383,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ViewGroupFadeHelper.reset(mView); } - void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { + @Override + public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { mView.getViewTreeObserver().addOnGlobalLayoutListener(listener); } - void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { + @Override + public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener); } @@ -3565,6 +3572,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { + mShadeLog.logEndMotionEvent("endMotionEvent called", forceCancel, false); mTrackingPointer = -1; mAmbientState.setSwipingUp(false); if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop @@ -3586,15 +3594,19 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { if (onKeyguard) { expand = true; + mShadeLog.logEndMotionEvent("endMotionEvent: cancel while on keyguard", + forceCancel, expand); } else if (mCentralSurfaces.isBouncerShowingOverDream()) { expand = false; } else { // If we get a cancel, put the shade back to the state it was in when the // gesture started expand = !mPanelClosedOnDown; + mShadeLog.logEndMotionEvent("endMotionEvent: cancel", forceCancel, expand); } } else { expand = flingExpands(vel, vectorVel, x, y); + mShadeLog.logEndMotionEvent("endMotionEvent: flingExpands", forceCancel, expand); } mDozeLog.traceFling( @@ -3847,8 +3859,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return !isFullyCollapsed() && !mTracking && !mClosing; } - /** Collapses the shade instantly without animation. */ - void instantCollapse() { + @Override + public void instantCollapse() { abortAnimations(); setExpandedFraction(0f); if (mExpanding) { @@ -4021,8 +4033,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mFixedDuration = NO_FIXED_DURATION; } - /** */ - boolean postToView(Runnable action) { + @Override + public boolean postToView(Runnable action) { return mView.post(action); } @@ -4731,6 +4743,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mTouchSlopExceeded = mTouchSlopExceededBeforeDown; mMotionAborted = false; mPanelClosedOnDown = isFullyCollapsed(); + mShadeLog.logPanelClosedOnDown("intercept down touch", mPanelClosedOnDown, + mExpandedFraction); mCollapsedAndHeadsUpOnDown = false; mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; @@ -4948,6 +4962,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); mMinExpandHeight = 0.0f; mPanelClosedOnDown = isFullyCollapsed(); + mShadeLog.logPanelClosedOnDown("handle down touch", mPanelClosedOnDown, + mExpandedFraction); mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; mMotionAborted = false; @@ -5113,18 +5129,5 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return super.performAccessibilityAction(host, action, args); } } - - /** Listens for when touch tracking begins. */ - interface TrackingStartedListener { - void onTrackingStarted(); - } - - /** Listens for when shade begins opening of finishes closing. */ - interface OpenCloseListener { - /** Called when the shade finishes closing. */ - void onClosingFinished(); - /** Called when the shade starts opening. */ - void onOpenStarted(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 1361c9f25eff..025c461110ef 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -68,6 +68,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; @@ -98,7 +99,6 @@ import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.LargeScreenUtils; @@ -113,7 +113,7 @@ import javax.inject.Inject; /** Handles QuickSettings touch handling, expansion and animation state * TODO (b/264460656) make this dumpable */ -@CentralSurfacesComponent.CentralSurfacesScope +@SysUISingleton public class QuickSettingsController implements Dumpable { public static final String TAG = "QuickSettingsController"; @@ -1220,14 +1220,15 @@ public class QuickSettingsController implements Dumpable { if (mIsFullWidth) { clipStatusView = qsVisible; float screenCornerRadius = - !mSplitShadeEnabled || mRecordingController.isRecording() - || mCastController.hasConnectedCastDevice() + mRecordingController.isRecording() || mCastController.hasConnectedCastDevice() ? 0 : mScreenCornerRadius; radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius, Math.min(top / (float) mScrimCornerRadius, 1f)); - float bottomRadius = mExpanded ? screenCornerRadius : - calculateBottomCornerRadius(screenCornerRadius); + float bottomRadius = mSplitShadeEnabled ? screenCornerRadius : 0; + if (!mExpanded) { + bottomRadius = calculateBottomCornerRadius(bottomRadius); + } mScrimController.setNotificationBottomRadius(bottomRadius); } if (isQsFragmentCreated()) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java index 9ed0e9a8b359..317d88585958 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java @@ -165,8 +165,7 @@ public interface ShadeController { NotificationShadeWindowViewController notificationShadeWindowViewController); /** */ - void setNotificationPanelViewController( - NotificationPanelViewController notificationPanelViewController); + void setShadeViewController(ShadeViewController shadeViewController); /** Listens for shade visibility changes. */ interface ShadeVisibilityListener { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java index c9338b3614ea..b92afac047fa 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java @@ -70,7 +70,8 @@ public final class ShadeControllerImpl implements ShadeController { private boolean mExpandedVisible; - private NotificationPanelViewController mNotificationPanelViewController; + // TODO(b/237661616): Rename this variable to mShadeViewController. + private ShadeViewController mNotificationPanelViewController; private NotificationPresenter mPresenter; private NotificationShadeWindowViewController mNotificationShadeWindowViewController; private ShadeVisibilityListener mShadeVisibilityListener; @@ -426,12 +427,11 @@ public final class ShadeControllerImpl implements ShadeController { } @Override - public void setNotificationPanelViewController( - NotificationPanelViewController notificationPanelViewController) { - mNotificationPanelViewController = notificationPanelViewController; + public void setShadeViewController(ShadeViewController shadeViewController) { + mNotificationPanelViewController = shadeViewController; mNotificationPanelViewController.setTrackingStartedListener(this::runPostCollapseRunnables); mNotificationPanelViewController.setOpenCloseListener( - new NotificationPanelViewController.OpenCloseListener() { + new OpenCloseListener() { @Override public void onClosingFinished() { ShadeControllerImpl.this.onClosingFinished(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index 3af75cef3d4c..8789a8b3b7f4 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -195,7 +195,9 @@ constructor( set(value) { if (visible && field != value) { field = value + iconContainer.setQsExpansionTransitioning(value > 0f && value < 1.0f) updatePosition() + updateIgnoredSlots() } } @@ -216,6 +218,8 @@ constructor( view.onApplyWindowInsets(insets) } + private var singleCarrier = false + private val demoModeReceiver = object : DemoMode { override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK) @@ -479,17 +483,20 @@ constructor( private fun updateListeners() { mShadeCarrierGroupController.setListening(visible) if (visible) { - updateSingleCarrier(mShadeCarrierGroupController.isSingleCarrier) + singleCarrier = mShadeCarrierGroupController.isSingleCarrier + updateIgnoredSlots() mShadeCarrierGroupController.setOnSingleCarrierChangedListener { - updateSingleCarrier(it) + singleCarrier = it + updateIgnoredSlots() } } else { mShadeCarrierGroupController.setOnSingleCarrierChangedListener(null) } } - private fun updateSingleCarrier(singleCarrier: Boolean) { - if (singleCarrier) { + private fun updateIgnoredSlots() { + // switching from QQS to QS state halfway through the transition + if (singleCarrier || qsExpandedFraction < 0.5) { iconContainer.removeIgnoredSlots(carrierIconSlots) } else { iconContainer.addIgnoredSlots(carrierIconSlots) diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index 2da8d5f4d921..1c30bddfe859 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.shade import android.view.MotionEvent import com.android.systemui.log.dagger.ShadeLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.shade.ShadeViewController.Companion.FLING_COLLAPSE import com.android.systemui.shade.ShadeViewController.Companion.FLING_EXPAND import com.android.systemui.shade.ShadeViewController.Companion.FLING_HIDE @@ -90,7 +90,7 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { double1 = event.y.toDouble() }, { - "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2" + "$str1: eventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2" } ) } @@ -280,6 +280,42 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { ) } + fun logEndMotionEvent( + msg: String, + forceCancel: Boolean, + expand: Boolean, + ) + { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = msg + bool1 = forceCancel + bool2 = expand + }, + { "$str1; force=$bool1; expand=$bool2" } + ) + } + + fun logPanelClosedOnDown( + msg: String, + panelClosedOnDown: Boolean, + expandFraction: Float, + ) + { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = msg + bool1 = panelClosedOnDown + double1 = expandFraction.toDouble() + }, + { "$str1; mPanelClosedOnDown=$bool1; mExpandedFraction=$double1" } + ) + } + fun flingQs(flingType: Int, isClick: Boolean) { buffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index a2b93516695a..8ae9e5e1fb8c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -49,6 +49,7 @@ import com.android.systemui.statusbar.NotificationShelfController import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.phone.KeyguardBottomAreaView import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.phone.TapAgainView import com.android.systemui.statusbar.policy.BatteryController @@ -71,6 +72,12 @@ abstract class ShadeModule { @ClassKey(AuthRippleController::class) abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable + @Binds + @SysUISingleton + abstract fun bindsShadeViewController( + notificationPanelViewController: NotificationPanelViewController + ): ShadeViewController + companion object { const val SHADE_HEADER = "large_screen_shade_header" @@ -165,6 +172,20 @@ abstract class ShadeModule { return notificationShadeWindowView.findViewById(R.id.notification_panel) } + /** + * Constructs a new, unattached [KeyguardBottomAreaView]. + * + * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it + */ + @Provides + fun providesKeyguardBottomAreaView( + npv: NotificationPanelView, + layoutInflater: LayoutInflater, + ): KeyguardBottomAreaView { + return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false) + as KeyguardBottomAreaView + } + @Provides @SysUISingleton fun providesLightRevealScrim( @@ -194,9 +215,15 @@ abstract class ShadeModule { @Provides @SysUISingleton fun providesLockIconView( - notificationShadeWindowView: NotificationShadeWindowView, + keyguardRootView: KeyguardRootView, + notificationPanelView: NotificationPanelView, + featureFlags: FeatureFlags ): LockIconView { - return notificationShadeWindowView.findViewById(R.id.lock_icon_view) + if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) { + return keyguardRootView.findViewById(R.id.lock_icon_view) + } else { + return notificationPanelView.findViewById(R.id.lock_icon_view) + } } // TODO(b/277762009): Only allow this view's controller to inject the view. See above. diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index 3d9fcf9cdecb..9aa5eb0cd68b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade import android.view.MotionEvent import android.view.ViewGroup +import android.view.ViewTreeObserver import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.statusbar.RemoteInputController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow @@ -77,6 +78,9 @@ interface ShadeViewController { /** Collapses the shade with an animation duration in milliseconds. */ fun collapseWithDuration(animationDuration: Int) + /** Collapses the shade instantly without animation. */ + fun instantCollapse() + /** * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If * in split shade, it will collapse the whole shade. @@ -100,6 +104,9 @@ interface ShadeViewController { /** Returns whether the shade's top level view is enabled. */ val isViewEnabled: Boolean + /** Sets a listener to be notified when the shade starts opening or finishes closing. */ + fun setOpenCloseListener(openCloseListener: OpenCloseListener) + /** Returns whether status bar icons should be hidden when the shade is expanded. */ fun shouldHideStatusBarIconsWhenExpanded(): Boolean @@ -109,6 +116,9 @@ interface ShadeViewController { */ fun blockExpansionForCurrentTouch() + /** Sets a listener to be notified when touch tracking begins. */ + fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) + /** * Disables the shade header. * @@ -178,6 +188,15 @@ interface ShadeViewController { /** Ensures that the touchable region is updated. */ fun updateTouchableRegion() + /** Adds a global layout listener. */ + fun addOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) + + /** Removes a global layout listener. */ + fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) + + /** Posts the given runnable to the view. */ + fun postToView(action: Runnable): Boolean + // ******* Begin Keyguard Section ********* /** Animate to expanded shade after a delay in ms. Used for lockscreen to shade transition. */ fun transitionToExpandedShade(delay: Long) @@ -337,3 +356,17 @@ interface ShadeViewStateProvider { /** Return the fraction of the shade that's expanded, when in lockscreen. */ val lockscreenShadeDragProgress: Float } + +/** Listens for when touch tracking begins. */ +interface TrackingStartedListener { + fun onTrackingStarted() +} + +/** Listens for when shade begins opening or finishes closing. */ +interface OpenCloseListener { + /** Called when the shade finishes closing. */ + fun onClosingFinished() + + /** Called when the shade starts opening. */ + fun onOpenStarted() +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt index d06634b63b6b..51a27cf8989a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt @@ -21,9 +21,9 @@ import com.android.systemui.log.dagger.ShadeWindowLog import com.android.systemui.log.ConstantStringsLogger import com.android.systemui.log.ConstantStringsLoggerImpl import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogMessage +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogMessage import javax.inject.Inject private const val TAG = "systemui.shadewindow" diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index eceda8453902..6fde84a35fb1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -17,38 +17,52 @@ package com.android.systemui.shade.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.user.domain.interactor.UserInteractor import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn /** Business logic for shade interactions. */ @SysUISingleton class ShadeInteractor @Inject constructor( + @Application scope: CoroutineScope, disableFlagsRepository: DisableFlagsRepository, keyguardRepository: KeyguardRepository, userSetupRepository: UserSetupRepository, deviceProvisionedController: DeviceProvisionedController, userInteractor: UserInteractor, ) { + /** Emits true if the shade is currently allowed and false otherwise. */ + val isShadeEnabled: StateFlow<Boolean> = + disableFlagsRepository.disableFlags + .map { it.isShadeEnabled() } + .stateIn(scope, SharingStarted.Eagerly, initialValue = false) + /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */ val isExpandToQsEnabled: Flow<Boolean> = combine( disableFlagsRepository.disableFlags, + isShadeEnabled, keyguardRepository.isDozing, userSetupRepository.isUserSetupFlow, - ) { disableFlags, isDozing, isUserSetup -> + ) { disableFlags, isShadeEnabled, isDozing, isUserSetup -> deviceProvisionedController.isDeviceProvisioned && // Disallow QS during setup if it's a simple user switcher. (The user intends to // use the lock screen user switcher, QS is not needed.) (isUserSetup || !userInteractor.isSimpleUserSwitcher) && - disableFlags.isShadeEnabled() && + isShadeEnabled && disableFlags.isQuickSettingsEnabled() && !isDozing } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt index e008ec0dc75c..d3c19b75a71d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar import android.app.PendingIntent import com.android.systemui.log.dagger.NotifInteractionLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.notification.collection.NotificationEntry import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 6c2c0cf12aad..a532195c5b9f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar; import static android.app.StatusBarManager.DISABLE2_NONE; import static android.app.StatusBarManager.DISABLE_NONE; import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; +import static android.inputmethodservice.InputMethodService.IME_INVISIBLE; import static android.view.Display.INVALID_DISPLAY; import android.annotation.Nullable; @@ -36,7 +37,7 @@ import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; -import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.InputMethodService.BackDispositionMode; import android.media.INearbyMediaDevicesProvider; import android.media.MediaRoute2Info; import android.os.Binder; @@ -225,10 +226,8 @@ public class CommandQueue extends IStatusBar.Stub implements * @param backDisposition Disposition mode of back button. It should be one of below flags: * @param showImeSwitcher {@code true} to show IME switch button. */ - default void setImeWindowStatus(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, - boolean showImeSwitcher) { } + default void setImeWindowStatus(int displayId, IBinder token, int vis, + @BackDispositionMode int backDisposition, boolean showImeSwitcher) { } default void showRecentApps(boolean triggeredFromAltTab) { } default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { } default void toggleTaskbar() { } @@ -679,9 +678,7 @@ public class CommandQueue extends IStatusBar.Stub implements } @Override - public void setImeWindowStatus(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, + public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { synchronized (mLock) { mHandler.removeMessages(MSG_SHOW_IME_BUTTON); @@ -1095,9 +1092,7 @@ public class CommandQueue extends IStatusBar.Stub implements } } - private void handleShowImeButton(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, + private void handleShowImeButton(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { if (displayId == INVALID_DISPLAY) return; @@ -1117,7 +1112,7 @@ public class CommandQueue extends IStatusBar.Stub implements private void sendImeInvisibleStatusForPrevNavBar() { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).setImeWindowStatus(mLastUpdatedImeDisplayId, - null /* token */, 0 /* vis */, BACK_DISPOSITION_DEFAULT, + null /* token */, IME_INVISIBLE, BACK_DISPOSITION_DEFAULT, false /* showImeSwitcher */); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index 39181449aaa0..ec66e994b58a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -557,19 +557,7 @@ public final class KeyboardShortcutListSearch { new ShortcutKeyGroupMultiMappingInfo( context.getString(R.string.group_system_access_google_assistant), Arrays.asList( - Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))), - /* Lock screen: Meta + L */ - new ShortcutKeyGroupMultiMappingInfo( - context.getString(R.string.group_system_lock_screen), - Arrays.asList( - Pair.create(KeyEvent.KEYCODE_L, KeyEvent.META_META_ON))), - /* Pull up Notes app for quick memo: Meta + Ctrl + N */ - new ShortcutKeyGroupMultiMappingInfo( - context.getString(R.string.group_system_quick_memo), - Arrays.asList( - Pair.create( - KeyEvent.KEYCODE_N, - KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON))) + Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))) ); for (ShortcutKeyGroupMultiMappingInfo info : infoList) { systemGroup.addItem(info.getShortcutMultiMappingInfo()); @@ -611,21 +599,12 @@ public final class KeyboardShortcutListSearch { new ArrayList<>()); // System multitasking shortcuts: - // Enter Split screen with current app to RHS: Meta + Ctrl + Right arrow - // Enter Split screen with current app to LHS: Meta + Ctrl + Left arrow // Switch from Split screen to full screen: Meta + Ctrl + Up arrow - // During Split screen: replace an app from one to another: Meta + Ctrl + Down arrow String[] shortcutLabels = { - context.getString(R.string.system_multitasking_rhs), - context.getString(R.string.system_multitasking_lhs), context.getString(R.string.system_multitasking_full_screen), - context.getString(R.string.system_multitasking_replace) }; int[] keyCodes = { - KeyEvent.KEYCODE_DPAD_RIGHT, - KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_UP, - KeyEvent.KEYCODE_DPAD_DOWN }; for (int i = 0; i < shortcutLabels.length; i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 96924821cc1d..42ebaa3877d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -41,7 +41,7 @@ import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewCont import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED; import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON; -import static com.android.systemui.log.LogLevel.ERROR; +import static com.android.systemui.log.core.LogLevel.ERROR; import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY; import android.app.AlarmManager; @@ -97,7 +97,7 @@ import com.android.systemui.keyguard.KeyguardIndication; import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.util.IndicationHelper; -import com.android.systemui.log.LogLevel; +import com.android.systemui.log.core.LogLevel; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index e2d2ac0fcb58..4710574ac20d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -32,6 +32,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView @@ -76,6 +77,7 @@ class LockscreenShadeTransitionController @Inject constructor( dumpManager: DumpManager, qsTransitionControllerFactory: LockscreenShadeQsTransitionController.Factory, private val shadeRepository: ShadeRepository, + private val shadeInteractor: ShadeInteractor, private val powerInteractor: PowerInteractor, ) : Dumpable { private var pulseHeight: Float = 0f @@ -558,7 +560,7 @@ class LockscreenShadeTransitionController @Inject constructor( animationHandler: ((Long) -> Unit)? = null, cancelAction: Runnable? = null ) { - if (centralSurfaces.isShadeDisabled) { + if (!shadeInteractor.isShadeEnabled.value) { cancelAction?.run() logger.logShadeDisabledOnGoToLockedShade() return diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt new file mode 100644 index 000000000000..de369c35345c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.commandline + +/** + * [CommandParser] defines the collection of tokens which can be parsed from an incoming command + * list, and parses them into their respective containers. Supported tokens are of the following + * forms: + * ``` + * Flag: boolean value, false by default. always optional. + * Param: named parameter, taking N args all of a given type. Currently only single arg parameters + * are supported. + * SubCommand: named command created by adding a command to a parent. Supports all fields above, but + * not other subcommands. + * ``` + * + * Tokens are added via the factory methods for each token type. They can be made `required` by + * calling the [require] method for the appropriate type, as follows: + * ``` + * val requiredParam = parser.require(parser.param(...)) + * ``` + * + * The reason for having an explicit require is so that generic type arguments can be handled + * properly. See [SingleArgParam] and [SingleArgParamOptional] for the difference between an + * optional parameter and a required one. + * + * Typical usage of a required parameter, however, will occur within the context of a + * [ParseableCommand], which defines a convenience `require()` method: + * ``` + * class MyCommand : ParseableCommand { + * val requiredParam = param(...).require() + * } + * ``` + * + * This parser defines two modes of parsing, both of which validate for required parameters. + * 1. [parse] is a top-level parsing method. This parser will walk the given arg list and populate + * all of the delegate classes based on their type. It will handle SubCommands, and after parsing + * will check for any required-but-missing SubCommands or Params. + * + * **This method requires that every received token is represented in its grammar.** + * 2. [parseAsSubCommand] is a second-level parsing method suitable for any [SubCommand]. This + * method will handle _only_ flags and params. It will return parsing control to its parent + * parser on the first unknown token rather than throwing. + */ +class CommandParser { + private val _flags = mutableListOf<Flag>() + val flags: List<Flag> = _flags + private val _params = mutableListOf<Param>() + val params: List<Param> = _params + private val _subCommands = mutableListOf<SubCommand>() + val subCommands: List<SubCommand> = _subCommands + + private val tokenSet = mutableSetOf<String>() + + /** + * Parse the arg list into the fields defined in the containing class. + * + * @return true if all required fields are present after parsing + * @throws ArgParseError on any failure to process args + */ + fun parse(args: List<String>): Boolean { + if (args.isEmpty()) { + return false + } + + val iterator = args.listIterator() + var tokenHandled: Boolean + while (iterator.hasNext()) { + val token = iterator.next() + tokenHandled = false + + flags + .find { it.matches(token) } + ?.let { + it.inner = true + tokenHandled = true + } + + if (tokenHandled) continue + + params + .find { it.matches(token) } + ?.let { + it.parseArgsFromIter(iterator) + tokenHandled = true + } + + if (tokenHandled) continue + + subCommands + .find { it.matches(token) } + ?.let { + it.parseSubCommandArgs(iterator) + tokenHandled = true + } + + if (!tokenHandled) { + throw ArgParseError("Unknown token: $token") + } + } + + return validateRequiredParams() + } + + /** + * Parse a subset of the commands that came in from the top-level [parse] method, for the + * subcommand that this parser represents. Note that subcommands may not contain other + * subcommands. But they may contain flags and params. + * + * @return true if all required fields are present after parsing + * @throws ArgParseError on any failure to process args + */ + fun parseAsSubCommand(iter: ListIterator<String>): Boolean { + // arg[-1] is our subcommand name, so the rest of the args are either for this + // subcommand, OR for the top-level command to handle. Therefore, we bail on the first + // failure, but still check our own required params + + // The mere presence of a subcommand (similar to a flag) is a valid subcommand + if (flags.isEmpty() && params.isEmpty()) { + return validateRequiredParams() + } + + var tokenHandled: Boolean + while (iter.hasNext()) { + val token = iter.next() + tokenHandled = false + + flags + .find { it.matches(token) } + ?.let { + it.inner = true + tokenHandled = true + } + + if (tokenHandled) continue + + params + .find { it.matches(token) } + ?.let { + it.parseArgsFromIter(iter) + tokenHandled = true + } + + if (!tokenHandled) { + // Move the cursor position backwards since we've arrived at a token + // that we don't own + iter.previous() + break + } + } + + return validateRequiredParams() + } + + /** + * If [parse] or [parseAsSubCommand] does not produce a valid result, generate a list of errors + * based on missing elements + */ + fun generateValidationErrorMessages(): List<String> { + val missingElements = mutableListOf<String>() + + if (unhandledParams.isNotEmpty()) { + val names = unhandledParams.map { it.longName } + missingElements.add("No values passed for required params: $names") + } + + if (unhandledSubCmds.isNotEmpty()) { + missingElements.addAll(unhandledSubCmds.map { it.longName }) + val names = unhandledSubCmds.map { it.shortName } + missingElements.add("No values passed for required sub-commands: $names") + } + + return missingElements + } + + /** Check for any missing, required params, or any invalid subcommands */ + private fun validateRequiredParams(): Boolean = + unhandledParams.isEmpty() && unhandledSubCmds.isEmpty() && unvalidatedSubCmds.isEmpty() + + // If any required param (aka non-optional) hasn't handled a field, then return false + private val unhandledParams: List<Param> + get() = params.filter { (it is SingleArgParam<*>) && !it.handled } + + private val unhandledSubCmds: List<SubCommand> + get() = subCommands.filter { (it is RequiredSubCommand<*> && !it.handled) } + + private val unvalidatedSubCmds: List<SubCommand> + get() = subCommands.filter { !it.validationStatus } + + private fun checkCliNames(short: String?, long: String): String? { + if (short != null && tokenSet.contains(short)) { + return short + } + + if (tokenSet.contains(long)) { + return long + } + + return null + } + + private fun subCommandContainsSubCommands(cmd: ParseableCommand): Boolean = + cmd.parser.subCommands.isNotEmpty() + + private fun registerNames(short: String?, long: String) { + if (short != null) { + tokenSet.add(short) + } + tokenSet.add(long) + } + + /** + * Turns a [SingleArgParamOptional]<T> into a [SingleArgParam] by converting the [T?] into [T] + * + * @return a [SingleArgParam] property delegate + */ + fun <T : Any> require(old: SingleArgParamOptional<T>): SingleArgParam<T> { + val newParam = + SingleArgParam( + longName = old.longName, + shortName = old.shortName, + description = old.description, + valueParser = old.valueParser, + ) + + replaceWithRequired(old, newParam) + return newParam + } + + private fun <T : Any> replaceWithRequired( + old: SingleArgParamOptional<T>, + new: SingleArgParam<T>, + ) { + _params.remove(old) + _params.add(new) + } + + /** + * Turns an [OptionalSubCommand] into a [RequiredSubCommand] by converting the [T?] in to [T] + * + * @return a [RequiredSubCommand] property delegate + */ + fun <T : ParseableCommand> require(optional: OptionalSubCommand<T>): RequiredSubCommand<T> { + val newCmd = RequiredSubCommand(optional.cmd) + replaceWithRequired(optional, newCmd) + return newCmd + } + + private fun <T : ParseableCommand> replaceWithRequired( + old: OptionalSubCommand<T>, + new: RequiredSubCommand<T>, + ) { + _subCommands.remove(old) + _subCommands.add(new) + } + + internal fun flag( + longName: String, + shortName: String? = null, + description: String = "", + ): Flag { + checkCliNames(shortName, longName)?.let { + throw IllegalArgumentException("Detected reused flag name ($it)") + } + registerNames(shortName, longName) + + val flag = Flag(shortName, longName, description) + _flags.add(flag) + return flag + } + + internal fun <T : Any> param( + longName: String, + shortName: String? = null, + description: String = "", + valueParser: ValueParser<T>, + ): SingleArgParamOptional<T> { + checkCliNames(shortName, longName)?.let { + throw IllegalArgumentException("Detected reused param name ($it)") + } + registerNames(shortName, longName) + + val param = + SingleArgParamOptional( + shortName = shortName, + longName = longName, + description = description, + valueParser = valueParser, + ) + _params.add(param) + return param + } + + internal fun <T : ParseableCommand> subCommand( + command: T, + ): OptionalSubCommand<T> { + checkCliNames(null, command.name)?.let { + throw IllegalArgumentException("Cannot re-use name for subcommand ($it)") + } + + if (subCommandContainsSubCommands(command)) { + throw IllegalArgumentException( + "SubCommands may not contain other SubCommands. $command" + ) + } + + registerNames(null, command.name) + + val subCmd = OptionalSubCommand(command) + _subCommands.add(subCmd) + return subCmd + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt new file mode 100644 index 000000000000..6ed5eed79c82 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.commandline + +import android.util.IndentingPrintWriter +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * Definitions for all parameter types usable by [ParseableCommand]. Parameters are command line + * tokens that accept a fixed number of arguments and convert them to a parsed type. + * + * Example: + * ``` + * my_command --single-arg-param arg + * ``` + * + * In the example, `my_command` is the name of the command, `--single-arg-param` is the parameter, + * and `arg` is the value parsed by that parameter into its eventual type. + * + * Note on generics: The intended usage for parameters is to be able to return the parsed type from + * the given command as a `val` via property delegation. For example, let's say we have a command + * that has one optional and one required parameter: + * ``` + * class MyCommand : ParseableCommand { + * val requiredParam: Int by parser.param(...).required() + * val optionalParam: Int? by parser.param(...) + * } + * ``` + * + * In order to make the simple `param` method return the correct type, we need to do two things: + * 1. Break out the generic type into 2 pieces (TParsed and T) + * 2. Create two different underlying Parameter subclasses to handle the property delegation. One + * handles `T?` and the other handles `T`. Note that in both cases, `TParsed` is always non-null + * since the value parsed from the argument will throw an exception if missing or if it cannot be + * parsed. + */ + +/** A param type knows the number of arguments it expects */ +sealed interface Param : Describable { + val numArgs: Int + + /** + * Consume [numArgs] items from the iterator and relay the result into its corresponding + * delegated type. + */ + fun parseArgsFromIter(iterator: Iterator<String>) +} + +/** + * Base class for required and optional SingleArgParam classes. For convenience, UnaryParam is + * defined as a [MultipleArgParam] where numArgs = 1. The benefit is that we can define the parsing + * in a single place, and yet on the client side we can unwrap the underlying list of params + * automatically. + */ +abstract class UnaryParamBase<out T, out TParsed : T>(val wrapped: MultipleArgParam<T, TParsed>) : + Param, ReadOnlyProperty<Any?, T> { + var handled = false + + override fun describe(pw: IndentingPrintWriter) { + if (shortName != null) { + pw.print("$shortName, ") + } + pw.print(longName) + pw.println(" ${typeDescription()}") + if (description != null) { + pw.indented { pw.println(description) } + } + } + + /** + * Try to describe the arg type. We can know if it's one of the base types what kind of input it + * takes. Otherwise just print "<arg>" and let the clients describe in the help text + */ + private fun typeDescription() = + when (wrapped.valueParser) { + Type.Int -> "<int>" + Type.Float -> "<float>" + Type.String -> "<string>" + Type.Boolean -> "<boolean>" + else -> "<arg>" + } +} + +/** Required single-arg parameter, delegating a non-null type to the client. */ +class SingleArgParam<out T : Any>( + override val longName: String, + override val shortName: String? = null, + override val description: String? = null, + val valueParser: ValueParser<T>, +) : + UnaryParamBase<T, T>( + MultipleArgParam( + longName, + shortName, + 1, + description, + valueParser, + ) + ) { + + override fun getValue(thisRef: Any?, property: KProperty<*>): T = + if (handled) { + wrapped.getValue(thisRef, property)[0] + } else { + throw IllegalStateException("Attempt to read property before parse() has executed") + } + + override val numArgs: Int = 1 + + override fun parseArgsFromIter(iterator: Iterator<String>) { + wrapped.parseArgsFromIter(iterator) + handled = true + } +} + +/** Optional single-argument parameter, delegating a nullable type to the client. */ +class SingleArgParamOptional<out T : Any>( + override val longName: String, + override val shortName: String? = null, + override val description: String? = null, + val valueParser: ValueParser<T>, +) : + UnaryParamBase<T?, T>( + MultipleArgParam( + longName, + shortName, + 1, + description, + valueParser, + ) + ) { + override fun getValue(thisRef: Any?, property: KProperty<*>): T? = + wrapped.getValue(thisRef, property).getOrNull(0) + + override val numArgs: Int = 1 + + override fun parseArgsFromIter(iterator: Iterator<String>) { + wrapped.parseArgsFromIter(iterator) + handled = true + } +} + +/** + * Parses a list of args into the underlying [T] data type. The resultant value is an ordered list + * of type [TParsed]. + * + * [T] and [TParsed] are split out here in the case where the entire param is optional. I.e., a + * MultipleArgParam<T?, T> indicates a command line argument that can be omitted. In that case, the + * inner list is List<T>?, NOT List<T?>. If the argument is provided, then the type is always going + * to be parsed into T rather than T?. + */ +class MultipleArgParam<out T, out TParsed : T>( + override val longName: String, + override val shortName: String? = null, + override val numArgs: Int = 1, + override val description: String? = null, + val valueParser: ValueParser<TParsed>, +) : ReadOnlyProperty<Any?, List<TParsed>>, Param { + private val inner: MutableList<TParsed> = mutableListOf() + + override fun getValue(thisRef: Any?, property: KProperty<*>): List<TParsed> = inner + + /** + * Consumes [numArgs] values of the iterator and parses them into [TParsed]. + * + * @throws ArgParseError on the first failure + */ + override fun parseArgsFromIter(iterator: Iterator<String>) { + if (!iterator.hasNext()) { + throw ArgParseError("no argument provided for $shortName") + } + for (i in 0 until numArgs) { + valueParser + .parseValue(iterator.next()) + .fold(onSuccess = { inner.add(it) }, onFailure = { throw it }) + } + } +} + +data class ArgParseError(override val message: String) : Exception(message) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt new file mode 100644 index 000000000000..ecd3fa6cc299 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.commandline + +import android.util.IndentingPrintWriter +import java.io.PrintWriter +import java.lang.IllegalArgumentException +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * An implementation of [Command] that includes a [CommandParser] which can set all delegated + * properties. + * + * As the number of registrants to [CommandRegistry] grows, we should have a default mechanism for + * parsing common command line arguments. We are not expecting to build an arbitrarily-functional + * CLI, nor a GNU arg parse compliant interface here, we simply want to be able to empower clients + * to create simple CLI grammars such as: + * ``` + * $ my_command [-f|--flag] + * $ my_command [-a|--arg] <params...> + * $ my_command [subcommand1] [subcommand2] + * $ my_command <positional_arg ...> # not-yet implemented + * ``` + * + * Note that the flags `-h` and `--help` are reserved for the base class. It seems prudent to just + * avoid them in your implementation. + * + * Usage: + * + * The intended usage tries to be clever enough to enable good ergonomics, while not too clever as + * to be unmaintainable. Using the default parser is done using property delegates, and looks like: + * ``` + * class MyCommand( + * onExecute: (cmd: MyCommand, pw: PrintWriter) -> () + * ) : ParseableCommand(name) { + * val flag1 by flag( + * shortName = "-f", + * longName = "--flag", + * required = false, + * ) + * val param1: String by param( + * shortName = "-a", + * longName = "--args", + * valueParser = Type.String + * ).required() + * val param2: Int by param(..., valueParser = Type.Int) + * val subCommand by subCommand(...) + * + * override fun execute(pw: PrintWriter) { + * onExecute(this, pw) + * } + * + * companion object { + * const val name = "my_command" + * } + * } + * + * fun main() { + * fun printArgs(cmd: MyCommand, pw: PrintWriter) { + * pw.println("${cmd.flag1}") + * pw.println("${cmd.param1}") + * pw.println("${cmd.param2}") + * pw.println("${cmd.subCommand}") + * } + * + * commandRegistry.registerCommand(MyCommand.companion.name) { + * MyCommand() { (cmd, pw) -> + * printArgs(cmd, pw) + * } + * } + * } + * + * ``` + */ +abstract class ParseableCommand(val name: String, val description: String? = null) : Command { + val parser: CommandParser = CommandParser() + + val help by flag(longName = "help", shortName = "h", description = "Print help and return") + + /** + * After [execute(pw, args)] is called, this class goes through a parsing stage and sets all + * delegated properties. It is safe to read any delegated properties here. + * + * This method is never called for [SubCommand]s, since they are associated with a top-level + * command that handles [execute] + */ + abstract fun execute(pw: PrintWriter) + + /** + * Given a command string list, [execute] parses the incoming command and validates the input. + * If this command or any of its subcommands is passed `-h` or `--help`, then execute will only + * print the relevant help message and exit. + * + * If any error is thrown during parsing, we will catch and log the error. This process should + * _never_ take down its process. Override [onParseFailed] to handle an [ArgParseError]. + * + * Important: none of the delegated fields can be read before this stage. + */ + override fun execute(pw: PrintWriter, args: List<String>) { + val success: Boolean + try { + success = parser.parse(args) + } catch (e: ArgParseError) { + pw.println(e.message) + onParseFailed(e) + return + } catch (e: Exception) { + pw.println("Unknown exception encountered during parse") + pw.println(e) + return + } + + // Now we've parsed the incoming command without error. There are two things to check: + // 1. If any help is requested, print the help message and return + // 2. Otherwise, make sure required params have been passed in, and execute + + val helpSubCmds = subCmdsRequestingHelp() + + // Top-level help encapsulates subcommands. Otherwise, if _any_ subcommand requests + // help then defer to them. Else, just execute + if (help) { + help(pw) + } else if (helpSubCmds.isNotEmpty()) { + helpSubCmds.forEach { it.help(pw) } + } else { + if (!success) { + parser.generateValidationErrorMessages().forEach { pw.println(it) } + } else { + execute(pw) + } + } + } + + /** + * Returns a list of all commands that asked for help. If non-empty, parsing will stop to print + * help. It is not guaranteed that delegates are fulfilled if help is requested + */ + private fun subCmdsRequestingHelp(): List<ParseableCommand> = + parser.subCommands.filter { it.cmd.help }.map { it.cmd } + + /** Override to do something when parsing fails */ + open fun onParseFailed(error: ArgParseError) {} + + /** Override to print a usage clause. E.g. `usage: my-cmd <arg1> <arg2>` */ + open fun usage(pw: IndentingPrintWriter) {} + + /** + * Print out the list of tokens, their received types if any, and their description in a + * formatted string. + * + * Example: + * ``` + * my-command: + * MyCmd.description + * + * [optional] usage block + * + * Flags: + * -f + * description + * --flag2 + * description + * + * Parameters: + * Required: + * -p1 [Param.Type] + * description + * --param2 [Param.Type] + * description + * Optional: + * same as above + * + * SubCommands: + * Required: + * ... + * Optional: + * ... + * ``` + */ + override fun help(pw: PrintWriter) { + val ipw = IndentingPrintWriter(pw) + ipw.printBoxed(name) + ipw.println() + + // Allow for a simple `usage` block for clients + ipw.indented { usage(ipw) } + + if (description != null) { + ipw.indented { ipw.println(description) } + ipw.println() + } + + val flags = parser.flags + if (flags.isNotEmpty()) { + ipw.println("FLAGS:") + ipw.indented { + flags.forEach { + it.describe(ipw) + ipw.println() + } + } + } + + val (required, optional) = parser.params.partition { it is SingleArgParam<*> } + if (required.isNotEmpty()) { + ipw.println("REQUIRED PARAMS:") + required.describe(ipw) + } + if (optional.isNotEmpty()) { + ipw.println("OPTIONAL PARAMS:") + optional.describe(ipw) + } + + val (reqSub, optSub) = parser.subCommands.partition { it is RequiredSubCommand<*> } + if (reqSub.isNotEmpty()) { + ipw.println("REQUIRED SUBCOMMANDS:") + reqSub.describe(ipw) + } + if (optSub.isNotEmpty()) { + ipw.println("OPTIONAL SUBCOMMANDS:") + optSub.describe(ipw) + } + } + + fun flag( + longName: String, + shortName: String? = null, + description: String = "", + ): Flag { + if (!checkShortName(shortName)) { + throw IllegalArgumentException( + "Flag short name must be one character long, or null. Got ($shortName)" + ) + } + + if (!checkLongName(longName)) { + throw IllegalArgumentException("Flags must not start with '-'. Got $($longName)") + } + + val short = shortName?.let { "-$shortName" } + val long = "--$longName" + + return parser.flag(long, short, description) + } + + fun <T : Any> param( + longName: String, + shortName: String? = null, + description: String = "", + valueParser: ValueParser<T>, + ): SingleArgParamOptional<T> { + if (!checkShortName(shortName)) { + throw IllegalArgumentException( + "Parameter short name must be one character long, or null. Got ($shortName)" + ) + } + + if (!checkLongName(longName)) { + throw IllegalArgumentException("Parameters must not start with '-'. Got $($longName)") + } + + val short = shortName?.let { "-$shortName" } + val long = "--$longName" + + return parser.param(long, short, description, valueParser) + } + + fun <T : ParseableCommand> subCommand( + command: T, + ) = parser.subCommand(command) + + /** For use in conjunction with [param], makes the parameter required */ + fun <T : Any> SingleArgParamOptional<T>.required(): SingleArgParam<T> = parser.require(this) + + /** For use in conjunction with [subCommand], makes the given [SubCommand] required */ + fun <T : ParseableCommand> OptionalSubCommand<T>.required(): RequiredSubCommand<T> = + parser.require(this) + + private fun checkShortName(short: String?): Boolean { + return short == null || short.length == 1 + } + + private fun checkLongName(long: String): Boolean { + return !long.startsWith("-") + } + + companion object { + fun Iterable<Describable>.describe(pw: IndentingPrintWriter) { + pw.indented { + forEach { + it.describe(pw) + pw.println() + } + } + } + } +} + +/** + * A flag is a boolean value passed over the command line. It can have a short form or long form. + * The value is [Boolean.true] if the flag is found, else false + */ +data class Flag( + override val shortName: String? = null, + override val longName: String, + override val description: String? = null, +) : ReadOnlyProperty<Any?, Boolean>, Describable { + var inner: Boolean = false + + override fun getValue(thisRef: Any?, property: KProperty<*>) = inner +} + +/** + * Named CLI token. Can have a short or long name. Note: consider renaming to "primary" and + * "secondary" names since we don't actually care what the strings are + * + * Flags and params will have [shortName]s that are always prefixed with a single dash, while + * [longName]s are prefixed by a double dash. E.g., `my_command -f --flag`. + * + * Subcommands do not do any prefixing, and register their name as the [longName] + * + * Can be matched against an incoming token + */ +interface CliNamed { + val shortName: String? + val longName: String + + fun matches(token: String) = shortName == token || longName == token +} + +interface Describable : CliNamed { + val description: String? + + fun describe(pw: IndentingPrintWriter) { + if (shortName != null) { + pw.print("$shortName, ") + } + pw.print(longName) + pw.println() + if (description != null) { + pw.indented { pw.println(description) } + } + } +} + +/** + * Print [s] inside of a unicode character box, like so: + * ``` + * ╔═══════════╗ + * ║ my-string ║ + * ╚═══════════╝ + * ``` + */ +fun PrintWriter.printDoubleBoxed(s: String) { + val length = s.length + println("╔${"═".repeat(length + 2)}╗") + println("║ $s ║") + println("╚${"═".repeat(length + 2)}╝") +} + +/** + * Print [s] inside of a unicode character box, like so: + * ``` + * ┌───────────┐ + * │ my-string │ + * └───────────┘ + * ``` + */ +fun PrintWriter.printBoxed(s: String) { + val length = s.length + println("┌${"─".repeat(length + 2)}┐") + println("│ $s │") + println("└${"─".repeat(length + 2)}┘") +} + +fun IndentingPrintWriter.indented(block: () -> Unit) { + increaseIndent() + block() + decreaseIndent() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt new file mode 100644 index 000000000000..41bac86fd6c9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.commandline + +import android.util.IndentingPrintWriter +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * Sub commands wrap [ParseableCommand]s and are attached to a parent [ParseableCommand]. As such + * they have their own parser which will parse the args as a subcommand. I.e., the subcommand's + * parser will consume the iterator created by the parent, reversing the index when it reaches an + * unknown token. + * + * In order to keep subcommands relatively simple and not have to do complicated validation, sub + * commands will return control to the parent parser as soon as they discover a token that they do + * not own. They will throw an [ArgParseError] if parsing fails or if they don't receive arguments + * for a required parameter. + */ +sealed interface SubCommand : Describable { + val cmd: ParseableCommand + + /** Checks if all of the required elements were passed in to [parseSubCommandArgs] */ + var validationStatus: Boolean + + /** + * To keep parsing simple, [parseSubCommandArgs] requires a [ListIterator] so that it can rewind + * the iterator when it yields control upwards + */ + fun parseSubCommandArgs(iterator: ListIterator<String>) +} + +/** + * Note that the delegated type from the subcommand is `T: ParseableCommand?`. SubCommands are + * created via adding a fully-formed [ParseableCommand] to parent command. + * + * At this point in time, I don't recommend nesting subcommands. + */ +class OptionalSubCommand<T : ParseableCommand>( + override val cmd: T, +) : SubCommand, ReadOnlyProperty<Any?, ParseableCommand?> { + override val shortName: String? = null + override val longName: String = cmd.name + override val description: String? = cmd.description + override var validationStatus = true + + private var isPresent = false + + /** Consume tokens from the iterator and pass them to the wrapped command */ + override fun parseSubCommandArgs(iterator: ListIterator<String>) { + validationStatus = cmd.parser.parseAsSubCommand(iterator) + isPresent = true + } + + override fun getValue(thisRef: Any?, property: KProperty<*>): T? = + if (isPresent) { + cmd + } else { + null + } + + override fun describe(pw: IndentingPrintWriter) { + cmd.help(pw) + } +} + +/** + * Non-optional subcommand impl. Top-level parser is expected to throw [ArgParseError] if this token + * is not present in the incoming command + */ +class RequiredSubCommand<T : ParseableCommand>( + override val cmd: T, +) : SubCommand, ReadOnlyProperty<Any?, ParseableCommand> { + override val shortName: String? = null + override val longName: String = cmd.name + override val description: String? = cmd.description + override var validationStatus = true + + /** Unhandled, required subcommands are an error */ + var handled = false + + override fun parseSubCommandArgs(iterator: ListIterator<String>) { + validationStatus = cmd.parser.parseAsSubCommand(iterator) + handled = true + } + + override fun getValue(thisRef: Any?, property: KProperty<*>): ParseableCommand = cmd + + override fun describe(pw: IndentingPrintWriter) { + cmd.help(pw) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt new file mode 100644 index 000000000000..01083d9a7907 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.commandline + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Utilities for parsing the [String] command line arguments. Arguments are related to the + * [Parameter] type, which declares the number of, and resulting type of, the arguments that it + * takes when parsing. For Example: + * ``` + * my-command --param <str> --param2 <int> + * ``` + * + * Defines 2 parameters, the first of which takes a string, and the second requires an int. Because + * fundamentally _everything_ is a string, we have to define a convenient way to get from the + * incoming `StringArg` to the resulting `T`-arg, where `T` is the type required by the client. + * + * Parsing is therefore a relatively straightforward operation: (String) -> T. However, since + * parsing can always fail, the type is actually (String) -> Result<T>. We will always want to fail + * on the first error and propagate it to the caller (typically this results in printing the `help` + * message of the command`). + * + * The identity parsing is trivial: + * ``` + * (s: String) -> String = { s -> s } + * ``` + * + * Basic mappings are actually even provided by Kotlin's stdlib: + * ``` + * (s: String) -> Boolean = { s -> s.toBooleanOrNull() } + * (s: String) -> Int = { s -> s.toIntOrNull() } + * ... + * ``` + * + * In order to properly encode errors, we will ascribe an error type to any `null` values, such that + * parsing looks like this: + * ``` + * val mapping: (String) -> T? = {...} // for some T + * val parser: (String) -> Result<T> = { s -> + * mapping(s)?.let { + * Result.success(it) + * } ?: Result.failure(/* some failure type */) + * } + * ``` + * + * Composition + * + * The ability to compose value parsing enables us to provide a couple of reasonable default parsers + * and allow clients to seamlessly build upon that using map functions. Consider the case where we + * want to validate that a value is an [Int] between 0 and 100. We start with the generic [Int] + * parser, and a validator, of the type (Int) -> Result<Int>: + * ``` + * val intParser = { s -> + * s.toStringOrNull().?let {...} ?: ... + * } + * + * val validator = { i -> + * if (i > 100 || i < 0) { + * Result.failure(...) + * } else { + * Result.success(i) + * } + * ``` + * + * In order to combine these functions, we need to define a new [flatMap] function that can get us + * from a `Result<T>` to a `Result<R>`, and short-circuit on any error. We want to see this: + * ``` + * val validatingParser = { s -> + * intParser.invoke(s).flatMap { i -> + * validator(i) + * } + * } + * ``` + * + * The flatMap is relatively simply defined, we can mimic the existing definition for [Result.map], + * though the implementation is uglier because of the `internal` definition for `value` + * + * ``` + * inline fun <R, T> Result<T>.flatMap(transform: (value: T) -> Result<R>): Result<R> { + * return when { + * isSuccess -> transform(getOrThrow()) + * else -> Result.failure(exceptionOrNull()!!) + * } + * } + * ``` + */ + +/** + * Given a [transform] that returns a [Result], apply the transform to this result, unwrapping the + * return value so that + * + * These [contract] and [callsInPlace] methods are copied from the [Result.map] definition + */ +@OptIn(ExperimentalContracts::class) +inline fun <R, T> Result<T>.flatMap(transform: (value: T) -> Result<R>): Result<R> { + contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } + + return when { + // Should never throw, we just don't have access to [this.value] + isSuccess -> transform(getOrThrow()) + // Exception should never be null here + else -> Result.failure(exceptionOrNull()!!) + } +} + +/** + * ValueParser turns a [String] into a Result<A> by applying a transform. See the default + * implementations below for starting points. The intention here is to provide the base mappings and + * allow clients to attach their own transforms. They are expected to succeed or return null on + * failure. The failure is propagated to the command parser as a Result and will fail on any + * [Result.failure] + */ +fun interface ValueParser<out A> { + fun parseValue(value: String): Result<A> +} + +/** Map a [ValueParser] of type A to one of type B, by applying the given [transform] */ +inline fun <A, B> ValueParser<A>.map(crossinline transform: (A) -> B?): ValueParser<B> { + return ValueParser<B> { value -> + this.parseValue(value).flatMap { a -> + transform(a)?.let { b -> Result.success(b) } + ?: Result.failure(ArgParseError("Failed to transform value $value")) + } + } +} + +/** + * Base type parsers are provided by the lib, and can be simply composed upon by [ValueParser.map] + * functions on the parser + */ + +/** String parsing always succeeds if the value exists */ +private val parseString: ValueParser<String> = ValueParser { value -> Result.success(value) } + +private val parseBoolean: ValueParser<Boolean> = ValueParser { value -> + value.toBooleanStrictOrNull()?.let { Result.success(it) } + ?: Result.failure(ArgParseError("Failed to parse $value as a boolean")) +} + +private val parseInt: ValueParser<Int> = ValueParser { value -> + value.toIntOrNull()?.let { Result.success(it) } + ?: Result.failure(ArgParseError("Failed to parse $value as an int")) +} + +private val parseFloat: ValueParser<Float> = ValueParser { value -> + value.toFloatOrNull()?.let { Result.success(it) } + ?: Result.failure(ArgParseError("Failed to parse $value as a float")) +} + +/** Default parsers that can be use as-is, or [map]ped to another type */ +object Type { + val Boolean = parseBoolean + val Int = parseInt + val Float = parseFloat + val String = parseString +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index 2465c21c956f..73f181b8c734 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -74,7 +74,7 @@ import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; import com.android.systemui.log.LogBuffer; -import com.android.systemui.log.LogLevel; +import com.android.systemui.log.core.LogLevel; import com.android.systemui.log.dagger.StatusBarNetworkControllerLog; import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; import com.android.systemui.settings.UserTracker; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 075b41b91d97..035fa0454bfc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -41,6 +41,8 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.settings.DisplayTracker; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.ShadeSurface; import com.android.systemui.shade.carrier.ShadeCarrierGroupController; import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; @@ -273,6 +275,21 @@ public interface CentralSurfacesDependenciesModule { return ongoingCallController; } + /** + * {@link NotificationPanelViewController} implements two interfaces: + * - {@link com.android.systemui.shade.ShadeViewController}, which can be used by any class + * needing access to the shade. + * - {@link ShadeSurface}, which should *only* be used by {@link CentralSurfacesImpl}. + * + * Since {@link ShadeSurface} should only be accessible by {@link CentralSurfacesImpl}, it's + * *only* bound in this CentralSurfaces dependencies module. + * The {@link com.android.systemui.shade.ShadeViewController} interface is bound in + * {@link com.android.systemui.shade.ShadeModule} so others can access it. + */ + @Binds + @SysUISingleton + ShadeSurface provideShadeSurface(NotificationPanelViewController impl); + /** */ @Binds ShadeCarrierGroupController.SlotIndexResolver provideSlotIndexResolver( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt index ac05248a2b87..2bb476523cb8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt @@ -20,7 +20,7 @@ import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS import android.app.StatusBarManager.DISABLE_NONE import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.disableflags.DisableFlagsLogger /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt index 3d6d48917dd3..84796f9acbc0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt @@ -22,6 +22,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogBufferFactory import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.time.SystemClock @@ -36,6 +38,13 @@ interface StatusBarEventsModule { @Provides @SysUISingleton + @SystemStatusAnimationSchedulerLog + fun provideSystemStatusAnimationSchedulerLogBuffer(factory: LogBufferFactory): LogBuffer { + return factory.create("SystemStatusAnimationSchedulerLog", 60) + } + + @Provides + @SysUISingleton fun provideSystemStatusAnimationScheduler( featureFlags: FeatureFlags, coordinator: SystemEventCoordinator, @@ -44,7 +53,8 @@ interface StatusBarEventsModule { dumpManager: DumpManager, systemClock: SystemClock, @Application coroutineScope: CoroutineScope, - @Main executor: DelayableExecutor + @Main executor: DelayableExecutor, + logger: SystemStatusAnimationSchedulerLogger ): SystemStatusAnimationScheduler { return if (featureFlags.isEnabled(Flags.PLUG_IN_STATUS_BAR_CHIP)) { SystemStatusAnimationSchedulerImpl( @@ -53,7 +63,8 @@ interface StatusBarEventsModule { statusBarWindowController, dumpManager, systemClock, - coroutineScope + coroutineScope, + logger ) } else { SystemStatusAnimationSchedulerLegacyImpl( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt index 56ea703668d0..6fc715a2b578 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.events import android.os.Process import android.provider.DeviceConfig -import android.util.Log import androidx.core.animation.Animator import androidx.core.animation.AnimatorListenerAdapter import androidx.core.animation.AnimatorSet @@ -69,7 +68,8 @@ constructor( private val statusBarWindowController: StatusBarWindowController, dumpManager: DumpManager, private val systemClock: SystemClock, - @Application private val coroutineScope: CoroutineScope + @Application private val coroutineScope: CoroutineScope, + private val logger: SystemStatusAnimationSchedulerLogger? ) : SystemStatusAnimationScheduler { companion object { @@ -121,6 +121,10 @@ constructor( } } } + + coroutineScope.launch { + animationState.collect { logger?.logAnimationStateUpdate(it) } + } } @SystemAnimationState override fun getAnimationState(): Int = animationState.value @@ -140,32 +144,17 @@ constructor( ) { // a event can only be scheduled if no other event is in progress or it has a higher // priority. If a persistent dot is currently displayed, don't schedule the event. - if (DEBUG) { - Log.d(TAG, "scheduling event $event") - } - + logger?.logScheduleEvent(event) scheduleEvent(event) } else if (currentlyDisplayedEvent?.shouldUpdateFromEvent(event) == true) { - if (DEBUG) { - Log.d( - TAG, - "updating current event from: $event. animationState=${animationState.value}" - ) - } + logger?.logUpdateEvent(event, animationState.value) currentlyDisplayedEvent?.updateFromEvent(event) if (event.forceVisible) hasPersistentDot = true } else if (scheduledEvent.value?.shouldUpdateFromEvent(event) == true) { - if (DEBUG) { - Log.d( - TAG, - "updating scheduled event from: $event. animationState=${animationState.value}" - ) - } + logger?.logUpdateEvent(event, animationState.value) scheduledEvent.value?.updateFromEvent(event) } else { - if (DEBUG) { - Log.d(TAG, "ignoring event $event") - } + logger?.logIgnoreEvent(event) } } @@ -356,6 +345,7 @@ constructor( } private fun notifyTransitionToPersistentDot(): Animator? { + logger?.logTransitionToPersistentDotCallbackInvoked() val anims: List<Animator> = listeners.mapNotNull { it.onSystemStatusAnimationTransitionToPersistentDot( @@ -373,6 +363,7 @@ constructor( private fun notifyHidePersistentDot(): Animator? { Assert.isMainThread() + logger?.logHidePersistentDotCallbackInvoked() val anims: List<Animator> = listeners.mapNotNull { it.onHidePersistentDot() } if (animationState.value == SHOWING_PERSISTENT_DOT) { @@ -424,5 +415,4 @@ constructor( } } -private const val DEBUG = false private const val TAG = "SystemStatusAnimationSchedulerImpl" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt new file mode 100644 index 000000000000..4ac94a60d4c6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.events + +import javax.inject.Qualifier + +/** Logs for the SystemStatusAnimationScheduler. */ +@Qualifier +@MustBeDocumented +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +annotation class SystemStatusAnimationSchedulerLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt new file mode 100644 index 000000000000..22b0b691ad3b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt @@ -0,0 +1,92 @@ +package com.android.systemui.statusbar.events + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import javax.inject.Inject + +/** Logs for the SystemStatusAnimationScheduler. */ +@SysUISingleton +class SystemStatusAnimationSchedulerLogger +@Inject +constructor( + @SystemStatusAnimationSchedulerLog private val logBuffer: LogBuffer, +) { + + fun logScheduleEvent(event: StatusEvent) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = event.javaClass.simpleName + int1 = event.priority + bool1 = event.forceVisible + bool2 = event.showAnimation + }, + { "Scheduling event: $str1(forceVisible=$bool1, priority=$int1, showAnimation=$bool2)" } + ) + } + + fun logUpdateEvent(event: StatusEvent, @SystemAnimationState animationState: Int) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = event.javaClass.simpleName + int1 = event.priority + bool1 = event.forceVisible + bool2 = event.showAnimation + int2 = animationState + }, + { + "Updating current event from: $str1(forceVisible=$bool1, priority=$int1, " + + "showAnimation=$bool2), animationState=${animationState.name()}" + } + ) + } + + fun logIgnoreEvent(event: StatusEvent) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = event.javaClass.simpleName + int1 = event.priority + bool1 = event.forceVisible + bool2 = event.showAnimation + }, + { "Ignore event: $str1(forceVisible=$bool1, priority=$int1, showAnimation=$bool2)" } + ) + } + + fun logHidePersistentDotCallbackInvoked() { + logBuffer.log(TAG, LogLevel.DEBUG, "Hide persistent dot callback invoked") + } + + fun logTransitionToPersistentDotCallbackInvoked() { + logBuffer.log(TAG, LogLevel.DEBUG, "Transition to persistent dot callback invoked") + } + + fun logAnimationStateUpdate(@SystemAnimationState animationState: Int) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { int1 = animationState }, + { "AnimationState update: ${int1.name()}" } + ) + animationState.name() + } + + private fun @receiver:SystemAnimationState Int.name() = + when (this) { + IDLE -> "IDLE" + ANIMATION_QUEUED -> "ANIMATION_QUEUED" + ANIMATING_IN -> "ANIMATING_IN" + RUNNING_CHIP_ANIM -> "RUNNING_CHIP_ANIM" + ANIMATING_OUT -> "ANIMATING_OUT" + SHOWING_PERSISTENT_DOT -> "SHOWING_PERSISTENT_DOT" + else -> "UNKNOWN_ANIMATION_STATE" + } +} + +private const val TAG = "SystemStatusAnimationSchedulerLog" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt index a67c26c06cb9..96725fc09d6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.gesture import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.dagger.SwipeUpLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject /** Log messages for [SwipeUpGestureHandler]. */ 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 877846336562..11b1053f35b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -124,6 +124,7 @@ constructor( private var showSensitiveContentForCurrentUser = false private var showSensitiveContentForManagedUser = false private var managedUserHandle: UserHandle? = null + private var mSplitShadeEnabled = false // TODO(b/202758428): refactor so that we can test color updates via region samping, similar to // how we test color updates when theme changes (See testThemeChangeUpdatesTextColor). @@ -131,6 +132,7 @@ constructor( // TODO: Move logic into SmartspaceView var stateChangeListener = object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { + (v as SmartspaceView).setSplitShadeEnabled(mSplitShadeEnabled) smartspaceViews.add(v as SmartspaceView) connectSession() @@ -221,6 +223,11 @@ constructor( execution.assertIsMainThread() smartspaceViews.forEach { it.setDozeAmount(eased) } } + + override fun onDozingChanged(isDozing: Boolean) { + execution.assertIsMainThread() + smartspaceViews.forEach { it.setDozing(isDozing) } + } } private val deviceProvisionedListener = @@ -426,6 +433,11 @@ constructor( reloadSmartspace() } + fun setSplitShadeEnabled(enabled: Boolean) { + mSplitShadeEnabled = enabled + smartspaceViews.forEach { it.setSplitShadeEnabled(enabled) } + } + /** * Requests the smartspace session for an update. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt index a3a72d92c2e4..cea2b595f595 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification import com.android.systemui.log.dagger.NotifInteractionLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.notification.collection.NotificationEntry import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt index f7679ed058c6..502e1d9ea639 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt @@ -14,7 +14,7 @@ package com.android.systemui.statusbar.notification import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.NotificationLockscreenLog import com.android.systemui.statusbar.StatusBarState import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt index 487a5f87d0bd..7809eaafe0b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.NotificationRemoteInputLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt index 39d0833c57d4..0ab348d174b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coalescer import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject class GroupCoalescerLogger @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt index 79c63e6b0db1..bd1141e79278 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt @@ -2,7 +2,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.notification.row.NotificationGuts import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt index e17ce5cff37d..496fb83c1cf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt @@ -4,7 +4,7 @@ import android.util.Log import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject private const val TAG = "HeadsUpCoordinator" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt index 1f8ec3411bcd..4c33524346eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.UnseenNotificationLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt index 6271d38f1efa..bf65043ea534 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt index 1f4861a10e75..0d9681f801f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject private const val TAG = "ShadeEventCoordinator" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt index f13ff6814df8..a8409d0c6fa0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt @@ -18,9 +18,9 @@ package com.android.systemui.statusbar.notification.collection.listbuilder import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.WARNING import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index 73227ab9f4fb..014ac549591e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -22,11 +22,11 @@ import android.service.notification.NotificationListenerService.RankingMap import android.service.notification.StatusBarNotification import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -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.LogLevel.WARNING -import com.android.systemui.log.LogLevel.WTF +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.WARNING +import com.android.systemui.log.core.LogLevel.WTF import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt index 07fd349d3786..e61f9bdda3d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.render import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection import com.android.systemui.util.Compile diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt index a880b7157708..082f308bc731 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.render import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import java.lang.RuntimeException import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt index 0b31265963ed..c6d2861a8c68 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt @@ -2,7 +2,7 @@ package com.android.systemui.statusbar.notification.interruption import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt index 5bac2a9350a5..4a823a40a272 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt @@ -20,9 +20,9 @@ import android.util.Log import com.android.systemui.log.dagger.NotificationInterruptLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.WARNING import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import com.android.systemui.util.Compile diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt index fe03b2ad6a32..0e1f66f7cbd6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.logging import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.log.dagger.NotificationRenderLog import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView 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 8af488ea443d..27510d47b5ab 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 @@ -359,9 +359,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } @Override - public long performRemoveAnimation(long duration, long delay, - float translationDirection, boolean isHeadsUpAnimation, float endLocation, - Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { + public long performRemoveAnimation(long duration, long delay, float translationDirection, + boolean isHeadsUpAnimation, Runnable onFinishedRunnable, + AnimatorListenerAdapter animationListener) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAnimation; if (mDrawingAppearAnimation) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 30747db0fb64..b34c28163abb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -2975,7 +2975,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView long delay, float translationDirection, boolean isHeadsUpAnimation, - float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { if (mMenuRow != null && mMenuRow.isMenuVisible()) { @@ -2986,7 +2985,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void onAnimationEnd(Animator animation) { ExpandableNotificationRow.super.performRemoveAnimation( duration, delay, translationDirection, isHeadsUpAnimation, - endLocation, onFinishedRunnable, animationListener); + onFinishedRunnable, animationListener); } }); anim.start(); @@ -2994,7 +2993,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } return super.performRemoveAnimation(duration, delay, translationDirection, - isHeadsUpAnimation, endLocation, onFinishedRunnable, animationListener); + isHeadsUpAnimation, onFinishedRunnable, animationListener); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index f0e15c27b7a7..f98624409e56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -367,7 +367,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro * such that the child appears to be going away to the top. 1 * Should mean the opposite. * @param isHeadsUpAnimation Is this a headsUp animation. - * @param endLocation The location where the horizonal heads up disappear animation should end. * @param onFinishedRunnable A runnable which should be run when the animation is finished. * @param animationListener An animation listener to add to the animation. * @@ -375,7 +374,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro * animation starts. */ public abstract long performRemoveAnimation(long duration, - long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, + long delay, float translationDirection, boolean isHeadsUpAnimation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt index 45be0b151870..385670080f63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.row import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt index 89338f9eeed3..4f5a04f2bdc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.row import android.view.ViewGroup import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.dagger.NotificationRenderLog import com.android.systemui.statusbar.notification.collection.NotificationEntry diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt index 684a276ed635..02627fd8f975 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.row import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index b24cec150941..0c686be0406d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -235,7 +235,7 @@ public abstract class StackScrollerDecorView extends ExpandableView { @Override public long performRemoveAnimation(long duration, long delay, - float translationDirection, boolean isHeadsUpAnimation, float endLocation, + float translationDirection, boolean isHeadsUpAnimation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { // TODO: Use duration diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt index b8f28b5a60ea..04308b47abc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt @@ -69,11 +69,14 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie canvas.clipPath(clipPath) } - - override fun performRemoveAnimation(duration: Long, delay: Long, translationDirection: Float, - isHeadsUpAnimation: Boolean, endLocation: Float, - onFinishedRunnable: Runnable?, - animationListener: AnimatorListenerAdapter?): Long { + override fun performRemoveAnimation( + duration: Long, + delay: Long, + translationDirection: Float, + isHeadsUpAnimation: Boolean, + onFinishedRunnable: Runnable?, + animationListener: AnimatorListenerAdapter? + ): Long { return 0 } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt index 6be1ef8711df..4986b632472d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.stack import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.NotificationRenderLog import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt index f9531876e30d..2da55828e30d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.stack import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.dagger.NotificationSectionLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject private const val TAG = "NotifSections" 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 7c66bbe548a5..ef7375aa690b 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 @@ -424,7 +424,8 @@ public class NotificationStackScrollLayoutController { } }; - private final NotificationSwipeHelper.NotificationCallback mNotificationCallback = + @VisibleForTesting + final NotificationSwipeHelper.NotificationCallback mNotificationCallback = new NotificationSwipeHelper.NotificationCallback() { @Override @@ -483,10 +484,11 @@ public class NotificationStackScrollLayoutController { */ public void handleChildViewDismissed(View view) { + // The View needs to clean up the Swipe states, e.g. roundness. + mView.onSwipeEnd(); if (mView.getClearAllInProgress()) { return; } - mView.onSwipeEnd(); if (view instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; if (row.isHeadsUp()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt index 9c1bd174107e..2c38b8dfca04 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt @@ -1,11 +1,11 @@ package com.android.systemui.statusbar.notification.stack import android.view.ViewGroup -import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.dagger.NotificationRenderLog import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index d73919b82c42..2742a23d5fad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -27,8 +27,6 @@ import com.android.keyguard.KeyguardSliceView; import com.android.systemui.R; import com.android.systemui.shared.clocks.AnimatableClockView; import com.android.systemui.statusbar.NotificationShelf; -import com.android.systemui.statusbar.StatusBarIconView; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; @@ -427,7 +425,7 @@ public class StackStateAnimator { } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - 0, postAnimation, null); + postAnimation, null); } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { if (mHostLayout.isFullySwipedOut(changingView)) { @@ -474,28 +472,12 @@ public class StackStateAnimator { mTmpState.initFrom(changingView); endRunnable = changingView::removeFromTransientContainer; } - float targetLocation = 0; boolean needsAnimation = true; if (changingView instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) changingView; if (row.isDismissed()) { needsAnimation = false; } - - NotificationEntry entry = row.getEntry(); - StatusBarIconView icon = entry.getIcons().getStatusBarIcon(); - final StatusBarIconView centeredIcon = entry.getIcons().getCenteredIcon(); - if (centeredIcon != null && centeredIcon.getParent() != null) { - icon = centeredIcon; - } - if (icon.getParent() != null) { - icon.getLocationOnScreen(mTmpLocation); - float iconPosition = mTmpLocation[0] - icon.getTranslationX() - + ViewState.getFinalTranslationX(icon) - + icon.getWidth() * 0.25f; - mHostLayout.getLocationOnScreen(mTmpLocation); - targetLocation = iconPosition - mTmpLocation[0]; - } } if (needsAnimation) { @@ -515,7 +497,7 @@ public class StackStateAnimator { } long removeAnimationDelay = changingView.performRemoveAnimation( ANIMATION_DURATION_HEADS_UP_DISAPPEAR, - 0, 0.0f, true /* isHeadsUpAppear */, targetLocation, + 0, 0.0f, true /* isHeadsUpAppear */, postAnimation, getGlobalAnimationFinishedListener()); mAnimationProperties.delay += removeAnimationDelay; } else if (endRunnable != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt index c7f80f311650..0b2c4863157c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt @@ -1,8 +1,8 @@ package com.android.systemui.statusbar.notification.stack -import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.dagger.NotificationRenderLog import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 4ba09e175b8b..478baf2f58d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -203,8 +203,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { int getStatusBarHeight(); - boolean isShadeDisabled(); - boolean isLaunchingActivityOverLockscreen(); void onKeyguardViewManagerStatesUpdated(); @@ -380,14 +378,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { void resendMessage(Object msg); - int getDisabled1(); - - void setDisabled1(int disabled); - - int getDisabled2(); - - void setDisabled2(int disabled); - void setLastCameraLaunchSource(int source); void setLaunchCameraOnFinishedGoingToSleep(boolean launch); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index 332be2aa957d..6431ef958239 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -111,6 +111,9 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); + private int mDisabled1; + private int mDisabled2; + @Inject CentralSurfacesCommandQueueCallbacks( CentralSurfaces centralSurfaces, @@ -256,22 +259,14 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba return; } - int state2BeforeAdjustment = state2; - state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2); - Log.d(CentralSurfaces.TAG, - mDisableFlagsLogger.getDisableFlagsString( - /* new= */ new DisableFlagsLogger.DisableState( - state1, state2BeforeAdjustment), - /* newStateAfterLocalModification= */ new DisableFlagsLogger.DisableState( - state1, state2))); - - final int old1 = mCentralSurfaces.getDisabled1(); + final int old1 = mDisabled1; final int diff1 = state1 ^ old1; - mCentralSurfaces.setDisabled1(state1); + mDisabled1 = state1; - final int old2 = mCentralSurfaces.getDisabled2(); + state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2); + final int old2 = mDisabled2; final int diff2 = state2 ^ old2; - mCentralSurfaces.setDisabled2(state2); + mDisabled2 = state2; if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) { if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 88ccae624dd0..0d3dfaeb85b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -182,7 +182,6 @@ import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.shade.CameraLauncher; -import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shade.QuickSettingsController; @@ -363,26 +362,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public int getDisabled1() { - return mDisabled1; - } - - @Override - public void setDisabled1(int disabled) { - mDisabled1 = disabled; - } - - @Override - public int getDisabled2() { - return mDisabled2; - } - - @Override - public void setDisabled2(int disabled) { - mDisabled2 = disabled; - } - - @Override public void setLastCameraLaunchSource(int source) { mLastCameraLaunchSource = source; } @@ -497,14 +476,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy; /** Controller for the Shade. */ - @VisibleForTesting - ShadeSurface mShadeSurface; + private final ShadeSurface mShadeSurface; private final ShadeLogger mShadeLogger; // settings private QSPanelController mQSPanelController; - @VisibleForTesting - QuickSettingsController mQsController; + private final QuickSettingsController mQsController; KeyguardIndicationController mKeyguardIndicationController; @@ -530,11 +507,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private CentralSurfacesComponent mCentralSurfacesComponent; - // Flags for disabling the status bar - // Two variables because the first one evidently ran out of room for new flags. - private int mDisabled1 = 0; - private int mDisabled2 = 0; - /** * This keeps track of whether we have (or haven't) registered the predictive back callback. * Since we can have visible -> visible transitions, we need to avoid @@ -729,9 +701,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { MetricsLogger metricsLogger, ShadeLogger shadeLogger, @UiBackground Executor uiBgExecutor, + ShadeSurface shadeSurface, NotificationMediaManager notificationMediaManager, NotificationLockscreenUserManager lockScreenUserManager, NotificationRemoteInputManager remoteInputManager, + QuickSettingsController quickSettingsController, UserSwitcherController userSwitcherController, BatteryController batteryController, SysuiColorExtractor colorExtractor, @@ -830,9 +804,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mMetricsLogger = metricsLogger; mShadeLogger = shadeLogger; mUiBgExecutor = uiBgExecutor; + mShadeSurface = shadeSurface; mMediaManager = notificationMediaManager; mLockscreenUserManager = lockScreenUserManager; mRemoteInputManager = remoteInputManager; + mQsController = quickSettingsController; mUserSwitcherController = userSwitcherController; mBatteryController = batteryController; mColorExtractor = colorExtractor; @@ -1636,13 +1612,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // (Right now, there's a circular dependency.) mNotificationShadeWindowController.setWindowRootView(windowRootView); mNotificationShadeWindowViewController.setupExpandedStatusBar(); - NotificationPanelViewController npvc = - mCentralSurfacesComponent.getNotificationPanelViewController(); - mShadeSurface = npvc; - mShadeController.setNotificationPanelViewController(npvc); + mShadeController.setShadeViewController(mShadeSurface); mShadeController.setNotificationShadeWindowViewController( mNotificationShadeWindowViewController); - mQsController = mCentralSurfacesComponent.getQuickSettingsController(); mBackActionInteractor.setup(mQsController, mShadeSurface); mPresenter = mCentralSurfacesComponent.getNotificationPresenter(); mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter(); @@ -1724,11 +1696,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return mStatusBarWindowController.getStatusBarHeight(); } - @Override - public boolean isShadeDisabled() { - return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0; - } - private void updateReportRejectedTouchVisibility() { if (mReportRejectedTouch == null) { return; @@ -1843,7 +1810,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void onStatusBarTrackpadEvent(MotionEvent event) { - mCentralSurfacesComponent.getNotificationPanelViewController().handleExternalTouch(event); + mShadeSurface.handleExternalTouch(event); } private void onExpandedInvisible() { @@ -2210,10 +2177,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { */ @Override public void setLockscreenUser(int newUserId) { - if (mLockscreenWallpaper != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { - mLockscreenWallpaper.setCurrentUser(newUserId); - } - mScrimController.setCurrentUser(newUserId); if (mWallpaperSupported) { mWallpaperChangedReceiver.onReceive(mContext, null); } 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 4d716c206908..680f19a79a05 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -45,7 +45,7 @@ import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.log.LogLevel; +import com.android.systemui.log.core.LogLevel; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeViewStateProvider; import com.android.systemui.statusbar.CommandQueue; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt index 5c357d7cd3ab..686efb7f1553 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt @@ -21,7 +21,7 @@ import android.view.View import com.android.internal.logging.nano.MetricsProto.MetricsEvent import com.android.systemui.log.dagger.LSShadeTransitionLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import javax.inject.Inject 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 c07b5e062d70..b2c39f7e289f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -40,12 +40,17 @@ import androidx.annotation.NonNull; import com.android.internal.util.IndentingPrintWriter; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.user.data.model.SelectedUserModel; +import com.android.systemui.user.data.model.SelectionStatus; +import com.android.systemui.user.data.repository.UserRepository; +import com.android.systemui.util.kotlin.JavaAdapter; import libcore.io.IoUtils; @@ -59,7 +64,7 @@ import javax.inject.Inject; */ @SysUISingleton public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable, - Dumpable { + Dumpable, CoreStartable { private static final String TAG = "LockscreenWallpaper"; @@ -72,6 +77,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen private final WallpaperManager mWallpaperManager; private final KeyguardUpdateMonitor mUpdateMonitor; private final Handler mH; + private final JavaAdapter mJavaAdapter; + private final UserRepository mUserRepository; private boolean mCached; private Bitmap mCache; @@ -88,6 +95,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen DumpManager dumpManager, NotificationMediaManager mediaManager, @Main Handler mainHandler, + JavaAdapter javaAdapter, + UserRepository userRepository, UserTracker userTracker) { dumpManager.registerDumpable(getClass().getSimpleName(), this); mWallpaperManager = wallpaperManager; @@ -95,6 +104,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen mUpdateMonitor = keyguardUpdateMonitor; mMediaManager = mediaManager; mH = mainHandler; + mJavaAdapter = javaAdapter; + mUserRepository = userRepository; if (iWallpaperManager != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { // Service is disabled on some devices like Automotive @@ -106,6 +117,14 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen } } + @Override + public void start() { + if (!isLockscreenLiveWallpaperEnabled()) { + mJavaAdapter.alwaysCollectFlow( + mUserRepository.getSelectedUser(), this::setSelectedUser); + } + } + public Bitmap getBitmap() { assertLockscreenLiveWallpaperNotEnabled(); @@ -169,9 +188,15 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen } } - public void setCurrentUser(int user) { + private void setSelectedUser(SelectedUserModel selectedUserModel) { assertLockscreenLiveWallpaperNotEnabled(); + if (selectedUserModel.getSelectionStatus().equals(SelectionStatus.SELECTION_IN_PROGRESS)) { + // Wait until the selection has finished before updating. + return; + } + + int user = selectedUserModel.getUserInfo().id; if (user != mCurrentUserId) { if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) { mCached = false; 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 47c4023ca8aa..c16e13cd4af3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -1476,10 +1476,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } } - public void setCurrentUser(int currentUser) { - // Don't care in the base class. - } - private void updateThemeColors() { if (mScrimBehind == null) return; int background = Utils.getColorAttr(mScrimBehind.getContext(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt index 12f023b21701..d07378e86ced 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt @@ -19,10 +19,10 @@ package com.android.systemui.statusbar.phone import android.app.PendingIntent import com.android.systemui.log.dagger.NotifInteractionLog import com.android.systemui.log.LogBuffer -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.LogLevel.WARNING +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.WARNING import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java index c3322808b2b8..604b1f5008db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java @@ -64,6 +64,7 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { private boolean mNeedsUnderflow; // Individual StatusBarIconViews draw their etc dots centered in this width private int mIconDotFrameWidth; + private boolean mQsExpansionTransitioning; private boolean mShouldRestrictIcons = true; // Used to count which states want to be visible during layout private ArrayList<StatusIconState> mLayoutStates = new ArrayList<>(); @@ -87,6 +88,10 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { super.onFinishInflate(); } + public void setQsExpansionTransitioning(boolean expansionTransitioning) { + mQsExpansionTransitioning = expansionTransitioning; + } + public void setShouldRestrictIcons(boolean should) { mShouldRestrictIcons = should; } @@ -386,6 +391,7 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { StatusIconState vs = getViewStateFromChild(child); if (vs != null) { vs.applyToView(child); + vs.qsExpansionTransitioning = mQsExpansionTransitioning; } } } @@ -420,6 +426,7 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { /// StatusBarIconView.STATE_* public int visibleState = STATE_ICON; public boolean justAdded = true; + public boolean qsExpansionTransitioning = false; // How far we are from the end of the view actually is the most relevant for animation float distanceToViewEnd = -1; @@ -462,12 +469,13 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { } icon.setVisibleState(visibleState, animateVisibility); - if (animationProperties != null) { + if (animationProperties != null && !qsExpansionTransitioning) { animateTo(view, animationProperties); } else { super.applyToView(view); } + qsExpansionTransitioning = false; justAdded = false; distanceToViewEnd = currentDistanceToEnd; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java index c618be843832..4ae460a3f0e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java @@ -21,10 +21,8 @@ import static com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.ST import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.android.systemui.scene.ui.view.WindowRootView; -import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; -import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeHeaderController; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.notification.NotificationActivityStarter; @@ -89,14 +87,6 @@ public interface CentralSurfacesComponent { NotificationShadeWindowViewController getNotificationShadeWindowViewController(); /** - * Creates a NotificationPanelViewController. - */ - NotificationPanelViewController getNotificationPanelViewController(); - - /** Creates a QuickSettingsController. */ - QuickSettingsController getQuickSettingsController(); - - /** * Creates a StatusBarHeadsUpChangeListener. */ StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 6b0746f08df0..ebdde78e4ea4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -16,28 +16,20 @@ package com.android.systemui.statusbar.phone.dagger; -import android.view.LayoutInflater; - import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shade.NotificationPanelView; -import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; -import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.NotificationIconAreaController; -import com.android.systemui.statusbar.phone.StatusBarBoundsProvider; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; -import com.android.systemui.statusbar.phone.SystemBarAttributesListener; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger; import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; @@ -49,10 +41,8 @@ import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.util.CarrierConfigTracker; import com.android.systemui.util.settings.SecureSettings; -import dagger.Binds; import dagger.Module; import dagger.Provides; -import dagger.multibindings.IntoSet; import java.util.concurrent.Executor; @@ -70,17 +60,6 @@ public abstract class StatusBarViewModule { public static final String STATUS_BAR_FRAGMENT = "status_bar_fragment"; - /** */ - @Binds - @CentralSurfacesComponent.CentralSurfacesScope - abstract ShadeViewController bindsShadeViewController( - NotificationPanelViewController notificationPanelViewController); - - @Binds - @IntoSet - abstract StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener( - SystemBarAttributesListener systemBarAttributesListener); - /** * Creates a new {@link CollapsedStatusBarFragment}. * @@ -145,17 +124,4 @@ public abstract class StatusBarViewModule { statusBarWindowStateController, keyguardUpdateMonitor); } - - /** - * Constructs a new, unattached {@link KeyguardBottomAreaView}. - * - * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it - */ - @Provides - public static KeyguardBottomAreaView providesKeyguardBottomAreaView( - NotificationPanelView npv, LayoutInflater layoutInflater) { - return (KeyguardBottomAreaView) layoutInflater.inflate(R - .layout.keyguard_bottom_area, npv, false); - } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt index f4ab408cc275..7cdb9c0a7aa8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.phone.fragment import com.android.systemui.log.dagger.CollapsedSbFragmentLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.disableflags.DisableFlagsLogger import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index 730ecded58e2..97a1bd1851d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -26,20 +26,23 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarBoundsProvider; +import com.android.systemui.statusbar.phone.SystemBarAttributesListener; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.window.StatusBarWindowController; +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.IntoSet; +import dagger.multibindings.Multibinds; + import java.util.Optional; import java.util.Set; import javax.inject.Named; -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.Multibinds; - /** Dagger module for {@link StatusBarFragmentComponent}. */ @Module public interface StatusBarFragmentModule { @@ -151,4 +154,10 @@ public interface StatusBarFragmentModule { /** */ @Multibinds Set<StatusBarBoundsProvider.BoundsChangeListener> boundsChangeListeners(); + + /** */ + @Binds + @IntoSet + StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener( + SystemBarAttributesListener systemBarAttributesListener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index b3a1c4075d87..051e88f9264d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -23,7 +23,7 @@ import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.MobileMappings import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.pipeline.dagger.MobileInputLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 74352d29cd9b..54948a4a41c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -250,15 +250,8 @@ constructor( .distinctUntilChanged() .onEach { logger.logDefaultMobileIconGroup(it) } - override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository { - if (!isValidSubId(subId)) { - throw IllegalArgumentException( - "subscriptionId $subId is not in the list of valid subscriptions" - ) - } - - return getOrCreateRepoForSubId(subId) - } + override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository = + getOrCreateRepoForSubId(subId) private fun getOrCreateRepoForSubId(subId: Int) = subIdRepositoryCache[subId] diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt index 7e0c145696c9..cea6654a48de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt @@ -21,7 +21,7 @@ import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.pipeline.dagger.MobileViewLog import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel import java.io.PrintWriter diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt index 507549b1e234..f4c572308e59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt @@ -20,7 +20,7 @@ import android.view.View import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt index cac0ae3dbab4..8a4d14e69652 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt @@ -20,7 +20,7 @@ import android.net.Network import android.net.NetworkCapabilities import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.pipeline.dagger.SharedConnectivityInputLog import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt index 328d901b541d..4b9de85915ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.pipeline.shared import android.net.Network import android.net.NetworkCapabilities import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel /** Helper object for logs that are shared between wifi and mobile. */ object LoggerHelper { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt index 058eda4400df..9a504c9534f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.pipeline.shared.data.model import android.net.NetworkCapabilities -import com.android.systemui.log.LogMessage +import com.android.systemui.log.core.LogMessage /** * A model for all of the current default connections(s). diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt index a4fbc2c93647..a57be665f105 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt @@ -96,7 +96,7 @@ constructor( networkId = DEMO_NET_ID, isValidated = validated ?: true, level = level ?: 0, - ssid = ssid, + ssid = ssid ?: DEMO_NET_SSID, // These fields below aren't supported in demo mode, since they aren't needed to satisfy // the interface. @@ -115,5 +115,6 @@ constructor( companion object { private const val DEMO_NET_ID = 1234 + private const val DEMO_NET_SSID = "Demo SSID" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt index 4a9ceacb0bd1..f244376f5a5a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt @@ -20,7 +20,7 @@ import android.net.Network import android.net.NetworkCapabilities import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog import com.android.systemui.statusbar.pipeline.shared.LoggerHelper import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt index 6ba2a81b4b13..096ad1f45841 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt @@ -22,7 +22,7 @@ import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED import com.android.internal.R import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.VERBOSE +import com.android.systemui.log.core.LogLevel.VERBOSE import com.android.systemui.log.dagger.DeviceStateAutoRotationLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt index 06ed1fd279b5..175473fcfe08 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt @@ -18,8 +18,8 @@ package com.android.systemui.statusbar.policy import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.VERBOSE +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.VERBOSE import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt index 21d03386b9e2..cac5e3290a26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt @@ -22,6 +22,13 @@ import android.app.PendingIntent import android.app.RemoteInput import android.content.Context import android.content.Intent +import android.graphics.Bitmap +import android.graphics.ImageDecoder +import android.graphics.drawable.AdaptiveIconDrawable +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.Icon import android.os.Build import android.os.Bundle import android.os.SystemClock @@ -48,7 +55,13 @@ import com.android.systemui.statusbar.policy.InflatedSmartReplyState.SuppressedA import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions import com.android.systemui.statusbar.policy.SmartReplyView.SmartButtonType import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies +import java.util.concurrent.FutureTask +import java.util.concurrent.SynchronousQueue +import java.util.concurrent.ThreadPoolExecutor +import java.util.concurrent.TimeUnit import javax.inject.Inject +import kotlin.system.measureTimeMillis + /** Returns whether we should show the smart reply view and its smart suggestions. */ fun shouldShowSmartReplyView( @@ -281,6 +294,51 @@ interface SmartActionInflater { ): Button } +private const val ICON_TASK_TIMEOUT_MS = 500L +private val iconTaskThreadPool = ThreadPoolExecutor(0, 25, 1, TimeUnit.MINUTES, SynchronousQueue()) + +private fun loadIconDrawableWithTimeout( + icon: Icon, + packageContext: Context, + targetSize: Int, +): Drawable? { + if (icon.type != Icon.TYPE_URI && icon.type != Icon.TYPE_URI_ADAPTIVE_BITMAP) { + return icon.loadDrawable(packageContext) + } + val bitmapTask = FutureTask { + val bitmap: Bitmap? + val durationMillis = measureTimeMillis { + val source = ImageDecoder.createSource(packageContext.contentResolver, icon.uri) + bitmap = ImageDecoder.decodeBitmap(source) { decoder, _, _ -> + decoder.setTargetSize(targetSize, targetSize) + decoder.allocator = ImageDecoder.ALLOCATOR_DEFAULT + } + } + if (durationMillis > ICON_TASK_TIMEOUT_MS) { + Log.w(TAG, "Loading $icon took ${durationMillis / 1000f} sec") + } + checkNotNull(bitmap) { "ImageDecoder.decodeBitmap() returned null" } + } + val bitmap = runCatching { + iconTaskThreadPool.execute(bitmapTask) + bitmapTask.get(ICON_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS) + }.getOrElse { ex -> + Log.e(TAG, "Failed to load $icon: $ex") + bitmapTask.cancel(true) + return null + } + // TODO(b/288561520): rewrite Icon so that we don't need to duplicate this logic + val bitmapDrawable = BitmapDrawable(packageContext.resources, bitmap) + val result = if (icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP) + AdaptiveIconDrawable(null, bitmapDrawable) else bitmapDrawable + if (icon.hasTint()) { + result.mutate() + result.setTintList(icon.tintList) + result.setTintBlendMode(icon.tintBlendMode) + } + return result +} + /* internal */ class SmartActionInflaterImpl @Inject constructor( private val constants: SmartReplyConstants, private val activityStarter: ActivityStarter, @@ -304,12 +362,12 @@ interface SmartActionInflater { // We received the Icon from the application - so use the Context of the application to // reference icon resources. - val iconDrawable = action.getIcon().loadDrawable(packageContext) - .apply { - val newIconSize: Int = context.resources.getDimensionPixelSize( - R.dimen.smart_action_button_icon_size) - setBounds(0, 0, newIconSize, newIconSize) - } + val newIconSize = context.resources + .getDimensionPixelSize(R.dimen.smart_action_button_icon_size) + val iconDrawable = + loadIconDrawableWithTimeout(action.getIcon(), packageContext, newIconSize) + ?: GradientDrawable() + iconDrawable.setBounds(0, 0, newIconSize, newIconSize) // Add the action icon to the Smart Action button. setCompoundDrawablesRelative(iconDrawable, null, null, null) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index b135d0d8c9dc..1c3a8850df8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -28,6 +28,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings.Global; @@ -122,7 +123,12 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { - updateZenModeConfig(); + try { + Trace.beginSection("updateZenModeConfig"); + updateZenModeConfig(); + } finally { + Trace.endSection(); + } } }; mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java index bcf3b0cbfc86..4a9921eb2d83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java @@ -240,8 +240,10 @@ public class StatusBarWindowController { Insets.of(0, safeTouchRegionHeight, 0, 0)); } lp.providedInsets = new InsetsFrameProvider[] { - new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()), - new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()), + new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()) + .setInsetsSize(Insets.of(0, height, 0, 0)), + new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()) + .setInsetsSize(Insets.of(0, height, 0, 0)), gestureInsetsProvider }; return lp; diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt index 066ac04c2727..a9d202911e93 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.temporarydisplay import android.view.View import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel /** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */ open class TemporaryViewLogger<T : TemporaryViewInfo>( diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt index d55751b9d8a0..6706873ebeb4 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.temporarydisplay.chipbar import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.temporarydisplay.TemporaryViewLogger import com.android.systemui.temporarydisplay.dagger.ChipbarLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt index dfe748afbd41..c109eb4134bb 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt @@ -18,9 +18,9 @@ package com.android.systemui.toast import com.android.systemui.log.dagger.ToastLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogMessage +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogMessage import javax.inject.Inject private const val TAG = "ToastLog" diff --git a/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt new file mode 100644 index 000000000000..cefd466374c0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.user.data.model + +import android.content.pm.UserInfo + +/** A model for the currently selected user. */ +data class SelectedUserModel( + /** Information about the user. */ + val userInfo: UserInfo, + /** The current status of the selection. */ + val selectionStatus: SelectionStatus, +) + +/** The current status of the selection. */ +enum class SelectionStatus { + /** This user has started being selected but the selection hasn't completed. */ + SELECTION_IN_PROGRESS, + /** The selection of this user has completed. */ + SELECTION_COMPLETE, +} diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 3de75ca2ed87..954765c4581d 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -33,6 +33,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.settings.UserTracker +import com.android.systemui.user.data.model.SelectedUserModel +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow @@ -69,6 +71,9 @@ interface UserRepository { /** List of all users on the device. */ val userInfos: Flow<List<UserInfo>> + /** Information about the currently-selected user, including [UserInfo] and other details. */ + val selectedUser: StateFlow<SelectedUserModel> + /** [UserInfo] of the currently-selected user. */ val selectedUserInfo: Flow<UserInfo> @@ -146,9 +151,6 @@ constructor( private val _userInfos = MutableStateFlow<List<UserInfo>?>(null) override val userInfos: Flow<List<UserInfo>> = _userInfos.filterNotNull() - private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null) - override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull() - override var mainUserId: Int = UserHandle.USER_NULL private set override var lastSelectedNonGuestUserId: Int = UserHandle.USER_NULL @@ -174,12 +176,57 @@ constructor( override var isRefreshUsersPaused: Boolean = false init { - observeSelectedUser() if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) { observeUserSwitching() } } + override val selectedUser: StateFlow<SelectedUserModel> = run { + // Some callbacks don't modify the selection status, so maintain the current value. + var currentSelectionStatus = SelectionStatus.SELECTION_COMPLETE + conflatedCallbackFlow { + fun send(selectionStatus: SelectionStatus) { + currentSelectionStatus = selectionStatus + trySendWithFailureLogging( + SelectedUserModel(tracker.userInfo, selectionStatus), + TAG, + ) + } + + val callback = + object : UserTracker.Callback { + override fun onUserChanging(newUser: Int, userContext: Context) { + send(SelectionStatus.SELECTION_IN_PROGRESS) + } + + override fun onUserChanged(newUser: Int, userContext: Context) { + send(SelectionStatus.SELECTION_COMPLETE) + } + + override fun onProfilesChanged(profiles: List<UserInfo>) { + send(currentSelectionStatus) + } + } + + tracker.addCallback(callback, mainDispatcher.asExecutor()) + send(currentSelectionStatus) + + awaitClose { tracker.removeCallback(callback) } + } + .onEach { + if (!it.userInfo.isGuest) { + lastSelectedNonGuestUserId = it.userInfo.id + } + } + .stateIn( + applicationScope, + SharingStarted.Eagerly, + initialValue = SelectedUserModel(tracker.userInfo, currentSelectionStatus) + ) + } + + override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo } + override fun refreshUsers() { applicationScope.launch { val result = withContext(backgroundDispatcher) { manager.aliveUsers } @@ -201,7 +248,7 @@ constructor( } override fun getSelectedUserInfo(): UserInfo { - return checkNotNull(_selectedUserInfo.value) + return selectedUser.value.userInfo } override fun isSimpleUserSwitcher(): Boolean { @@ -234,38 +281,6 @@ constructor( .launchIn(applicationScope) } - private fun observeSelectedUser() { - conflatedCallbackFlow { - fun send() { - trySendWithFailureLogging(tracker.userInfo, TAG) - } - - val callback = - object : UserTracker.Callback { - override fun onUserChanging(newUser: Int, userContext: Context) { - send() - } - - override fun onProfilesChanged(profiles: List<UserInfo>) { - send() - } - } - - tracker.addCallback(callback, mainDispatcher.asExecutor()) - send() - - awaitClose { tracker.removeCallback(callback) } - } - .onEach { - if (!it.isGuest) { - lastSelectedNonGuestUserId = it.id - } - - _selectedUserInfo.value = it - } - .launchIn(applicationScope) - } - private suspend fun getSettings(): UserSwitcherSettingsModel { return withContext(backgroundDispatcher) { val isSimpleUserSwitcher = diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt index 891ee0cf66d7..e32ed5fd3671 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt @@ -16,12 +16,19 @@ package com.android.systemui.util.kotlin +import android.annotation.UserHandleAware import android.annotation.WorkerThread import android.content.pm.ComponentInfo import android.content.pm.PackageManager import com.android.systemui.util.Assert +/** + * Determines whether a component is actually enabled (not just its default value). + * + * @throws IllegalArgumentException if the component is not found + */ @WorkerThread +@UserHandleAware fun PackageManager.isComponentActuallyEnabled(componentInfo: ComponentInfo): Boolean { Assert.isNotMainThread() return when (getComponentEnabledSetting(componentInfo.componentName)) { diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt index 09268007dddc..d69b10ffb210 100644 --- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.util.wakelock import android.os.PowerManager import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject class WakeLockLogger @Inject constructor(@WakeLockLog private val buffer: LogBuffer) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 7456d349a933..93622200ad46 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -476,7 +476,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); - mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 4c7e6b007f38..5144d1966222 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -334,10 +334,8 @@ public final class WMShell implements } @Override - public void setImeWindowStatus(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, - boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, IBinder token, int vis, + int backDisposition, boolean showImeSwitcher) { if (displayId == mDisplayTracker.getDefaultDisplayId() && (vis & InputMethodService.IME_VISIBLE) != 0) { oneHanded.stopOneHanded( diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 0dcd404d2fc5..231898884adf 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -21,21 +21,22 @@ import android.view.View import android.widget.TextView import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.flags.FeatureFlags -import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.log.LogBuffer import com.android.systemui.plugins.ClockAnimations import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.ClockEvents -import com.android.systemui.plugins.ClockFaceController import com.android.systemui.plugins.ClockFaceConfig +import com.android.systemui.plugins.ClockFaceController import com.android.systemui.plugins.ClockFaceEvents import com.android.systemui.plugins.ClockTickRate -import com.android.systemui.log.LogBuffer import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController @@ -64,7 +65,6 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import java.util.TimeZone import java.util.concurrent.Executor -import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @@ -122,7 +122,9 @@ class ClockEventControllerTest : SysuiTestCase() { bouncerRepository = bouncerRepository, configurationRepository = FakeConfigurationRepository(), ), - KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ).keyguardTransitionInteractor, broadcastDispatcher, batteryController, keyguardUpdateMonitor, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index b21cc6dde815..9e561ed290f7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -408,4 +408,18 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { any(ClockRegistry.ClockChangeListener.class)); verify(mClockEventController, times).registerListeners(mView); } + + @Test + public void testSplitShadeEnabledSetToSmartspaceController() { + mController.setSplitShadeEnabled(true); + verify(mSmartspaceController, times(1)).setSplitShadeEnabled(true); + verify(mSmartspaceController, times(0)).setSplitShadeEnabled(false); + } + + @Test + public void testSplitShadeDisabledSetToSmartspaceController() { + mController.setSplitShadeEnabled(false); + verify(mSmartspaceController, times(1)).setSplitShadeEnabled(false); + verify(mSmartspaceController, times(0)).setSplitShadeEnabled(true); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 5a56bafc1992..d3b41902499c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -97,7 +97,21 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { `when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button)) .thenReturn(deleteButton) `when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton) - constructViewController() + pinViewController = + KeyguardPinViewController( + keyguardPinView, + keyguardUpdateMonitor, + securityMode, + lockPatternUtils, + mKeyguardSecurityCallback, + keyguardMessageAreaControllerFactory, + mLatencyTracker, + liftToActivateListener, + mEmergencyButtonController, + falsingCollector, + postureController, + featureFlags + ) } @Test @@ -121,10 +135,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3) `when`(passwordTextView.text).thenReturn("") - constructViewController() pinViewController.startAppearAnimation() - verify(deleteButton).visibility = View.INVISIBLE verify(enterButton).visibility = View.INVISIBLE verify(passwordTextView).setUsePinShapes(true) @@ -138,10 +150,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6) `when`(passwordTextView.text).thenReturn("") - constructViewController() pinViewController.startAppearAnimation() - verify(deleteButton).visibility = View.VISIBLE verify(enterButton).visibility = View.VISIBLE verify(passwordTextView).setUsePinShapes(true) @@ -153,22 +163,4 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { pinViewController.handleAttemptLockout(0) verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt()) } - - fun constructViewController() { - pinViewController = - KeyguardPinViewController( - keyguardPinView, - keyguardUpdateMonitor, - securityMode, - lockPatternUtils, - mKeyguardSecurityCallback, - keyguardMessageAreaControllerFactory, - mLatencyTracker, - liftToActivateListener, - mEmergencyButtonController, - falsingCollector, - postureController, - featureFlags - ) - } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index e561f1f233b4..58b1edcc5511 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -62,12 +62,12 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.SideFpsController; import com.android.systemui.biometrics.SideFpsUiRequestSource; +import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.ActivityStarter; @@ -395,6 +395,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { // WHEN a request is made from the SimPin screens to show the next security method when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None); + when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true); mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish( /* authenticated= */true, TARGET_USER_ID, @@ -423,6 +424,28 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { } @Test + public void showNextSecurityScreenOrFinish_SimPin_Swipe() { + // GIVEN the current security method is SimPin + when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false); + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false); + mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin); + + // WHEN a request is made from the SimPin screens to show the next security method + when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None); + // WHEN security method is SWIPE + when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false); + mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish( + /* authenticated= */true, + TARGET_USER_ID, + /* bypassSecondaryLockScreen= */true, + SecurityMode.SimPin); + + // THEN the next security method of None will dismiss keyguard. + verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()); + } + + + @Test public void onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() { KeyguardSecurityContainer.SwipeListener registeredSwipeListener = getRegisteredSwipeListener(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index a2c632936047..512e5dc1a0d6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -17,6 +17,7 @@ package com.android.keyguard; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -155,4 +156,18 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 20f, true); verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 20f, true); } + + @Test + public void splitShadeEnabledPassedToClockSwitchController() { + mController.setSplitShadeEnabled(true); + verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(true); + verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(false); + } + + @Test + public void splitShadeDisabledPassedToClockSwitchController() { + mController.setSplitShadeEnabled(false); + verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(false); + verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(true); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 4263091c5ee8..5abab6239b1e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -3002,6 +3002,16 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void testOnSimStateChanged_HandleSimStateNotReady() { + KeyguardUpdateMonitorCallback keyguardUpdateMonitorCallback = spy( + KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback); + mKeyguardUpdateMonitor.handleSimStateChange(-1, 0, TelephonyManager.SIM_STATE_NOT_READY); + verify(keyguardUpdateMonitorCallback).onSimStateChanged(-1, 0, + TelephonyManager.SIM_STATE_NOT_READY); + } + + @Test public void onAuthEnrollmentChangesCallbacksAreNotified() { KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); ArgumentCaptor<AuthController.Callback> authCallback = ArgumentCaptor.forClass( diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index c88c4d65f412..e7e1cc94b7c8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -19,6 +19,7 @@ package com.android.keyguard; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1; import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR; +import static com.android.systemui.flags.Flags.MIGRATE_LOCK_ICON; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; @@ -143,6 +144,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(FACE_AUTH_REFACTOR, false); + mFeatureFlags.set(MIGRATE_LOCK_ICON, false); mUnderTest = new LockIconViewController( mLockIconView, mStatusBarStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 79c87cfd1f3e..796e66514afe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -96,6 +96,7 @@ import com.android.systemui.log.ScreenDecorationsLogger; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.FakeThreadFactory; @@ -139,6 +140,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { @Mock private Display mDisplay; @Mock + private CommandRegistry mCommandRegistry; + @Mock private UserTracker mUserTracker; @Mock private PrivacyDotViewController mDotViewController; @@ -231,8 +234,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { mExecutor, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")))); - mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings, - mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory, + mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings, + mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController, + mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController) { @@ -1226,8 +1230,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { mFaceScanningProviders.add(mFaceScanningDecorProvider); when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders); when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(true); - ScreenDecorations screenDecorations = new ScreenDecorations(mContext, mExecutor, - mSecureSettings, mUserTracker, mDisplayTracker, mDotViewController, + ScreenDecorations screenDecorations = new ScreenDecorations(mContext, + mSecureSettings, mCommandRegistry, mUserTracker, mDisplayTracker, + mDotViewController, mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController); screenDecorations.start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index 224875590d75..0e0d0e3f3748 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -43,11 +43,12 @@ import com.android.systemui.R import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -58,6 +59,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Rule import org.junit.Test @@ -70,6 +72,7 @@ import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit +import javax.inject.Provider import org.mockito.Mockito.`when` as whenever private const val REQUEST_ID = 2L @@ -80,6 +83,7 @@ private const val DISPLAY_HEIGHT = 1920 private const val SENSOR_WIDTH = 30 private const val SENSOR_HEIGHT = 60 +@ExperimentalCoroutinesApi @SmallTest @RoboPilotTest @RunWith(AndroidJUnit4::class) @@ -116,6 +120,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var udfpsUtils: UdfpsUtils @Mock private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate + @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels> @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams> private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true } @@ -148,6 +153,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { controllerCallback, onTouch, activityLaunchAnimator, featureFlags, primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils, udfpsKeyguardAccessibilityDelegate, + udfpsKeyguardViewModels, ) block() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 30e54474bbde..58982d13481d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -22,6 +22,7 @@ import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; import static com.android.internal.util.FunctionalUtils.ThrowingConsumer; +import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -59,6 +60,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.VibrationAttributes; import android.testing.TestableLooper.RunWithLooper; +import android.util.Pair; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; @@ -83,13 +85,14 @@ import com.android.systemui.biometrics.udfps.InteractionEvent; import com.android.systemui.biometrics.udfps.NormalizedTouchData; import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor; import com.android.systemui.biometrics.udfps.TouchProcessorResult; +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -219,6 +222,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private SecureSettings mSecureSettings; @Mock private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; + @Mock + private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels; // Capture listeners so that they can be used to send events @Captor @@ -318,7 +323,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker, mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils, mock(KeyguardFaceAuthInteractor.class), - mUdfpsKeyguardAccessibilityDelegate); + mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); @@ -1200,8 +1205,53 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void fingerDown_falsingManagerInformed() throws RemoteException { + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenAcceptFingerDownEvent(); + + // WHEN ACTION_DOWN is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + downEvent.recycle(); + + // THEN falsing manager is informed of the touch + verify(mFalsingManager).isFalseTouch(UDFPS_AUTHENTICATION); + } + + @Test public void onTouch_withNewTouchDetection_shouldCallNewFingerprintManagerPath() throws RemoteException { + final Pair<TouchProcessorResult, TouchProcessorResult> processorResultDownAndUp = + givenAcceptFingerDownEvent(); + + // WHEN ACTION_DOWN is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDownAndUp.first); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + downEvent.recycle(); + + // AND ACTION_UP is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDownAndUp.second); + MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); + mBiometricExecutor.runAllReady(); + upEvent.recycle(); + + // THEN the new FingerprintManager path is invoked. + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + } + + private Pair<TouchProcessorResult, TouchProcessorResult> givenAcceptFingerDownEvent() + throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, 0L); final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( @@ -1227,27 +1277,7 @@ public class UdfpsControllerTest extends SysuiTestCase { verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // AND ACTION_UP is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultUp); - MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); - mBiometricExecutor.runAllReady(); - upEvent.recycle(); - - // THEN the new FingerprintManager path is invoked. - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); - verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + return new Pair<>(processorResultDown, processorResultUp); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt index 263ce1a2e9f5..8f8004f1cbb8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt @@ -7,7 +7,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -54,10 +54,11 @@ class LogContextInteractorImplTest : SysuiTestCase() { LogContextInteractorImpl( testScope.backgroundScope, foldProvider, - KeyguardTransitionInteractor( - keyguardTransitionRepository, - testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + repository = keyguardTransitionRepository, + scope = testScope.backgroundScope, + ) + .keyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt index bd029a727ee3..a341ca365ada 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt @@ -17,7 +17,7 @@ package com.android.systemui.dump import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.LogcatEchoTracker /** 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 6f7c217e4f3a..666978e78f98 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -205,6 +205,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot); when(mDreamingToLockscreenTransitionViewModel.getDreamOverlayAlpha()) .thenReturn(mock(Flow.class)); + when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded()) + .thenReturn(mock(Flow.class)); mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mViewMediator, mKeyguardBypassController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt index b4bd473b8b8c..925ac30b99fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt @@ -10,7 +10,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.WakeSleepReason @@ -67,10 +67,11 @@ class ResourceTrimmerTest : SysuiTestCase() { resourceTrimmer = ResourceTrimmer( keyguardInteractor, - KeyguardTransitionInteractor( - keyguardTransitionRepository, - testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor, globalWindowManager, testScope.backgroundScope, testDispatcher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index e042564646e7..d62db5d1b890 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -49,7 +49,7 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.AuthenticationStatus import com.android.systemui.keyguard.shared.model.DetectionStatus import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus @@ -216,7 +216,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { ) keyguardTransitionRepository = FakeKeyguardTransitionRepository() val keyguardTransitionInteractor = - KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor return DeviceEntryFaceAuthRepositoryImpl( mContext, fmOverride, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index ee5c1cc31b0b..3e81cd336824 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -89,7 +89,11 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { faceAuthRepository = FakeDeviceEntryFaceAuthRepository() keyguardTransitionRepository = FakeKeyguardTransitionRepository() keyguardTransitionInteractor = - KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor underTest = SystemUIKeyguardFaceAuthInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt index e9c22f9d551d..0050d64d7505 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt @@ -292,10 +292,11 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { appContext = mContext, scope = testScope.backgroundScope, transitionInteractor = - KeyguardTransitionInteractor( - keyguardTransitionRepository, - testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = testScope.backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor, repository = keyguardRepository, logger = logger, featureFlags = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index fa4941cbb895..9e9c25eafa33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -17,9 +17,9 @@ package com.android.systemui.keyguard.domain.interactor -import com.android.systemui.RoboPilotTest import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository @@ -54,7 +54,10 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - underTest = KeyguardTransitionInteractor(repository, testScope.backgroundScope) + underTest = KeyguardTransitionInteractorFactory.create( + scope = testScope.backgroundScope, + repository = repository, + ).keyguardTransitionInteractor } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index b5590154f7f4..d01a46e06b9b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN +import com.android.keyguard.TestScopeProvider import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.flags.FakeFeatureFlags @@ -92,76 +93,97 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - testScope = TestScope() + testScope = TestScopeProvider.getTestScope() keyguardRepository = FakeKeyguardRepository() bouncerRepository = FakeKeyguardBouncerRepository() shadeRepository = FakeShadeRepository() transitionRepository = spy(FakeKeyguardTransitionRepository()) - transitionInteractor = KeyguardTransitionInteractor( - transitionRepository, testScope.backgroundScope) whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN) featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) } - fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - shadeRepository = shadeRepository, - ).apply { start() } - - fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - keyguardSecurityModel = keyguardSecurityModel, - ).apply { start() } - - fromDreamingTransitionInteractor = FromDreamingTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromAodTransitionInteractor = FromAodTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromGoneTransitionInteractor = FromGoneTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromDozingTransitionInteractor = FromDozingTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromOccludedTransitionInteractor = FromOccludedTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromAlternateBouncerTransitionInteractor = FromAlternateBouncerTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } + transitionInteractor = + KeyguardTransitionInteractorFactory.create( + scope = testScope, + repository = transitionRepository, + ) + .keyguardTransitionInteractor + + fromLockscreenTransitionInteractor = + FromLockscreenTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + shadeRepository = shadeRepository, + ) + .apply { start() } + + fromPrimaryBouncerTransitionInteractor = + FromPrimaryBouncerTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + keyguardSecurityModel = keyguardSecurityModel, + ) + .apply { start() } + + fromDreamingTransitionInteractor = + FromDreamingTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromAodTransitionInteractor = + FromAodTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromGoneTransitionInteractor = + FromGoneTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromDozingTransitionInteractor = + FromDozingTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromOccludedTransitionInteractor = + FromOccludedTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromAlternateBouncerTransitionInteractor = + FromAlternateBouncerTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt index 08e99dc6b7d0..6e7ba6dd1ecb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt @@ -46,7 +46,11 @@ class LightRevealScrimInteractorTest : SysuiTestCase() { private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository() private val keyguardTransitionInteractor = - KeyguardTransitionInteractor(fakeKeyguardTransitionRepository, TestScope().backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = fakeKeyguardTransitionRepository, + ) + .keyguardTransitionInteractor private lateinit var underTest: LightRevealScrimInteractor diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt new file mode 100644 index 000000000000..1baca2184e9b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.doze.util.BurnInHelperWrapper +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.WakeSleepReason +import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class UdfpsKeyguardInteractorTest : SysuiTestCase() { + private val burnInProgress = 1f + private val burnInYOffset = 20 + private val burnInXOffset = 10 + + private lateinit var testScope: TestScope + private lateinit var configRepository: FakeConfigurationRepository + private lateinit var bouncerRepository: KeyguardBouncerRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var fakeCommandQueue: FakeCommandQueue + private lateinit var featureFlags: FakeFeatureFlags + private lateinit var burnInInteractor: BurnInInteractor + + @Mock private lateinit var burnInHelper: BurnInHelperWrapper + + private lateinit var underTest: UdfpsKeyguardInteractor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + testScope = TestScope() + configRepository = FakeConfigurationRepository() + keyguardRepository = FakeKeyguardRepository() + bouncerRepository = FakeKeyguardBouncerRepository() + fakeCommandQueue = FakeCommandQueue() + featureFlags = + FakeFeatureFlags().apply { + set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) + set(Flags.FACE_AUTH_REFACTOR, false) + } + burnInInteractor = + BurnInInteractor( + context, + burnInHelper, + testScope.backgroundScope, + configRepository, + FakeSystemClock(), + ) + + underTest = + UdfpsKeyguardInteractor( + configRepository, + burnInInteractor, + KeyguardInteractor( + keyguardRepository, + fakeCommandQueue, + featureFlags, + bouncerRepository, + configRepository, + ), + ) + } + + @Test + fun dozeChanges_updatesUdfpsAodModel() = + testScope.runTest { + val burnInOffsets by collectLastValue(underTest.burnInOffsets) + initializeBurnInOffsets() + + // WHEN we're not dozing + setAwake() + runCurrent() + + // THEN burn in offsets are 0 + assertThat(burnInOffsets?.burnInProgress).isEqualTo(0f) + assertThat(burnInOffsets?.burnInYOffset).isEqualTo(0) + assertThat(burnInOffsets?.burnInXOffset).isEqualTo(0) + + // WHEN we're in the middle of the doze amount change + keyguardRepository.setDozeAmount(.50f) + runCurrent() + + // THEN burn in is updated (between 0 and the full offset) + assertThat(burnInOffsets?.burnInProgress).isGreaterThan(0f) + assertThat(burnInOffsets?.burnInYOffset).isGreaterThan(0) + assertThat(burnInOffsets?.burnInXOffset).isGreaterThan(0) + assertThat(burnInOffsets?.burnInProgress).isLessThan(burnInProgress) + assertThat(burnInOffsets?.burnInYOffset).isLessThan(burnInYOffset) + assertThat(burnInOffsets?.burnInXOffset).isLessThan(burnInXOffset) + + // WHEN we're fully dozing + keyguardRepository.setDozeAmount(1f) + runCurrent() + + // THEN burn in offsets are updated to final current values (for the given time) + assertThat(burnInOffsets?.burnInProgress).isEqualTo(burnInProgress) + assertThat(burnInOffsets?.burnInYOffset).isEqualTo(burnInYOffset) + assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset) + } + + private fun initializeBurnInOffsets() { + whenever(burnInHelper.burnInProgressOffset()).thenReturn(burnInProgress) + whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(true))) + .thenReturn(burnInXOffset) + whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(false))) + .thenReturn(burnInYOffset) + } + + private fun setAwake() { + keyguardRepository.setDozeAmount(0f) + burnInInteractor.dozeTimeTick() + + bouncerRepository.setAlternateVisible(false) + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + bouncerRepository.setPrimaryShow(false) + keyguardRepository.setWakefulnessModel( + WakefulnessModel( + WakefulnessState.AWAKE, + WakeSleepReason.POWER_BUTTON, + WakeSleepReason.POWER_BUTTON, + ) + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt new file mode 100644 index 000000000000..2e97208e44de --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import android.graphics.Point +import android.view.ViewGroup +import android.view.WindowManager +import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.AuthController +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.monet.utils.ArgbSubject.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Answers +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +@SmallTest +class DefaultLockscreenLayoutTest : SysuiTestCase() { + private lateinit var defaultLockscreenLayout: DefaultLockscreenLayout + private lateinit var rootView: KeyguardRootView + @Mock private lateinit var authController: AuthController + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + rootView = KeyguardRootView(context, null) + defaultLockscreenLayout = + DefaultLockscreenLayout(authController, keyguardUpdateMonitor, windowManager, context) + } + + @Test + fun testLayoutViews_KeyguardIndicationArea() { + defaultLockscreenLayout.layoutViews(rootView) + val constraint = getViewConstraint(R.id.keyguard_indication_area) + assertThat(constraint.layout.bottomToBottom).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.mWidth).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT) + assertThat(constraint.layout.mHeight).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT) + } + + @Test + fun testLayoutViews_lockIconView() { + defaultLockscreenLayout.layoutViews(rootView) + val constraint = getViewConstraint(R.id.lock_icon_view) + assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) + } + + @Test + fun testCenterLockIcon() { + defaultLockscreenLayout.centerLockIcon(Point(5, 6), 1F, 5, rootView) + val constraint = getViewConstraint(R.id.lock_icon_view) + + assertThat(constraint.layout.mWidth).isEqualTo(2) + assertThat(constraint.layout.mHeight).isEqualTo(2) + assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.topMargin).isEqualTo(5) + assertThat(constraint.layout.startMargin).isEqualTo(4) + } + + /** Get the ConstraintLayout constraint of the view. */ + private fun getViewConstraint(viewId: Int): ConstraintSet.Constraint { + val constraintSet = ConstraintSet() + constraintSet.clone(rootView) + return constraintSet.getConstraint(viewId) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt new file mode 100644 index 000000000000..145b2fdb000f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.view.layout + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.mockito.withArgCaptor +import java.io.PrintWriter +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +@SmallTest +class KeyguardLayoutManagerCommandListenerTest : SysuiTestCase() { + private lateinit var keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener + @Mock private lateinit var commandRegistry: CommandRegistry + @Mock private lateinit var keyguardLayoutManager: KeyguardLayoutManager + @Mock private lateinit var pw: PrintWriter + private lateinit var command: () -> Command + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + keyguardLayoutManagerCommandListener = + KeyguardLayoutManagerCommandListener( + commandRegistry, + keyguardLayoutManager, + ) + keyguardLayoutManagerCommandListener.start() + command = + withArgCaptor<() -> Command> { + verify(commandRegistry).registerCommand(eq("layout"), capture()) + } + } + + @Test + fun testHelp() { + command().execute(pw, listOf("help")) + verify(pw, atLeastOnce()).println(anyString()) + verify(keyguardLayoutManager, never()).transitionToLayout(anyString()) + } + + @Test + fun testBlank() { + command().execute(pw, listOf()) + verify(pw, atLeastOnce()).println(anyString()) + verify(keyguardLayoutManager, never()).transitionToLayout(anyString()) + } + + @Test + fun testValidArg() { + bindFakeIdMapToLayoutManager() + command().execute(pw, listOf("fake")) + verify(keyguardLayoutManager).transitionToLayout("fake") + } + + private fun bindFakeIdMapToLayoutManager() { + val map = mapOf("fake" to mock(LockscreenLayout::class.java)) + whenever(keyguardLayoutManager.layoutIdMap).thenReturn(map) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt new file mode 100644 index 000000000000..95b2030de923 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +@SmallTest +class KeyguardLayoutManagerTest : SysuiTestCase() { + private lateinit var keyguardLayoutManager: KeyguardLayoutManager + @Mock lateinit var configurationController: ConfigurationController + @Mock lateinit var defaultLockscreenLayout: DefaultLockscreenLayout + @Mock lateinit var keyguardRootView: KeyguardRootView + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + whenever(defaultLockscreenLayout.id).thenReturn(DEFAULT) + keyguardLayoutManager = + KeyguardLayoutManager( + configurationController, + setOf(defaultLockscreenLayout), + keyguardRootView + ) + } + + @Test + fun testDefaultLayout() { + keyguardLayoutManager.transitionToLayout(DEFAULT) + verify(defaultLockscreenLayout).layoutViews(keyguardRootView) + } + + @Test + fun testTransitionToLayout_validId() { + assertThat(keyguardLayoutManager.transitionToLayout(DEFAULT)).isTrue() + } + @Test + fun testTransitionToLayout_invalidId() { + assertThat(keyguardLayoutManager.transitionToLayout("abc")).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt index a3413466d62e..c67f53519957 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt @@ -22,8 +22,18 @@ import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED +import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.util.mockito.mock import com.google.common.collect.Range @@ -47,7 +57,12 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = DreamingToLockscreenTransitionViewModel(interactor, mock()) } @@ -60,7 +75,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.5f)) @@ -82,7 +97,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this) // Should start running here... - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.5f)) @@ -104,7 +119,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.2f)) @@ -126,7 +141,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.5f)) @@ -138,13 +153,44 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { job.cancel() } - private fun step( - value: Float, - state: TransitionState = TransitionState.RUNNING - ): TransitionStep { + @Test + fun transitionEnded() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<TransitionStep>() + + val job = underTest.transitionEnded.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(TransitionStep(DOZING, DREAMING, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DOZING, DREAMING, 1.0f, FINISHED)) + + repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 0.1f, RUNNING)) + repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 1.0f, FINISHED)) + + repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 0.5f, RUNNING)) + repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 1.0f, FINISHED)) + + repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 0.5f, RUNNING)) + repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 1.0f, CANCELED)) + + repository.sendTransitionStep(TransitionStep(DREAMING, AOD, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DREAMING, AOD, 1.0f, FINISHED)) + + assertThat(values.size).isEqualTo(3) + values.forEach { + assertThat(it.transitionState == FINISHED || it.transitionState == CANCELED) + .isTrue() + } + + job.cancel() + } + + private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep { return TransitionStep( - from = KeyguardState.DREAMING, - to = KeyguardState.LOCKSCREEN, + from = DREAMING, + to = LOCKSCREEN, value = value, transitionState = state, ownerName = "DreamingToLockscreenTransitionViewModelTest" diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt index 694539b0cbfe..75c8bff326b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class GoneToDreamingTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = GoneToDreamingTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 29886d5481b9..d02b3fccef1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -41,13 +41,12 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition @@ -210,10 +209,10 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { appContext = mContext, scope = testScope.backgroundScope, transitionInteractor = - KeyguardTransitionInteractor( - repository = FakeKeyguardTransitionRepository(), - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ) + .keyguardTransitionInteractor, repository = repository, logger = UiEventLoggerFake(), featureFlags = featureFlags, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index ea17751782c4..12fe07f0827e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = LockscreenToDreamingTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt index bf56a981fa31..83ae631ed164 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = LockscreenToOccludedTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt index 34da26ecc0bf..88603999cdd7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = OccludedToLockscreenTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt index f88b71d469cf..d8c78ebdca49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt @@ -22,7 +22,7 @@ import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.shared.model.TransitionState @@ -55,7 +55,12 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = PrimaryBouncerToGoneTransitionViewModel( interactor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt new file mode 100644 index 000000000000..436c09ca4a05 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.doze.util.BurnInHelperWrapper +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@ExperimentalCoroutinesApi +@SmallTest +@RoboPilotTest +@RunWith(AndroidJUnit4::class) +class UdfpsAodViewModelTest : SysuiTestCase() { + private val defaultPadding = 12 + private lateinit var underTest: UdfpsAodViewModel + + private lateinit var testScope: TestScope + private lateinit var configRepository: FakeConfigurationRepository + private lateinit var bouncerRepository: KeyguardBouncerRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var fakeCommandQueue: FakeCommandQueue + private lateinit var featureFlags: FakeFeatureFlags + + @Mock private lateinit var burnInHelper: BurnInHelperWrapper + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding) + testScope = TestScope() + configRepository = FakeConfigurationRepository() + keyguardRepository = FakeKeyguardRepository() + bouncerRepository = FakeKeyguardBouncerRepository() + fakeCommandQueue = FakeCommandQueue() + featureFlags = + FakeFeatureFlags().apply { + set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) + set(Flags.FACE_AUTH_REFACTOR, false) + } + + val udfpsKeyguardInteractor = + UdfpsKeyguardInteractor( + configRepository, + BurnInInteractor( + context, + burnInHelper, + testScope.backgroundScope, + configRepository, + FakeSystemClock(), + ), + KeyguardInteractor( + keyguardRepository, + fakeCommandQueue, + featureFlags, + bouncerRepository, + configRepository, + ), + ) + + underTest = + UdfpsAodViewModel( + udfpsKeyguardInteractor, + context, + ) + } + + @Test + fun alphaAndVisibleUpdates_onDozeAmountChanges() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha) + val visible by collectLastValue(underTest.isVisible) + + keyguardRepository.setDozeAmount(0f) + runCurrent() + assertThat(alpha).isEqualTo(0f) + assertThat(visible).isFalse() + + keyguardRepository.setDozeAmount(.65f) + runCurrent() + assertThat(alpha).isEqualTo(.65f) + assertThat(visible).isTrue() + + keyguardRepository.setDozeAmount(.23f) + runCurrent() + assertThat(alpha).isEqualTo(.23f) + assertThat(visible).isTrue() + + keyguardRepository.setDozeAmount(1f) + runCurrent() + assertThat(alpha).isEqualTo(1f) + assertThat(visible).isTrue() + } + + @Test + fun paddingUpdates_onScaleForResolutionChanges() = + testScope.runTest { + val padding by collectLastValue(underTest.padding) + + configRepository.setScaleForResolution(1f) + runCurrent() + assertThat(padding).isEqualTo(defaultPadding) + + configRepository.setScaleForResolution(2f) + runCurrent() + assertThat(padding).isEqualTo(defaultPadding * 2) + + configRepository.setScaleForResolution(.5f) + runCurrent() + assertThat(padding).isEqualTo((defaultPadding * .5f).toInt()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt new file mode 100644 index 000000000000..a30e2a601e9d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.doze.util.BurnInHelperWrapper +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +/** Tests UdfpsFingerprintViewModel specific flows. */ +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class UdfpsFingerprintViewModelTest : SysuiTestCase() { + private val defaultPadding = 12 + private lateinit var underTest: FingerprintViewModel + + private lateinit var testScope: TestScope + private lateinit var configRepository: FakeConfigurationRepository + private lateinit var bouncerRepository: KeyguardBouncerRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var fakeCommandQueue: FakeCommandQueue + private lateinit var featureFlags: FakeFeatureFlags + private lateinit var transitionRepository: FakeKeyguardTransitionRepository + + @Mock private lateinit var burnInHelper: BurnInHelperWrapper + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding) + testScope = TestScope() + configRepository = FakeConfigurationRepository() + keyguardRepository = FakeKeyguardRepository() + bouncerRepository = FakeKeyguardBouncerRepository() + fakeCommandQueue = FakeCommandQueue() + featureFlags = + FakeFeatureFlags().apply { + set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) + set(Flags.FACE_AUTH_REFACTOR, false) + } + bouncerRepository = FakeKeyguardBouncerRepository() + transitionRepository = FakeKeyguardTransitionRepository() + val transitionInteractor = + KeyguardTransitionInteractor( + transitionRepository, + testScope.backgroundScope, + ) + val udfpsKeyguardInteractor = + UdfpsKeyguardInteractor( + configRepository, + BurnInInteractor( + context, + burnInHelper, + testScope.backgroundScope, + configRepository, + FakeSystemClock(), + ), + KeyguardInteractor( + keyguardRepository, + fakeCommandQueue, + featureFlags, + bouncerRepository, + configRepository, + ), + ) + + underTest = + FingerprintViewModel( + context, + transitionInteractor, + udfpsKeyguardInteractor, + ) + } + + @Test + fun paddingUpdates_onScaleForResolutionChanges() = + testScope.runTest { + val padding by collectLastValue(underTest.padding) + + configRepository.setScaleForResolution(1f) + runCurrent() + assertThat(padding).isEqualTo(defaultPadding) + + configRepository.setScaleForResolution(2f) + runCurrent() + assertThat(padding).isEqualTo(defaultPadding * 2) + + configRepository.setScaleForResolution(.5f) + runCurrent() + assertThat(padding).isEqualTo((defaultPadding * .5).toInt()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt new file mode 100644 index 000000000000..d58ceee40c68 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.Utils +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.collect.Range +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations + +/** Tests UDFPS lockscreen view model transitions. */ +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class UdfpsLockscreenViewModelTest : SysuiTestCase() { + private val lockscreenColorResId = android.R.attr.textColorPrimary + private val alternateBouncerResId = com.android.internal.R.attr.materialColorOnPrimaryFixed + private val lockscreenColor = Utils.getColorAttrDefaultColor(context, lockscreenColorResId) + private val alternateBouncerColor = + Utils.getColorAttrDefaultColor(context, alternateBouncerResId) + + private lateinit var underTest: UdfpsLockscreenViewModel + private lateinit var testScope: TestScope + private lateinit var transitionRepository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testScope = TestScope() + transitionRepository = FakeKeyguardTransitionRepository() + val transitionInteractor = + KeyguardTransitionInteractor( + transitionRepository, + testScope.backgroundScope, + ) + underTest = + UdfpsLockscreenViewModel( + context, + lockscreenColorResId, + alternateBouncerResId, + transitionInteractor, + ) + } + + @Test + fun goneToAodTransition() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: gone -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "goneToAodTransition", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isFalse() + + // TransitionState.RUNNING: gone -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "goneToAodTransition", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isFalse() + + // TransitionState.FINISHED: gone -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "goneToAodTransition", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isFalse() + } + + @Test + fun lockscreenToAod() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: lockscreen -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "lockscreenToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: lockscreen -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "lockscreenToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: lockscreen -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "lockscreenToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isFalse() + } + + @Test + fun aodToLockscreen() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: AOD -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "aodToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isFalse() + + // TransitionState.RUNNING: AOD -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "aodToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: AOD -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "aodToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + } + + @Test + fun lockscreenToAlternateBouncer() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: lockscreen -> alternate bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.ALTERNATE_BOUNCER, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "lockscreenToAlternateBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: lockscreen -> alternate bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.ALTERNATE_BOUNCER, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "lockscreenToAlternateBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: lockscreen -> alternate bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.ALTERNATE_BOUNCER, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "lockscreenToAlternateBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + } + + fun alternateBouncerToPrimaryBouncer() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: alternate bouncer -> primary bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.PRIMARY_BOUNCER, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "alternateBouncerToPrimaryBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: alternate bouncer -> primary bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.PRIMARY_BOUNCER, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "alternateBouncerToPrimaryBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: alternate bouncer -> primary bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.PRIMARY_BOUNCER, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "alternateBouncerToPrimaryBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isFalse() + } + + fun alternateBouncerToAod() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: alternate bouncer -> aod + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "alternateBouncerToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: alternate bouncer -> aod + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.AOD, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "alternateBouncerToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: alternate bouncer -> aod + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "alternateBouncerToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isFalse() + } + + @Test + fun lockscreenToOccluded() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: lockscreen -> occluded + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "lockscreenToOccluded", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: lockscreen -> occluded + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "lockscreenToOccluded", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: lockscreen -> occluded + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "lockscreenToOccluded", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isFalse() + } + + @Test + fun occludedToLockscreen() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: occluded -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.OCCLUDED, + to = KeyguardState.LOCKSCREEN, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "occludedToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: occluded -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.OCCLUDED, + to = KeyguardState.LOCKSCREEN, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "occludedToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: occluded -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.OCCLUDED, + to = KeyguardState.LOCKSCREEN, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "occludedToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt index 0cf6d3da7e9c..b5eae5b03b74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt @@ -2,6 +2,7 @@ package com.android.systemui.log import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.log.core.Logger import com.google.common.truth.Truth.assertThat import java.io.PrintWriter import java.io.StringWriter @@ -32,7 +33,8 @@ class LogBufferTest : SysuiTestCase() { @Test fun log_shouldSaveLogToBuffer() { - buffer.log("Test", LogLevel.INFO, "Some test message") + val logger = Logger(buffer, "Test") + logger.i("Some test message") val dumpedString = dumpBuffer() @@ -41,8 +43,9 @@ class LogBufferTest : SysuiTestCase() { @Test fun log_shouldRotateIfLogBufferIsFull() { - buffer.log("Test", LogLevel.INFO, "This should be rotated") - buffer.log("Test", LogLevel.INFO, "New test message") + val logger = Logger(buffer, "Test") + logger.i("This should be rotated") + logger.i("New test message") val dumpedString = dumpBuffer() @@ -53,7 +56,8 @@ class LogBufferTest : SysuiTestCase() { fun dump_writesExceptionAndStacktrace() { buffer = createBuffer() val exception = createTestException("Exception message", "TestClass") - buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception) + val logger = Logger(buffer, "Test") + logger.e("Extra message", exception) val dumpedString = dumpBuffer() @@ -72,7 +76,8 @@ class LogBufferTest : SysuiTestCase() { "TestClass", cause = createTestException("The real cause!", "TestClass") ) - buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception) + val logger = Logger(buffer, "Test") + logger.e("Extra message", exception) val dumpedString = dumpBuffer() @@ -93,7 +98,8 @@ class LogBufferTest : SysuiTestCase() { ) ) exception.addSuppressed(createTestException("Second suppressed exception", "SecondClass")) - buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception) + val logger = Logger(buffer, "Test") + logger.e("Extra message", exception) val dumpedStr = dumpBuffer() diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt new file mode 100644 index 000000000000..ab19b3aeceb0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt @@ -0,0 +1,140 @@ +package com.android.systemui.log.core + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.log.LogMessageImpl +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.nullable +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.anyString +import org.mockito.Mockito.isNull +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.junit.MockitoJUnitRunner + +@SmallTest +@RunWith(MockitoJUnitRunner::class) +class LoggerTest : SysuiTestCase() { + @Mock private lateinit var buffer: MessageBuffer + private lateinit var message: LogMessage + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + whenever(buffer.obtain(any(), any(), any(), isNull())).thenAnswer { + message = LogMessageImpl.Factory.create() + return@thenAnswer message + } + } + + @Test + fun log_shouldCommitLogMessage() { + val logger = Logger(buffer, "LoggerTest") + logger.log(LogLevel.DEBUG, { "count=$int1" }) { + int1 = 1 + str1 = "test" + bool1 = true + } + + assertThat(message.int1).isEqualTo(1) + assertThat(message.str1).isEqualTo("test") + assertThat(message.bool1).isEqualTo(true) + } + + @Test + fun log_shouldUseCorrectLoggerTag() { + val logger = Logger(buffer, "LoggerTest") + logger.log(LogLevel.DEBUG, { "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(eq("LoggerTest"), any(), any(), nullable()) + } + + @Test + fun v_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.v({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.VERBOSE), any(), nullable()) + } + + @Test + fun v_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.v("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.VERBOSE), any(), nullable()) + } + + @Test + fun d_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.d({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.DEBUG), any(), nullable()) + } + + @Test + fun d_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.d("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.DEBUG), any(), nullable()) + } + + @Test + fun i_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.i({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.INFO), any(), nullable()) + } + + @Test + fun i_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.i("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.INFO), any(), nullable()) + } + + @Test + fun w_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.w({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.WARNING), any(), nullable()) + } + + @Test + fun w_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.w("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.WARNING), any(), nullable()) + } + + @Test + fun e_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.e({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.ERROR), any(), nullable()) + } + + @Test + fun e_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.e("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.ERROR), any(), nullable()) + } + + @Test + fun wtf_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.wtf({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.WTF), any(), nullable()) + } + + @Test + fun wtf_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.wtf("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.WTF), any(), nullable()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt index 12f46898ab8d..83182c5cf1b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.log.table import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.log.LogLevel import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX import com.android.systemui.log.table.TableChange.Companion.MAX_STRING_LENGTH import com.android.systemui.util.mockito.any diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt index b40ebc9bb156..91b0245be8d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt @@ -193,6 +193,17 @@ class KeyguardMediaControllerTest : SysuiTestCase() { } @Test + fun dozeWakeUpAnimationWaiting_inSplitShade_mediaIsHidden() { + val splitShadeContainer = FrameLayout(context) + keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) + keyguardMediaController.useSplitShade = true + + keyguardMediaController.isDozeWakeUpAnimationWaiting = true + + assertThat(splitShadeContainer.visibility).isEqualTo(GONE) + } + + @Test fun dozing_inSingleShade_mediaIsVisible() { val splitShadeContainer = FrameLayout(context) keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) @@ -203,6 +214,17 @@ class KeyguardMediaControllerTest : SysuiTestCase() { assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) } + @Test + fun dozeWakeUpAnimationWaiting_inSingleShade_mediaIsVisible() { + val splitShadeContainer = FrameLayout(context) + keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) + keyguardMediaController.useSplitShade = false + + keyguardMediaController.isDozeWakeUpAnimationWaiting = true + + assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) + } + private fun setDozing() { whenever(statusBarStateController.isDozing).thenReturn(true) statusBarStateListener.onDozingChanged(true) 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 7df54d44e69e..e4f89a226a34 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 @@ -291,13 +291,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase { assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1); - assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE); } @Test @@ -525,16 +525,16 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo(TEST_CUSTOM_SUBTEXT); assertThat(mViewHolder.mTwoLineTitleText.getText().toString()).isEqualTo( TEST_DEVICE_NAME_1); - assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isTrue(); + assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isFalse(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt index 9e5422470d8b..5890cbd06476 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt @@ -25,7 +25,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -97,10 +97,11 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() { multiShadeInteractor = interactor, featureFlags = featureFlags, keyguardTransitionInteractor = - KeyguardTransitionInteractor( - repository = keyguardTransitionRepository, - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor, falsingManager = falsingManager, shadeController = shadeController, ) 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 697d1a3b775c..25d494cee5e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -20,6 +20,7 @@ import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_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; @@ -363,7 +364,7 @@ public class NavigationBarTest extends SysuiTestCase { externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); defaultNavBar.setImeWindowStatus( - DEFAULT_DISPLAY, null, 0 /* vis */, BACK_DISPOSITION_DEFAULT, false); + 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 | NAVIGATION_HINT_IME_SWITCHER_SHOWN, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt index a60dad4a14fe..fe6c9b3fe7b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt @@ -15,9 +15,11 @@ package com.android.systemui.qs import android.graphics.Rect import android.testing.AndroidTestingRunner +import android.testing.TestableContext import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.testing.ViewUtils +import android.view.ContextThemeWrapper import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.accessibility.AccessibilityNodeInfo @@ -55,19 +57,24 @@ class QSPanelTest : SysuiTestCase() { private lateinit var footer: View + private val themedContext = TestableContext( + ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) + ) + @Before @Throws(Exception::class) fun setup() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) + // Apply only the values of the theme that are not defined testableLooper.runWithLooper { - qsPanel = QSPanel(context, null) + qsPanel = QSPanel(themedContext, null) qsPanel.mUsingMediaPlayer = true qsPanel.initialize(qsLogger) // QSPanel inflates a footer inside of it, mocking it here - footer = LinearLayout(context).apply { id = R.id.qs_footer } + footer = LinearLayout(themedContext).apply { id = R.id.qs_footer } qsPanel.addView(footer, MATCH_PARENT, 100) qsPanel.onFinishInflate() // Provides a parent with non-zero size for QSPanel @@ -105,12 +112,12 @@ class QSPanelTest : SysuiTestCase() { qsPanel.tileLayout?.addTile( QSPanelControllerBase.TileRecord( mock(QSTile::class.java), - QSTileViewImpl(context, QSIconViewImpl(context)) + QSTileViewImpl(themedContext, QSIconViewImpl(themedContext)) ) ) - val mediaView = FrameLayout(context) - mediaView.addView(View(context), MATCH_PARENT, 800) + val mediaView = FrameLayout(themedContext) + mediaView.addView(View(themedContext), MATCH_PARENT, 800) qsPanel.setUsingHorizontalLayout(/* horizontal */ true, mediaView, /* force */ true) qsPanel.measure( @@ -135,12 +142,12 @@ class QSPanelTest : SysuiTestCase() { qsPanel.tileLayout?.addTile( QSPanelControllerBase.TileRecord( mock(QSTile::class.java), - QSTileViewImpl(context, QSIconViewImpl(context)) + QSTileViewImpl(themedContext, QSIconViewImpl(themedContext)) ) ) - val mediaView = FrameLayout(context) - mediaView.addView(View(context), MATCH_PARENT, 800) + val mediaView = FrameLayout(themedContext) + mediaView.addView(View(themedContext), MATCH_PARENT, 800) qsPanel.setUsingHorizontalLayout(/* horizontal */ true, mediaView, /* force */ true) qsPanel.measure( @@ -161,7 +168,10 @@ class QSPanelTest : SysuiTestCase() { @Test fun testBottomPadding() { val padding = 10 - context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_bottom, padding) + themedContext.orCreateTestableResources.addOverride( + R.dimen.qs_panel_padding_bottom, + padding + ) qsPanel.updatePadding() assertThat(qsPanel.paddingBottom).isEqualTo(padding) } @@ -170,8 +180,11 @@ class QSPanelTest : SysuiTestCase() { fun testTopPadding() { val padding = 10 val paddingCombined = 100 - context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding) - context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, paddingCombined) + themedContext.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding) + themedContext.orCreateTestableResources.addOverride( + R.dimen.qs_panel_padding_top, + paddingCombined + ) qsPanel.updatePadding() assertThat(qsPanel.paddingTop).isEqualTo(paddingCombined) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java index 87892539ccfe..f55ef65a8fc1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java @@ -26,12 +26,14 @@ 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 static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.test.suitebuilder.annotation.SmallTest; import android.testing.TestableLooper; +import android.view.ContextThemeWrapper; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; @@ -56,14 +58,17 @@ public class TileLayoutTest extends SysuiTestCase { private Resources mResources; private int mLayoutSizeForOneTile; private TileLayout mTileLayout; // under test + private Context mSpyContext; + @Before public void setUp() throws Exception { - Context context = Mockito.spy(mContext); - mResources = Mockito.spy(context.getResources()); - Mockito.when(mContext.getResources()).thenReturn(mResources); + mSpyContext = Mockito.spy( + new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_QuickSettings)); + mResources = Mockito.spy(mSpyContext.getResources()); + when(mSpyContext.getResources()).thenReturn(mResources); - mTileLayout = new TileLayout(context); + mTileLayout = new TileLayout(mSpyContext); // Layout needs to leave space for the tile margins. Three times the margin size is // sufficient for any number of columns. mLayoutSizeForOneTile = @@ -73,7 +78,7 @@ public class TileLayoutTest extends SysuiTestCase { private QSPanelControllerBase.TileRecord createTileRecord() { return new QSPanelControllerBase.TileRecord( mock(QSTile.class), - spy(new QSTileViewImpl(mContext, new QSIconViewImpl(mContext)))); + spy(new QSTileViewImpl(mSpyContext, new QSIconViewImpl(mSpyContext)))); } @Test @@ -161,7 +166,7 @@ public class TileLayoutTest extends SysuiTestCase { .layout(left2.capture(), top2.capture(), right2.capture(), bottom2.capture()); // We assume two tiles will always fit side-by-side. - assertTrue(mContext.getResources().getInteger(R.integer.quick_settings_num_columns) > 1); + assertTrue(mSpyContext.getResources().getInteger(R.integer.quick_settings_num_columns) > 1); // left <= right, top <= bottom assertTrue(left1.getValue() <= right1.getValue()); @@ -218,16 +223,16 @@ public class TileLayoutTest extends SysuiTestCase { @Test public void resourcesChanged_updateResources_returnsTrue() { - Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1); + when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1); mTileLayout.updateResources(); // setup with 1 - Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2); + when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2); assertEquals(true, mTileLayout.updateResources()); } @Test public void resourcesSame_updateResources_returnsFalse() { - Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1); + when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1); mTileLayout.updateResources(); // setup with 1 assertEquals(false, mTileLayout.updateResources()); @@ -250,7 +255,7 @@ public class TileLayoutTest extends SysuiTestCase { QSPanelControllerBase.TileRecord tileRecord = createTileRecord(); mTileLayout.addTile(tileRecord); - FakeTileView tileView = new FakeTileView(mContext); + FakeTileView tileView = new FakeTileView(mSpyContext); QSTile.State state = new QSTile.State(); state.label = "TEST LABEL"; state.secondaryLabel = "TEST SECONDARY LABEL"; @@ -276,9 +281,10 @@ public class TileLayoutTest extends SysuiTestCase { } private void changeFontScaling(float scale) { - Configuration configuration = new Configuration(mContext.getResources().getConfiguration()); + Configuration configuration = + new Configuration(mSpyContext.getResources().getConfiguration()); configuration.fontScale = scale; // updateConfiguration could help update on both resource configuration and displayMetrics - mContext.getResources().updateConfiguration(configuration, null, null); + mSpyContext.getResources().updateConfiguration(configuration, null, null); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index 2cc6709d0f37..d647d6add512 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt @@ -21,6 +21,7 @@ import android.os.UserManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import android.view.ContextThemeWrapper import androidx.test.filters.SmallTest import com.android.settingslib.Utils import com.android.settingslib.drawable.UserIconDrawable @@ -63,6 +64,8 @@ class FooterActionsViewModelTest : SysuiTestCase() { private val testScope = TestScope() private lateinit var utils: FooterActionsTestUtils + private val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) + @Before fun setUp() { utils = FooterActionsTestUtils(context, TestableLooper.get(this), testScope.testScheduler) @@ -84,8 +87,14 @@ class FooterActionsViewModelTest : SysuiTestCase() { ContentDescription.Resource(R.string.accessibility_quick_settings_settings) ) ) - assertThat(settings.backgroundColor).isEqualTo(R.attr.offStateColor) - assertThat(settings.iconTint).isNull() + assertThat(settings.backgroundColor).isEqualTo(R.attr.shadeInactive) + assertThat(settings.iconTint) + .isEqualTo( + Utils.getColorAttrDefaultColor( + themedContext, + R.attr.onShadeInactiveVariant, + ) + ) } @Test @@ -105,12 +114,12 @@ class FooterActionsViewModelTest : SysuiTestCase() { ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu) ) ) - assertThat(power.backgroundColor).isEqualTo(com.android.internal.R.attr.colorAccent) + assertThat(power.backgroundColor).isEqualTo(R.attr.shadeActive) assertThat(power.iconTint) .isEqualTo( Utils.getColorAttrDefaultColor( - context, - com.android.internal.R.attr.textColorOnAccent, + themedContext, + R.attr.onShadeActive, ), ) } @@ -170,7 +179,7 @@ class FooterActionsViewModelTest : SysuiTestCase() { assertThat(userSwitcher).isNotNull() assertThat(userSwitcher!!.icon) .isEqualTo(Icon.Loaded(picture, ContentDescription.Loaded("Signed in as foo"))) - assertThat(userSwitcher.backgroundColor).isEqualTo(R.attr.offStateColor) + assertThat(userSwitcher.backgroundColor).isEqualTo(R.attr.shadeInactive) // Change the current user name. userSwitcherControllerWrapper.currentUserName = "bar" diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt index 18f3837a7d36..dc0fae53f0c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -75,6 +76,9 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) + whenever(context.createContextAsUser(any(), anyInt())).thenReturn(context) + whenever(context.packageManager).thenReturn(packageManager) + // Use the default value set in the ServiceInfo whenever(packageManager.getComponentEnabledSetting(any())) .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) @@ -86,7 +90,6 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() { underTest = InstalledTilesComponentRepositoryImpl( context, - packageManager, testDispatcher, ) } @@ -224,6 +227,52 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() { assertThat(componentNames).isEmpty() } + @Test + fun packageOnlyInSecondaryUser_noException() = + testScope.runTest { + val userId = 10 + val secondaryUserContext: Context = mock() + whenever(context.userId).thenReturn(0) // System context + whenever(context.createContextAsUser(eq(UserHandle.of(userId)), anyInt())) + .thenReturn(secondaryUserContext) + + val secondaryUserPackageManager: PackageManager = mock() + whenever(secondaryUserContext.packageManager).thenReturn(secondaryUserPackageManager) + + // Use the default value set in the ServiceInfo + whenever(secondaryUserPackageManager.getComponentEnabledSetting(any())) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) + // System User package manager throws exception if the component doesn't exist for that + // user + whenever(packageManager.getComponentEnabledSetting(TEST_COMPONENT)) + .thenThrow(IllegalArgumentException()) // The package is not in the system user + + val resolveInfo = + ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true) + // Both package manager should return the same (because the query is for the secondary + // user) + whenever( + secondaryUserPackageManager.queryIntentServicesAsUser( + matchIntent(), + matchFlags(), + eq(userId) + ) + ) + .thenReturn(listOf(resolveInfo)) + whenever( + packageManager.queryIntentServicesAsUser( + matchIntent(), + matchFlags(), + eq(userId) + ) + ) + .thenReturn(listOf(resolveInfo)) + + val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId)) + + assertThat(componentNames).containsExactly(TEST_COMPONENT) + } + private fun getRegisteredReceiver(): BroadcastReceiver { verify(context) .registerReceiverAsUser( diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java index 962b53737274..22b1c7b58ab3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java @@ -204,6 +204,19 @@ public class QSTileImplTest extends SysuiTestCase { } @Test + public void testLongClick_falsing() { + mFalsingManager.setFalseLongTap(true); + mTile.longClick(null /* view */); + mTestableLooper.processAllMessages(); + assertThat(mTile.mLongClicked).isFalse(); + + mFalsingManager.setFalseLongTap(false); + mTile.longClick(null /* view */); + mTestableLooper.processAllMessages(); + assertThat(mTile.mLongClicked).isTrue(); + } + + @Test public void testSecondaryClick_Metrics() { mTile.secondaryClick(null /* view */); verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK))); @@ -518,6 +531,7 @@ public class QSTileImplTest extends SysuiTestCase { } private static class TileImpl extends QSTileImpl<QSTile.BooleanState> { boolean mClicked; + boolean mLongClicked; int mRefreshes = 0; protected TileImpl( @@ -551,6 +565,11 @@ public class QSTileImplTest extends SysuiTestCase { } @Override + protected void handleLongClick(@Nullable View view) { + mLongClicked = true; + } + + @Override protected void handleUpdateState(BooleanState state, Object arg) { mRefreshes++; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt index 28aeba461c50..3c667725c78a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt @@ -22,6 +22,7 @@ import android.service.quicksettings.Tile import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.text.TextUtils +import android.view.ContextThemeWrapper import android.view.View import android.view.accessibility.AccessibilityNodeInfo import android.widget.TextView @@ -386,7 +387,11 @@ class QSTileViewImplTest : SysuiTestCase() { context: Context, icon: QSIconView, collapsed: Boolean - ) : QSTileViewImpl(context, icon, collapsed) { + ) : QSTileViewImpl( + ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings), + icon, + collapsed + ) { fun changeState(state: QSTile.State) { handleStateChanged(state) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt index f0e4e3adda7c..77a443666442 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt @@ -25,6 +25,7 @@ import android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS import android.provider.Settings.Global.ZEN_MODE_OFF import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.view.ContextThemeWrapper import android.view.View import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger @@ -110,7 +111,9 @@ class DndTileTest : SysuiTestCase() { whenever(qsHost.userId).thenReturn(DEFAULT_USER) - val wrappedContext = object : ContextWrapper(context) { + val wrappedContext = object : ContextWrapper( + ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) + ) { override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences { return sharedPreferences } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index ef7c7bc0844d..9188293dc751 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -80,6 +80,7 @@ import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.common.ui.view.LongPressHandlingView; @@ -91,7 +92,6 @@ import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewConfigurator; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; @@ -629,7 +629,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mHeadsUpManager); mNotificationPanelViewController.setTrackingStartedListener(() -> {}); mNotificationPanelViewController.setOpenCloseListener( - new NotificationPanelViewController.OpenCloseListener() { + new OpenCloseListener() { @Override public void onClosingFinished() {} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 5802eb3d9618..eb4ae1a743ef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -514,6 +514,17 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + public void keyguardStatusView_willPlayDelayedDoze_notifiesKeyguardMediaController() { + when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); + mStatusBarStateController.setState(KEYGUARD); + enableSplitShade(/* enabled= */ true); + + mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true); + + verify(mKeyguardMediaController).setDozeWakeUpAnimationWaiting(true); + } + + @Test public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); mStatusBarStateController.setState(KEYGUARD); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 2a9b403cb2e6..5fb3a7955b5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -28,6 +28,11 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.back.domain.interactor.BackActionInteractor +import com.android.systemui.bouncer.data.factory.BouncerMessageFactory +import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository +import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor +import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil +import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.dock.DockManager @@ -35,14 +40,9 @@ import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.bouncer.data.factory.BouncerMessageFactory -import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor -import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil -import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.log.BouncerLogger import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy @@ -80,9 +80,9 @@ import org.mockito.Mockito.anyFloat import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations import java.util.Optional +import org.mockito.Mockito.`when` as whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -198,10 +198,9 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { multiShadeInteractor = multiShadeInteractor, featureFlags = featureFlags, keyguardTransitionInteractor = - KeyguardTransitionInteractor( - repository = FakeKeyguardTransitionRepository(), - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ).keyguardTransitionInteractor, falsingManager = FalsingManagerFake(), shadeController = shadeController, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 252a03bb07d2..544137e95779 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -40,8 +40,8 @@ import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.log.BouncerLogger import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy @@ -211,10 +211,10 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { multiShadeInteractor = multiShadeInteractor, featureFlags = featureFlags, keyguardTransitionInteractor = - KeyguardTransitionInteractor( - repository = FakeKeyguardTransitionRepository(), - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ) + .keyguardTransitionInteractor, falsingManager = FalsingManagerFake(), shadeController = shadeController, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index c72f4e77d4eb..a2c291281fdf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -169,6 +169,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true); mShadeInteractor = new ShadeInteractor( + mTestScope.getBackgroundScope(), mDisableFlagsRepository, mKeyguardRepository, new FakeUserSetupRepository(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt index 00a056708f07..729c4a9145c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt @@ -56,7 +56,7 @@ class ShadeControllerImplTest : SysuiTestCase() { @Mock private lateinit var windowManager: WindowManager @Mock private lateinit var assistManager: AssistManager @Mock private lateinit var gutsManager: NotificationGutsManager - @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController + @Mock private lateinit var shadeViewController: ShadeViewController @Mock private lateinit var nswvc: NotificationShadeWindowViewController @Mock private lateinit var display: Display @@ -82,7 +82,7 @@ class ShadeControllerImplTest : SysuiTestCase() { Lazy { gutsManager }, ) shadeController.setNotificationShadeWindowViewController(nswvc) - shadeController.setNotificationPanelViewController(notificationPanelViewController) + shadeController.setShadeViewController(shadeViewController) } @Test @@ -91,9 +91,9 @@ class ShadeControllerImplTest : SysuiTestCase() { // Trying to open it does nothing. shadeController.animateExpandShade() - verify(notificationPanelViewController, never()).expandToNotifications() + verify(shadeViewController, never()).expandToNotifications() shadeController.animateExpandQs() - verify(notificationPanelViewController, never()).expand(ArgumentMatchers.anyBoolean()) + verify(shadeViewController, never()).expand(ArgumentMatchers.anyBoolean()) } @Test @@ -102,15 +102,15 @@ class ShadeControllerImplTest : SysuiTestCase() { // Can now be opened. shadeController.animateExpandShade() - verify(notificationPanelViewController).expandToNotifications() + verify(shadeViewController).expandToNotifications() shadeController.animateExpandQs() - verify(notificationPanelViewController).expandToQs() + verify(shadeViewController).expandToQs() } @Test fun cancelExpansionAndCollapseShade_callsCancelCurrentTouch() { // GIVEN the shade is tracking a touch - whenever(notificationPanelViewController.isTracking).thenReturn(true) + whenever(shadeViewController.isTracking).thenReturn(true) // WHEN cancelExpansionAndCollapseShade is called shadeController.cancelExpansionAndCollapseShade() @@ -122,7 +122,7 @@ class ShadeControllerImplTest : SysuiTestCase() { @Test fun cancelExpansionAndCollapseShade_doesNotCallAnimateCollapseShade_whenCollapsed() { // GIVEN the shade is tracking a touch - whenever(notificationPanelViewController.isTracking).thenReturn(false) + whenever(shadeViewController.isTracking).thenReturn(false) // WHEN cancelExpansionAndCollapseShade is called shadeController.cancelExpansionAndCollapseShade() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt index 2da2e9238d0a..f542ab099517 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt @@ -237,11 +237,22 @@ class ShadeHeaderControllerTest : SysuiTestCase() { whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false) makeShadeVisible() + shadeHeaderController.qsExpandedFraction = 1.0f verify(statusIcons).addIgnoredSlots(carrierIconSlots) } @Test + fun dualCarrier_enablesCarrierIconsInStatusIcons_qsExpanded() { + whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false) + + makeShadeVisible() + shadeHeaderController.qsExpandedFraction = 0.0f + + verify(statusIcons, times(2)).removeIgnoredSlots(carrierIconSlots) + } + + @Test fun disableQS_notDisabled_visible() { makeShadeVisible() shadeHeaderController.disable(0, 0, false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java index 44613103a5b2..dae9c975b997 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java @@ -20,8 +20,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.content.Context; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; @@ -48,7 +50,9 @@ public class ShadeCarrierTest extends SysuiTestCase { @Before public void setUp() throws Exception { mTestableLooper = TestableLooper.get(this); - LayoutInflater inflater = LayoutInflater.from(mContext); + Context themedContext = + new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_QuickSettings); + LayoutInflater inflater = LayoutInflater.from(themedContext); mContext.ensureTestableResources(); mTestableLooper.runWithLooper(() -> mShadeCarrier = (ShadeCarrier) inflater.inflate(R.layout.shade_carrier, null)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index 7392a94a04c2..3ea8f5412b40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt @@ -84,6 +84,7 @@ class ShadeInteractorTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) featureFlags.set(Flags.FACE_AUTH_REFACTOR, false) + featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val refreshUsersScheduler = RefreshUsersScheduler( @@ -117,6 +118,7 @@ class ShadeInteractorTest : SysuiTestCase() { ) underTest = ShadeInteractor( + testScope.backgroundScope, disableFlagsRepository, keyguardRepository, userSetupRepository, @@ -126,6 +128,20 @@ class ShadeInteractorTest : SysuiTestCase() { } @Test + fun isShadeEnabled_matchesDisableFlagsRepo() = + testScope.runTest { + val actual by collectLastValue(underTest.isShadeEnabled) + + disableFlagsRepository.disableFlags.value = + DisableFlagsModel(disable2 = DISABLE2_NOTIFICATION_SHADE) + assertThat(actual).isFalse() + + disableFlagsRepository.disableFlags.value = DisableFlagsModel(disable2 = DISABLE2_NONE) + + assertThat(actual).isTrue() + } + + @Test fun isExpandToQsEnabled_deviceNotProvisioned_false() = testScope.runTest { whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false) 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 385d556092a6..1643e174ee13 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -16,6 +16,7 @@ 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.WindowInsetsController.BEHAVIOR_DEFAULT; @@ -200,7 +201,7 @@ public class CommandQueueTest extends SysuiTestCase { mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, null, 1, 2, true); waitForIdleSync(); - verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(null), eq(0), + 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/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 21e0f68cff2d..ff2f1065049b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.statusbar +import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper @@ -11,6 +12,7 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.media.controls.ui.MediaHierarchyManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager @@ -19,6 +21,9 @@ import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel +import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.stack.AmbientState @@ -28,8 +33,13 @@ import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.LSShadeTransitionLogger import com.android.systemui.statusbar.phone.ScrimController +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.util.mockito.mock +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent import org.junit.After import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull @@ -60,8 +70,11 @@ private fun <T> anyObject(): T { @SmallTest @RunWithLooper(setAsMainLooper = true) @RunWith(AndroidTestingRunner::class) +@OptIn(ExperimentalCoroutinesApi::class) class LockscreenShadeTransitionControllerTest : SysuiTestCase() { + private val testScope = TestScope(StandardTestDispatcher()) + lateinit var transitionController: LockscreenShadeTransitionController lateinit var row: ExpandableNotificationRow @Mock lateinit var statusbarStateController: SysuiStatusBarStateController @@ -87,6 +100,15 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Mock lateinit var qsTransitionController: LockscreenShadeQsTransitionController @Mock lateinit var activityStarter: ActivityStarter @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback + private val disableFlagsRepository = FakeDisableFlagsRepository() + private val shadeInteractor = ShadeInteractor( + testScope.backgroundScope, + disableFlagsRepository, + keyguardRepository = FakeKeyguardRepository(), + userSetupRepository = FakeUserSetupRepository(), + deviceProvisionedController = mock(), + userInteractor = mock(), + ) private val powerInteractor = PowerInteractor( FakePowerRepository(), FalsingCollectorFake(), @@ -99,6 +121,10 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Before fun setup() { + // By default, have the shade enabled + disableFlagsRepository.disableFlags.value = DisableFlagsModel() + testScope.runCurrent() + val helper = NotificationTestHelper( mContext, mDependency, @@ -139,6 +165,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { qsTransitionControllerFactory = { qsTransitionController }, activityStarter = activityStarter, shadeRepository = FakeShadeRepository(), + shadeInteractor = shadeInteractor, powerInteractor = powerInteractor, ) transitionController.addCallback(transitionControllerCallback) @@ -214,7 +241,10 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Test fun testDontGoWhenShadeDisabled() { - whenever(mCentralSurfaces.isShadeDisabled).thenReturn(true) + disableFlagsRepository.disableFlags.value = DisableFlagsModel( + disable2 = DISABLE2_NOTIFICATION_SHADE, + ) + testScope.runCurrent() transitionController.goToLockedShade(null) verify(statusbarStateController, never()).setState(anyInt()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt new file mode 100644 index 000000000000..cfbe8e36537d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.commandline + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import org.junit.Assert.assertFalse +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue +import org.junit.Test + +@SmallTest +class CommandParserTest : SysuiTestCase() { + private val parser = CommandParser() + + @Test + fun registerToken_cannotReuseNames() { + parser.flag("-f") + assertThrows(IllegalArgumentException::class.java) { parser.flag("-f") } + } + + @Test + fun unknownToken_throws() { + assertThrows(ArgParseError::class.java) { parser.parse(listOf("unknown-token")) } + } + + @Test + fun parseSingleFlag_present() { + val flag by parser.flag("-f") + parser.parse(listOf("-f")) + assertTrue(flag) + } + + @Test + fun parseSingleFlag_notPresent() { + val flag by parser.flag("-f") + parser.parse(listOf()) + assertFalse(flag) + } + + @Test + fun parseSingleOptionalParam_present() { + val param by parser.param("-p", valueParser = Type.Int) + parser.parse(listOf("-p", "123")) + assertThat(param).isEqualTo(123) + } + + @Test + fun parseSingleOptionalParam_notPresent() { + val param by parser.param("-p", valueParser = Type.Int) + parser.parse(listOf()) + assertThat(param).isNull() + } + + @Test + fun parseSingleOptionalParam_missingArg_throws() { + val param by parser.param("-p", valueParser = Type.Int) + assertThrows(ArgParseError::class.java) { parser.parse(listOf("-p")) } + } + + @Test + fun parseSingleRequiredParam_present() { + val param by parser.require(parser.param("-p", valueParser = Type.Int)) + parser.parse(listOf("-p", "123")) + assertThat(param).isEqualTo(123) + } + + @Test + fun parseSingleRequiredParam_notPresent_failsValidation() { + val param by parser.require(parser.param("-p", valueParser = Type.Int)) + assertFalse(parser.parse(listOf())) + } + + @Test + fun parseSingleRequiredParam_missingArg_throws() { + val param by parser.require(parser.param("-p", valueParser = Type.Int)) + assertThrows(ArgParseError::class.java) { parser.parse(listOf("-p")) } + } + + @Test + fun parseAsSubCommand_singleFlag_present() { + val flag by parser.flag("-f") + val args = listOf("-f").listIterator() + parser.parseAsSubCommand(args) + + assertTrue(flag) + } + + @Test + fun parseAsSubCommand_singleFlag_notPresent() { + val flag by parser.flag("-f") + val args = listOf("--other-flag").listIterator() + parser.parseAsSubCommand(args) + + assertFalse(flag) + } + + @Test + fun parseAsSubCommand_singleOptionalParam_present() { + val param by parser.param("-p", valueParser = Type.Int) + parser.parseAsSubCommand(listOf("-p", "123", "--other-arg", "321").listIterator()) + assertThat(param).isEqualTo(123) + } + + @Test + fun parseAsSubCommand_singleOptionalParam_notPresent() { + val param by parser.param("-p", valueParser = Type.Int) + parser.parseAsSubCommand(listOf("--other-arg", "321").listIterator()) + assertThat(param).isNull() + } + + @Test + fun parseAsSubCommand_singleRequiredParam_present() { + val param by parser.require(parser.param("-p", valueParser = Type.Int)) + parser.parseAsSubCommand(listOf("-p", "123", "--other-arg", "321").listIterator()) + assertThat(param).isEqualTo(123) + } + + @Test + fun parseAsSubCommand_singleRequiredParam_notPresent() { + parser.require(parser.param("-p", valueParser = Type.Int)) + assertFalse(parser.parseAsSubCommand(listOf("--other-arg", "321").listIterator())) + } + + @Test + fun parseCommandWithSubCommand_required_provided() { + val topLevelFlag by parser.flag("flag", shortName = "-f") + + val cmd = + object : ParseableCommand("test") { + val flag by flag("flag1") + override fun execute(pw: PrintWriter) {} + } + + parser.require(parser.subCommand(cmd)) + parser.parse(listOf("-f", "test", "--flag1")) + + assertTrue(topLevelFlag) + assertThat(cmd).isNotNull() + assertTrue(cmd.flag) + } + + @Test + fun parseCommandWithSubCommand_required_notProvided() { + val topLevelFlag by parser.flag("-f") + + val cmd = + object : ParseableCommand("test") { + val flag by parser.flag("flag1") + override fun execute(pw: PrintWriter) {} + } + + parser.require(parser.subCommand(cmd)) + + assertFalse(parser.parse(listOf("-f"))) + } + + @Test + fun flag_requiredParam_optionalParam_allProvided_failsValidation() { + val flag by parser.flag("-f") + val optionalParam by parser.param("-p", valueParser = Type.Int) + val requiredParam by parser.require(parser.param("-p2", valueParser = Type.Boolean)) + + parser.parse( + listOf( + "-f", + "-p", + "123", + "-p2", + "false", + ) + ) + + assertTrue(flag) + assertThat(optionalParam).isEqualTo(123) + assertFalse(requiredParam) + } + + @Test + fun flag_requiredParam_optionalParam_optionalExcluded() { + val flag by parser.flag("-f") + val optionalParam by parser.param("-p", valueParser = Type.Int) + val requiredParam by parser.require(parser.param("-p2", valueParser = Type.Boolean)) + + parser.parse( + listOf( + "-p2", + "true", + ) + ) + + assertFalse(flag) + assertThat(optionalParam).isNull() + assertTrue(requiredParam) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt new file mode 100644 index 000000000000..e391d6b11cd6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt @@ -0,0 +1,55 @@ +package com.android.systemui.statusbar.commandline + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertFalse +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue +import org.junit.Test + +@SmallTest +class ParametersTest : SysuiTestCase() { + @Test + fun singleArgOptional_returnsNullBeforeParse() { + val optional by SingleArgParamOptional(longName = "longName", valueParser = Type.Int) + assertThat(optional).isNull() + } + + @Test + fun singleArgOptional_returnsParsedValue() { + val param = SingleArgParamOptional(longName = "longName", valueParser = Type.Int) + param.parseArgsFromIter(listOf("3").listIterator()) + val optional by param + assertThat(optional).isEqualTo(3) + } + + @Test + fun singleArgRequired_throwsBeforeParse() { + val req by SingleArgParam(longName = "param", valueParser = Type.Boolean) + assertThrows(IllegalStateException::class.java) { req } + } + + @Test + fun singleArgRequired_returnsParsedValue() { + val param = SingleArgParam(longName = "param", valueParser = Type.Boolean) + param.parseArgsFromIter(listOf("true").listIterator()) + val req by param + assertTrue(req) + } + + @Test + fun param_handledAfterParse() { + val optParam = SingleArgParamOptional(longName = "string1", valueParser = Type.String) + val reqParam = SingleArgParam(longName = "string2", valueParser = Type.Float) + + assertFalse(optParam.handled) + assertFalse(reqParam.handled) + + optParam.parseArgsFromIter(listOf("test").listIterator()) + reqParam.parseArgsFromIter(listOf("1.23").listIterator()) + + assertTrue(optParam.handled) + assertTrue(reqParam.handled) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt new file mode 100644 index 000000000000..86548d079003 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.commandline + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +class ParseableCommandTest : SysuiTestCase() { + @Mock private lateinit var pw: PrintWriter + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + } + + /** + * A little change-detector-y, but this is just a general assertion that building up a command + * parser via its wrapper works as expected. + */ + @Test + fun testFactoryMethods() { + val mySubCommand = + object : ParseableCommand("subCommand") { + val flag by flag("flag") + override fun execute(pw: PrintWriter) {} + } + + val mySubCommand2 = + object : ParseableCommand("subCommand2") { + val flag by flag("flag") + override fun execute(pw: PrintWriter) {} + } + + // Verify that the underlying parser contains the correct types + val myCommand = + object : ParseableCommand("testName") { + val flag by flag("flag", shortName = "f") + val requiredParam by + param(longName = "required-param", shortName = "r", valueParser = Type.String) + .required() + val optionalParam by + param(longName = "optional-param", shortName = "o", valueParser = Type.Boolean) + val optionalSubCommand by subCommand(mySubCommand) + val requiredSubCommand by subCommand(mySubCommand2).required() + + override fun execute(pw: PrintWriter) {} + } + + val flags = myCommand.parser.flags + val params = myCommand.parser.params + val subCommands = myCommand.parser.subCommands + + assertThat(flags).hasSize(2) + assertThat(flags[0]).isInstanceOf(Flag::class.java) + assertThat(flags[1]).isInstanceOf(Flag::class.java) + + assertThat(params).hasSize(2) + val req = params.filter { it is SingleArgParam<*> } + val opt = params.filter { it is SingleArgParamOptional<*> } + assertThat(req).hasSize(1) + assertThat(opt).hasSize(1) + + val reqSub = subCommands.filter { it is RequiredSubCommand<*> } + val optSub = subCommands.filter { it is OptionalSubCommand<*> } + assertThat(reqSub).hasSize(1) + assertThat(optSub).hasSize(1) + } + + @Test + fun factoryMethods_enforceShortNameRules() { + // Short names MUST be one character long + assertThrows(IllegalArgumentException::class.java) { + val myCommand = + object : ParseableCommand("test-command") { + val flag by flag("longName", "invalidShortName") + + override fun execute(pw: PrintWriter) {} + } + } + + assertThrows(IllegalArgumentException::class.java) { + val myCommand = + object : ParseableCommand("test-command") { + val param by param("longName", "invalidShortName", valueParser = Type.String) + + override fun execute(pw: PrintWriter) {} + } + } + } + + @Test + fun factoryMethods_enforceLongNames_notPrefixed() { + // Long names must not start with "-", since they will be added + assertThrows(IllegalArgumentException::class.java) { + val myCommand = + object : ParseableCommand("test-command") { + val flag by flag("--invalid") + + override fun execute(pw: PrintWriter) {} + } + } + + assertThrows(IllegalArgumentException::class.java) { + val myCommand = + object : ParseableCommand("test-command") { + val param by param("-invalid", valueParser = Type.String) + + override fun execute(pw: PrintWriter) {} + } + } + } + + @Test + fun executeDoesNotPropagateExceptions() { + val cmd = + object : ParseableCommand("test-command") { + val flag by flag("flag") + override fun execute(pw: PrintWriter) {} + } + + val throwingCommand = listOf("unknown-token") + + // Given a command that would cause an ArgParseError + assertThrows(ArgParseError::class.java) { cmd.parser.parse(throwingCommand) } + + // The parser consumes that error + cmd.execute(pw, throwingCommand) + } + + @Test + fun executeFailingCommand_callsOnParseFailed() { + val cmd = + object : ParseableCommand("test-command") { + val flag by flag("flag") + + var onParseFailedCalled = false + + override fun execute(pw: PrintWriter) {} + override fun onParseFailed(error: ArgParseError) { + onParseFailedCalled = true + } + } + + val throwingCommand = listOf("unknown-token") + cmd.execute(pw, throwingCommand) + + assertTrue(cmd.onParseFailedCalled) + } + + @Test + fun baseCommand() { + val myCommand = MyCommand() + myCommand.execute(pw, baseCommand) + + assertThat(myCommand.flag1).isFalse() + assertThat(myCommand.singleParam).isNull() + } + + @Test + fun commandWithFlags() { + val command = MyCommand() + command.execute(pw, cmdWithFlags) + + assertThat(command.flag1).isTrue() + assertThat(command.flag2).isTrue() + } + + @Test + fun commandWithArgs() { + val cmd = MyCommand() + cmd.execute(pw, cmdWithSingleArgParam) + + assertThat(cmd.singleParam).isEqualTo("single_param") + } + + @Test + fun commandWithRequiredParam_provided() { + val cmd = + object : ParseableCommand(name) { + val singleRequiredParam: String by + param( + longName = "param1", + shortName = "p", + valueParser = Type.String, + ) + .required() + + override fun execute(pw: PrintWriter) {} + } + + val cli = listOf("-p", "value") + cmd.execute(pw, cli) + + assertThat(cmd.singleRequiredParam).isEqualTo("value") + } + + @Test + fun commandWithRequiredParam_not_provided_throws() { + val cmd = + object : ParseableCommand(name) { + val singleRequiredParam by + param(shortName = "p", longName = "param1", valueParser = Type.String) + .required() + + override fun execute(pw: PrintWriter) {} + + override fun execute(pw: PrintWriter, args: List<String>) { + parser.parse(args) + execute(pw) + } + } + + val cli = listOf("") + assertThrows(ArgParseError::class.java) { cmd.execute(pw, cli) } + } + + @Test + fun commandWithSubCommand() { + val subName = "sub-command" + val subCmd = + object : ParseableCommand(subName) { + val singleOptionalParam: String? by param("param", valueParser = Type.String) + + override fun execute(pw: PrintWriter) {} + } + + val cmd = + object : ParseableCommand(name) { + val subCmd by subCommand(subCmd) + override fun execute(pw: PrintWriter) {} + } + + cmd.execute(pw, listOf("sub-command", "--param", "test")) + assertThat(cmd.subCmd?.singleOptionalParam).isEqualTo("test") + } + + @Test + fun complexCommandWithSubCommands_reusedNames() { + val commandLine = "-f --param1 arg1 sub-command1 -f -p arg2 --param2 arg3".split(" ") + + val subName = "sub-command1" + val subCmd = + object : ParseableCommand(subName) { + val flag1 by flag("flag", shortName = "f") + val param1: String? by param("param1", shortName = "p", valueParser = Type.String) + + override fun execute(pw: PrintWriter) {} + } + + val myCommand = + object : ParseableCommand(name) { + val flag1 by flag(longName = "flag", shortName = "f") + val param1 by param("param1", shortName = "p", valueParser = Type.String).required() + val param2: String? by param(longName = "param2", valueParser = Type.String) + val subCommand by subCommand(subCmd) + + override fun execute(pw: PrintWriter) {} + } + + myCommand.execute(pw, commandLine) + + assertThat(myCommand.flag1).isTrue() + assertThat(myCommand.param1).isEqualTo("arg1") + assertThat(myCommand.param2).isEqualTo("arg3") + assertThat(myCommand.subCommand).isNotNull() + assertThat(myCommand.subCommand?.flag1).isTrue() + assertThat(myCommand.subCommand?.param1).isEqualTo("arg2") + } + + class MyCommand( + private val onExecute: ((MyCommand) -> Unit)? = null, + ) : ParseableCommand(name) { + + val flag1 by flag(shortName = "f", longName = "flag1", description = "flag 1 for test") + val flag2 by flag(shortName = "g", longName = "flag2", description = "flag 2 for test") + val singleParam: String? by + param( + shortName = "a", + longName = "arg1", + valueParser = Type.String, + ) + + override fun execute(pw: PrintWriter) { + onExecute?.invoke(this) + } + } + + companion object { + const val name = "my_command" + val baseCommand = listOf("") + val cmdWithFlags = listOf("-f", "--flag2") + val cmdWithSingleArgParam = listOf("--arg1", "single_param") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt new file mode 100644 index 000000000000..759f0bcd6ea8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt @@ -0,0 +1,61 @@ +package com.android.systemui.statusbar.commandline + +import android.graphics.Rect +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertTrue +import org.junit.Test + +@SmallTest +class ValueParserTest : SysuiTestCase() { + @Test + fun parseString() { + assertThat(Type.String.parseValue("test")).isEqualTo(Result.success("test")) + } + + @Test + fun parseInt() { + assertThat(Type.Int.parseValue("123")).isEqualTo(Result.success(123)) + + assertTrue(Type.Int.parseValue("not an Int").isFailure) + } + + @Test + fun parseFloat() { + assertThat(Type.Float.parseValue("1.23")).isEqualTo(Result.success(1.23f)) + + assertTrue(Type.Int.parseValue("not a Float").isFailure) + } + + @Test + fun parseBoolean() { + assertThat(Type.Boolean.parseValue("true")).isEqualTo(Result.success(true)) + assertThat(Type.Boolean.parseValue("false")).isEqualTo(Result.success(false)) + + assertTrue(Type.Boolean.parseValue("not a Boolean").isFailure) + } + + @Test + fun mapToComplexType() { + val parseSquare = Type.Int.map { Rect(it, it, it, it) } + + assertThat(parseSquare.parseValue("10")).isEqualTo(Result.success(Rect(10, 10, 10, 10))) + } + + @Test + fun mapToFallibleComplexType() { + val fallibleParseSquare = + Type.Int.map { + if (it > 0) { + Rect(it, it, it, it) + } else { + null + } + } + + assertThat(fallibleParseSquare.parseValue("10")) + .isEqualTo(Result.success(Rect(10, 10, 10, 10))) + assertTrue(fallibleParseSquare.parseValue("-10").isFailure) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt index 914301f2e830..2af0cebf3519 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt @@ -69,6 +69,8 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { @Mock private lateinit var listener: SystemStatusAnimationCallback + @Mock private lateinit var logger: SystemStatusAnimationSchedulerLogger + private lateinit var systemClock: FakeSystemClock private lateinit var chipAnimationController: SystemEventChipAnimationController private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler @@ -538,7 +540,8 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { statusBarWindowController, dumpManager, systemClock, - CoroutineScope(StandardTestDispatcher(testScope.testScheduler)) + CoroutineScope(StandardTestDispatcher(testScope.testScheduler)), + logger ) // add a mock listener systemStatusAnimationScheduler.addCallback(listener) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt index a37c38669bfd..902dd51d3a87 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt @@ -20,8 +20,8 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.StatusBarState import com.google.common.truth.Truth.assertThat import org.junit.Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt index 7707a7e8bf8c..47c5e5b021ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt @@ -20,8 +20,8 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.notification.stack.StackStateLogger import com.google.common.truth.Truth import org.junit.Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 6ae7dca296c8..ee8325ec02b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -87,6 +87,7 @@ import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent; +import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback; import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -415,6 +416,36 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test + public void callSwipeCallbacksDuringClearAll() { + initController(/* viewIsAttached= */ true); + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + NotificationCallback notificationCallback = mController.mNotificationCallback; + + when(mNotificationStackScrollLayout.getClearAllInProgress()).thenReturn(true); + + notificationCallback.onBeginDrag(row); + verify(mNotificationStackScrollLayout).onSwipeBegin(row); + + notificationCallback.handleChildViewDismissed(row); + verify(mNotificationStackScrollLayout).onSwipeEnd(); + } + + @Test + public void callSwipeCallbacksDuringClearNotification() { + initController(/* viewIsAttached= */ true); + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + NotificationCallback notificationCallback = mController.mNotificationCallback; + + when(mNotificationStackScrollLayout.getClearAllInProgress()).thenReturn(false); + + notificationCallback.onBeginDrag(row); + verify(mNotificationStackScrollLayout).onSwipeBegin(row); + + notificationCallback.handleChildViewDismissed(row); + verify(mNotificationStackScrollLayout).onSwipeEnd(); + } + + @Test public void testOnMenuClickedLogging() { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS); when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index 036b8becfbbc..8545b894ad41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -20,6 +20,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -144,23 +145,32 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Test public void testDisableNotificationShade() { - when(mCentralSurfaces.getDisabled1()).thenReturn(StatusBarManager.DISABLE_NONE); - when(mCentralSurfaces.getDisabled2()).thenReturn(StatusBarManager.DISABLE_NONE); + // Start with nothing disabled + mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE, + StatusBarManager.DISABLE2_NONE, false); + when(mCommandQueue.panelsEnabled()).thenReturn(false); + // WHEN the new disable flags have the shade disabled mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE, StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false); + // THEN the shade is collapsed verify(mShadeController).animateCollapseShade(); } @Test public void testEnableNotificationShade() { - when(mCentralSurfaces.getDisabled1()).thenReturn(StatusBarManager.DISABLE_NONE); - when(mCentralSurfaces.getDisabled2()) - .thenReturn(StatusBarManager.DISABLE2_NOTIFICATION_SHADE); + // Start with the shade disabled + mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE, + StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false); + reset(mShadeController); + when(mCommandQueue.panelsEnabled()).thenReturn(true); + // WHEN the new disable flags have the shade enabled mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE, StatusBarManager.DISABLE2_NONE, false); + + // THEN the shade is not collapsed verify(mShadeController, never()).animateCollapseShade(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 1ffffe4dca75..88d8dfc50b47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -450,7 +450,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { () -> mAssistManager, () -> mNotificationGutsManager )); - mShadeController.setNotificationPanelViewController(mNotificationPanelViewController); + mShadeController.setShadeViewController(mNotificationPanelViewController); mShadeController.setNotificationShadeWindowViewController( mNotificationShadeWindowViewController); mShadeController.setNotificationPresenter(mNotificationPresenter); @@ -490,9 +490,11 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mMetricsLogger, mShadeLogger, mUiBgExecutor, + mNotificationPanelViewController, mNotificationMediaManager, mLockscreenUserManager, mRemoteInputManager, + mQuickSettingsController, mUserSwitcherController, mBatteryController, mColorExtractor, @@ -587,8 +589,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // TODO: we should be able to call mCentralSurfaces.start() and have all the below values // initialized automatically and make NPVC private. mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView; - mCentralSurfaces.mShadeSurface = mNotificationPanelViewController; - mCentralSurfaces.mQsController = mQuickSettingsController; mCentralSurfaces.mDozeScrimController = mDozeScrimController; mCentralSurfaces.mPresenter = mNotificationPresenter; mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt new file mode 100644 index 000000000000..47671fbadd0a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone + +import android.app.WallpaperManager +import android.content.pm.UserInfo +import android.os.Looper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.user.data.model.SelectionStatus +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.kotlin.JavaAdapter +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.utils.os.FakeHandler +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.verify + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class LockscreenWallpaperTest : SysuiTestCase() { + + private lateinit var underTest: LockscreenWallpaper + + private val testScope = TestScope(StandardTestDispatcher()) + private val userRepository = FakeUserRepository() + + private val wallpaperManager: WallpaperManager = mock() + + @Before + fun setUp() { + whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false) + whenever(wallpaperManager.isWallpaperSupported).thenReturn(true) + underTest = + LockscreenWallpaper( + /* wallpaperManager= */ wallpaperManager, + /* iWallpaperManager= */ mock(), + /* keyguardUpdateMonitor= */ mock(), + /* dumpManager= */ mock(), + /* mediaManager= */ mock(), + /* mainHandler= */ FakeHandler(Looper.getMainLooper()), + /* javaAdapter= */ JavaAdapter(testScope.backgroundScope), + /* userRepository= */ userRepository, + /* userTracker= */ mock(), + ) + underTest.start() + } + + @Test + fun getBitmap_matchesUserIdFromUserRepo() = + testScope.runTest { + val info = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0) + userRepository.setUserInfos(listOf(info)) + userRepository.setSelectedUserInfo(info) + + underTest.bitmap + + verify(wallpaperManager).getWallpaperFile(any(), eq(5)) + } + + @Test + fun getBitmap_usesOldUserIfNewUserInProgress() = + testScope.runTest { + val info5 = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0) + val info6 = UserInfo(/* id= */ 6, /* name= */ "id6", /* flags= */ 0) + userRepository.setUserInfos(listOf(info5, info6)) + userRepository.setSelectedUserInfo(info5) + + // WHEN the selection of user 6 is only in progress + userRepository.setSelectedUserInfo( + info6, + selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS + ) + + underTest.bitmap + + // THEN we still use user 5 for wallpaper selection + verify(wallpaperManager).getWallpaperFile(any(), eq(5)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 74bcdeec25cd..862eb001becc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -73,7 +73,6 @@ import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -588,11 +587,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } @Test - fun testConnectionRepository_invalidSubId_throws() = + fun testConnectionRepository_invalidSubId_doesNotThrow() = testScope.runTest { - assertThrows(IllegalArgumentException::class.java) { - underTest.getRepoForSubId(SUB_1_ID) - } + underTest.getRepoForSubId(SUB_1_ID) + // No exception } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index 6301fa0be463..842d548c8358 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -20,7 +20,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -48,7 +48,11 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { keyguardTransitionRepository = FakeKeyguardTransitionRepository() val interactor = - KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor underTest = CollapsedStatusBarViewModelImpl(interactor, testScope.backgroundScope) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index 079fbcd0304c..0c28cbb52831 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -26,6 +26,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.user.data.model.SelectedUserModel +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat @@ -224,6 +226,40 @@ class UserRepositoryImplTest : SysuiTestCase() { } @Test + fun userTrackerCallback_updatesSelectionStatus() = runSelfCancelingTest { + underTest = create(this) + var selectedUser: SelectedUserModel? = null + underTest.selectedUser.onEach { selectedUser = it }.launchIn(this) + setUpUsers(count = 2, selectedIndex = 1) + + // WHEN the user is changing + tracker.onUserChanging(userId = 1) + + // THEN the selection status is IN_PROGRESS + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS) + + // WHEN the user has finished changing + tracker.onUserChanged(userId = 1) + + // THEN the selection status is COMPLETE + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE) + + tracker.onProfileChanged() + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE) + + setUpUsers(count = 2, selectedIndex = 0) + + tracker.onUserChanging(userId = 0) + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS) + + // WHEN a profile change occurs while a user is changing + tracker.onProfileChanged() + + // THEN the selection status remains as IN_PROGRESS + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS) + } + + @Test fun userSwitchingInProgress_registersUserTrackerCallback() = runSelfCancelingTest { underTest = create(this) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt new file mode 100644 index 000000000000..312ade510784 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import kotlinx.coroutines.CoroutineScope + +/** + * Helper to create a new KeyguardTransitionInteractor in a way that doesn't require modifying 20+ + * tests whenever we add a constructor param. + */ +object KeyguardTransitionInteractorFactory { + @JvmOverloads + @JvmStatic + fun create( + scope: CoroutineScope, + repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(), + ): WithDependencies { + return WithDependencies( + repository = repository, + KeyguardTransitionInteractor( + scope = scope, + repository = repository, + ) + ) + } + + data class WithDependencies( + val repository: KeyguardTransitionRepository, + val keyguardTransitionInteractor: KeyguardTransitionInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index 61e5b5fc27ea..51ee0c00cb0d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -19,18 +19,28 @@ package com.android.systemui.user.data.repository import android.content.pm.UserInfo import android.os.UserHandle +import com.android.systemui.user.data.model.SelectedUserModel +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel import java.util.concurrent.atomic.AtomicBoolean import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.yield class FakeUserRepository : UserRepository { companion object { // User id to represent a non system (human) user id. We presume this is the main user. private const val MAIN_USER_ID = 10 + + private val DEFAULT_SELECTED_USER = 0 + private val DEFAULT_SELECTED_USER_INFO = + UserInfo( + /* id= */ DEFAULT_SELECTED_USER, + /* name= */ "default selected user", + /* flags= */ 0, + ) } private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel()) @@ -40,8 +50,11 @@ class FakeUserRepository : UserRepository { private val _userInfos = MutableStateFlow<List<UserInfo>>(emptyList()) override val userInfos: Flow<List<UserInfo>> = _userInfos.asStateFlow() - private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null) - override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull() + override val selectedUser = + MutableStateFlow( + SelectedUserModel(DEFAULT_SELECTED_USER_INFO, SelectionStatus.SELECTION_COMPLETE) + ) + override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo } private val _userSwitchingInProgress = MutableStateFlow(false) override val userSwitchingInProgress: Flow<Boolean> @@ -72,7 +85,7 @@ class FakeUserRepository : UserRepository { } override fun getSelectedUserInfo(): UserInfo { - return checkNotNull(_selectedUserInfo.value) + return selectedUser.value.userInfo } override fun isSimpleUserSwitcher(): Boolean { @@ -87,12 +100,15 @@ class FakeUserRepository : UserRepository { _userInfos.value = infos } - suspend fun setSelectedUserInfo(userInfo: UserInfo) { + suspend fun setSelectedUserInfo( + userInfo: UserInfo, + selectionStatus: SelectionStatus = SelectionStatus.SELECTION_COMPLETE, + ) { check(_userInfos.value.contains(userInfo)) { "Cannot select the following user, it is not in the list of user infos: $userInfo!" } - _selectedUserInfo.value = userInfo + selectedUser.value = SelectedUserModel(userInfo, selectionStatus) yield() } diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index c2ebddf00fb4..502ee4db80c8 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -75,6 +75,7 @@ import android.util.Range; import android.util.Size; import android.view.Surface; +import androidx.annotation.NonNull; import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl; import androidx.camera.extensions.impl.AutoPreviewExtenderImpl; import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl; @@ -204,7 +205,7 @@ public class CameraExtensionsProxyService extends Service { * A per-process global camera extension manager instance, to track and * initialize/release extensions depending on client activity. */ - private static final class CameraExtensionManagerGlobal { + private static final class CameraExtensionManagerGlobal implements IBinder.DeathRecipient { private static final String TAG = "CameraExtensionManagerGlobal"; private final int EXTENSION_DELAY_MS = 1000; @@ -212,8 +213,9 @@ public class CameraExtensionsProxyService extends Service { private final HandlerThread mHandlerThread; private final Object mLock = new Object(); - private long mCurrentClientCount = 0; - private ArraySet<Long> mActiveClients = new ArraySet<>(); + private ArraySet<IBinder> mActiveClients = new ArraySet<>(); + private HashMap<IBinder, ArraySet<IBinder.DeathRecipient>> mClientDeathRecipient = + new HashMap<>(); private IInitializeSessionCallback mInitializeCb = null; // Singleton instance @@ -314,8 +316,20 @@ public class CameraExtensionsProxyService extends Service { return GLOBAL_CAMERA_MANAGER; } - public long registerClient(Context ctx) { + public boolean registerClient(Context ctx, IBinder token) { synchronized (mLock) { + if (mActiveClients.contains(token)) { + Log.e(TAG, "Failed to register existing client!"); + return false; + } + + try { + token.linkToDeath(this, 0); + } catch (RemoteException e) { + Log.e(TAG, "Failed to link to binder token!"); + return false; + } + if (INIT_API_SUPPORTED) { if (mActiveClients.isEmpty()) { InitializerFuture status = new InitializerFuture(); @@ -327,47 +341,80 @@ public class CameraExtensionsProxyService extends Service { TimeUnit.MILLISECONDS); } catch (TimeoutException e) { Log.e(TAG, "Timed out while initializing camera extensions!"); - return -1; + return false; } if (!initSuccess) { Log.e(TAG, "Failed while initializing camera extensions!"); - return -1; + return false; } } } - long ret = mCurrentClientCount; - mCurrentClientCount++; - if (mCurrentClientCount < 0) { - mCurrentClientCount = 0; - } - mActiveClients.add(ret); + mActiveClients.add(token); + mClientDeathRecipient.put(token, new ArraySet<>()); - return ret; + return true; } } - public void unregisterClient(long clientId) { + public void unregisterClient(IBinder token) { synchronized (mLock) { - if (mActiveClients.remove(clientId) && mActiveClients.isEmpty() && - INIT_API_SUPPORTED) { - InitializerFuture status = new InitializerFuture(); - InitializerImpl.deinit(new ReleaseHandler(status), - new HandlerExecutor(mHandler)); - boolean releaseSuccess; - try { - releaseSuccess = status.get(EXTENSION_DELAY_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.e(TAG, "Timed out while releasing camera extensions!"); - return; - } - if (!releaseSuccess) { - Log.e(TAG, "Failed while releasing camera extensions!"); + if (mActiveClients.remove(token)) { + token.unlinkToDeath(this, 0); + mClientDeathRecipient.remove(token); + if (mActiveClients.isEmpty() && INIT_API_SUPPORTED) { + InitializerFuture status = new InitializerFuture(); + InitializerImpl.deinit(new ReleaseHandler(status), + new HandlerExecutor(mHandler)); + boolean releaseSuccess; + try { + releaseSuccess = status.get(EXTENSION_DELAY_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + Log.e(TAG, "Timed out while releasing camera extensions!"); + return; + } + if (!releaseSuccess) { + Log.e(TAG, "Failed while releasing camera extensions!"); + } } } } } + @Override + public void binderDied() { + // Do nothing, handled below + } + + @Override + public void binderDied(@NonNull IBinder who) { + synchronized (mLock) { + if (mClientDeathRecipient.containsKey(who)) { + mClientDeathRecipient.get(who).stream().forEach( + recipient -> recipient.binderDied(who)); + } + unregisterClient(who); + } + } + + public void registerDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) { + synchronized (mLock) { + if (mClientDeathRecipient.containsKey(token)) { + ArraySet<IBinder.DeathRecipient> recipients = mClientDeathRecipient.get(token); + recipients.add(recipient); + } + } + } + + public void unregisterDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) { + synchronized (mLock) { + if (mClientDeathRecipient.containsKey(token)) { + ArraySet<IBinder.DeathRecipient> recipients = mClientDeathRecipient.get(token); + recipients.remove(recipient); + } + } + } + private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { @@ -406,21 +453,35 @@ public class CameraExtensionsProxyService extends Service { /** * @hide */ - private static long registerClient(Context ctx) { + private static boolean registerClient(Context ctx, IBinder token) { if (!EXTENSIONS_PRESENT) { - return -1; + return false; } - return CameraExtensionManagerGlobal.get().registerClient(ctx); + return CameraExtensionManagerGlobal.get().registerClient(ctx, token); } /** * @hide */ - public static void unregisterClient(long clientId) { + public static void unregisterClient(IBinder token) { if (!EXTENSIONS_PRESENT) { return; } - CameraExtensionManagerGlobal.get().unregisterClient(clientId); + CameraExtensionManagerGlobal.get().unregisterClient(token); + } + + /** + * @hide + */ + private static void registerDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) { + CameraExtensionManagerGlobal.get().registerDeathRecipient(token, recipient); + } + + /** + * @hide + */ + private static void unregisterDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) { + CameraExtensionManagerGlobal.get().unregisterDeathRecipient(token, recipient); } /** @@ -649,13 +710,14 @@ public class CameraExtensionsProxyService extends Service { private class CameraExtensionsProxyServiceStub extends ICameraExtensionsProxyService.Stub { @Override - public long registerClient() { - return CameraExtensionsProxyService.registerClient(CameraExtensionsProxyService.this); + public boolean registerClient(IBinder token) { + return CameraExtensionsProxyService.registerClient(CameraExtensionsProxyService.this, + token); } @Override - public void unregisterClient(long clientId) { - CameraExtensionsProxyService.unregisterClient(clientId); + public void unregisterClient(IBinder token) { + CameraExtensionsProxyService.unregisterClient(token); } private boolean checkCameraPermission() { @@ -1192,16 +1254,18 @@ public class CameraExtensionsProxyService extends Service { } } - private class SessionProcessorImplStub extends ISessionProcessorImpl.Stub { + private class SessionProcessorImplStub extends ISessionProcessorImpl.Stub implements + IBinder.DeathRecipient { private final SessionProcessorImpl mSessionProcessor; private String mCameraId = null; + private IBinder mToken; public SessionProcessorImplStub(SessionProcessorImpl sessionProcessor) { mSessionProcessor = sessionProcessor; } @Override - public CameraSessionConfig initSession(String cameraId, + public CameraSessionConfig initSession(IBinder token, String cameraId, Map<String, CameraMetadataNative> charsMapNative, OutputSurface previewSurface, OutputSurface imageCaptureSurface, OutputSurface postviewSurface) { OutputSurfaceImplStub outputPreviewSurfaceImpl = @@ -1253,12 +1317,14 @@ public class CameraExtensionsProxyService extends Service { ret.sessionParameter = initializeParcelableMetadata( sessionConfig.getSessionParameters(), cameraId); mCameraId = cameraId; - + mToken = token; + CameraExtensionsProxyService.registerDeathRecipient(mToken, this); return ret; } @Override - public void deInitSession() { + public void deInitSession(IBinder token) { + CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this); mSessionProcessor.deInitSession(); } @@ -1330,6 +1396,11 @@ public class CameraExtensionsProxyService extends Service { return null; } + + @Override + public void binderDied() { + mSessionProcessor.deInitSession(); + } } private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl { @@ -1395,24 +1466,31 @@ public class CameraExtensionsProxyService extends Service { } } - private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub { + private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements + IBinder.DeathRecipient { private final PreviewExtenderImpl mPreviewExtender; private String mCameraId = null; + private boolean mSessionEnabled; + private IBinder mToken; public PreviewExtenderImplStub(PreviewExtenderImpl previewExtender) { mPreviewExtender = previewExtender; } @Override - public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) { + public void onInit(IBinder token, String cameraId, + CameraMetadataNative cameraCharacteristics) { mCameraId = cameraId; CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics); mCameraManager.registerDeviceStateListener(chars); mPreviewExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this); + mToken = token; + CameraExtensionsProxyService.registerDeathRecipient(mToken, this); } @Override - public void onDeInit() { + public void onDeInit(IBinder token) { + CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this); mPreviewExtender.onDeInit(); } @@ -1423,11 +1501,13 @@ public class CameraExtensionsProxyService extends Service { @Override public CaptureStageImpl onEnableSession() { + mSessionEnabled = true; return initializeParcelable(mPreviewExtender.onEnableSession(), mCameraId); } @Override public CaptureStageImpl onDisableSession() { + mSessionEnabled = false; return initializeParcelable(mPreviewExtender.onDisableSession(), mCameraId); } @@ -1516,26 +1596,41 @@ public class CameraExtensionsProxyService extends Service { } return null; } + + @Override + public void binderDied() { + if (mSessionEnabled) { + mPreviewExtender.onDisableSession(); + } + mPreviewExtender.onDeInit(); + } } - private class ImageCaptureExtenderImplStub extends IImageCaptureExtenderImpl.Stub { + private class ImageCaptureExtenderImplStub extends IImageCaptureExtenderImpl.Stub implements + IBinder.DeathRecipient { private final ImageCaptureExtenderImpl mImageExtender; private String mCameraId = null; + private boolean mSessionEnabled; + private IBinder mToken; public ImageCaptureExtenderImplStub(ImageCaptureExtenderImpl imageExtender) { mImageExtender = imageExtender; } @Override - public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) { + public void onInit(IBinder token, String cameraId, + CameraMetadataNative cameraCharacteristics) { CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics); mCameraManager.registerDeviceStateListener(chars); mImageExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this); mCameraId = cameraId; + mToken = token; + CameraExtensionsProxyService.registerDeathRecipient(mToken, this); } @Override - public void onDeInit() { + public void onDeInit(IBinder token) { + CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this); mImageExtender.onDeInit(); } @@ -1564,11 +1659,13 @@ public class CameraExtensionsProxyService extends Service { @Override public CaptureStageImpl onEnableSession() { + mSessionEnabled = true; return initializeParcelable(mImageExtender.onEnableSession(), mCameraId); } @Override public CaptureStageImpl onDisableSession() { + mSessionEnabled = false; return initializeParcelable(mImageExtender.onDisableSession(), mCameraId); } @@ -1737,6 +1834,14 @@ public class CameraExtensionsProxyService extends Service { return null; } + + @Override + public void binderDied() { + if (mSessionEnabled) { + mImageExtender.onDisableSession(); + } + mImageExtender.onDeInit(); + } } private class ProcessResultCallback implements ProcessResultImpl { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index b1cdc5053c27..ba3d4340a157 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -319,6 +319,10 @@ public class FullScreenMagnificationController implements FullScreenMagnificationController::onUserContextChanged, FullScreenMagnificationController.this, mDisplayId); mControllerCtx.getHandler().sendMessage(m); + + synchronized (mLock) { + refreshThumbnail(); + } } @Override @@ -344,7 +348,7 @@ public class FullScreenMagnificationController implements mMagnificationRegion.set(magnified); mMagnificationRegion.getBounds(mMagnificationBounds); - refreshThumbnail(getScale(), getCenterX(), getCenterY()); + refreshThumbnail(); // It's possible that our magnification spec is invalid with the new bounds. // Adjust the current spec's offsets if necessary. @@ -602,13 +606,13 @@ public class FullScreenMagnificationController implements } @GuardedBy("mLock") - void refreshThumbnail(float scale, float centerX, float centerY) { + void refreshThumbnail() { if (mMagnificationThumbnail != null) { mMagnificationThumbnail.setThumbnailBounds( mMagnificationBounds, - scale, - centerX, - centerY + getScale(), + getCenterX(), + getCenterY() ); } } @@ -627,7 +631,7 @@ public class FullScreenMagnificationController implements // We call refreshThumbnail when the thumbnail is just created to set current // magnification bounds to thumbnail. It to prevent the thumbnail size has not yet // updated properly and thus shows with huge size. (b/276314641) - refreshThumbnail(getScale(), getCenterX(), getCenterY()); + refreshThumbnail(); } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java index 03fa93d8a3bc..a7bdd5a09ac2 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java @@ -99,15 +99,17 @@ public class MagnificationThumbnail { Log.d(LOG_TAG, "setThumbnailBounds " + currentBounds); } mHandler.post(() -> { - mWindowBounds = currentBounds; - setBackgroundBounds(); + refreshBackgroundBounds(currentBounds); if (mVisible) { updateThumbnailMainThread(scale, centerX, centerY); } }); } - private void setBackgroundBounds() { + @MainThread + private void refreshBackgroundBounds(Rect currentBounds) { + mWindowBounds = currentBounds; + Point magnificationBoundary = getMagnificationThumbnailPadding(mContext); mThumbnailWidth = (int) (mWindowBounds.width() / BG_ASPECT_RATIO); mThumbnailHeight = (int) (mWindowBounds.height() / BG_ASPECT_RATIO); @@ -117,6 +119,10 @@ public class MagnificationThumbnail { mBackgroundParams.height = mThumbnailHeight; mBackgroundParams.x = initX; mBackgroundParams.y = initY; + + if (mVisible) { + mWindowManager.updateViewLayout(mThumbnailLayout, mBackgroundParams); + } } @MainThread @@ -264,21 +270,16 @@ public class MagnificationThumbnail { mThumbnailView.setScaleX(scaleDown); mThumbnailView.setScaleY(scaleDown); } - float thumbnailWidth; - float thumbnailHeight; - if (mThumbnailView.getWidth() == 0 || mThumbnailView.getHeight() == 0) { - // if the thumbnail view size is not updated correctly, we just use the cached values. - thumbnailWidth = mThumbnailWidth; - thumbnailHeight = mThumbnailHeight; - } else { - thumbnailWidth = mThumbnailView.getWidth(); - thumbnailHeight = mThumbnailView.getHeight(); - } - if (!Float.isNaN(centerX)) { + + if (!Float.isNaN(centerX) + && !Float.isNaN(centerY) + && mThumbnailWidth > 0 + && mThumbnailHeight > 0 + ) { var padding = mThumbnailView.getPaddingTop(); var ratio = 1f / BG_ASPECT_RATIO; - var centerXScaled = centerX * ratio - (thumbnailWidth / 2f + padding); - var centerYScaled = centerY * ratio - (thumbnailHeight / 2f + padding); + var centerXScaled = centerX * ratio - (mThumbnailWidth / 2f + padding); + var centerYScaled = centerY * ratio - (mThumbnailHeight / 2f + padding); if (DEBUG) { Log.d( diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java index 7ee96aaca21d..a20623cd1ee9 100644 --- a/services/core/java/com/android/server/am/PendingIntentController.java +++ b/services/core/java/com/android/server/am/PendingIntentController.java @@ -26,6 +26,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NA import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManagerInternal; +import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.PendingIntent; import android.app.PendingIntentStats; @@ -126,6 +127,18 @@ public class PendingIntentController { } } Bundle.setDefusable(bOptions, true); + ActivityOptions opts = ActivityOptions.fromBundle(bOptions); + if (opts != null && opts.getPendingIntentBackgroundActivityStartMode() + != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { + Slog.wtf(TAG, "Resetting option setPendingIntentBackgroundActivityStartMode(" + + opts.getPendingIntentBackgroundActivityStartMode() + + ") to SYSTEM_DEFINED from the options provided by the pending " + + "intent creator (" + + packageName + + ") because this option is meant for the pending intent sender"); + opts.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED); + } final boolean noCreate = (flags & PendingIntent.FLAG_NO_CREATE) != 0; final boolean cancelCurrent = (flags & PendingIntent.FLAG_CANCEL_CURRENT) != 0; @@ -135,7 +148,7 @@ public class PendingIntentController { PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, featureId, token, resultWho, requestCode, intents, resolvedTypes, flags, - SafeActivityOptions.fromBundle(bOptions), userId); + new SafeActivityOptions(opts), userId); WeakReference<PendingIntentRecord> ref; ref = mIntentSenderRecords.get(key); PendingIntentRecord rec = ref != null ? ref.get() : null; diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 202d407651ad..a0e76f1d6af9 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -457,6 +457,20 @@ public final class PendingIntentRecord extends IIntentSender.Stub { // can specify a consistent launch mode even if the PendingIntent is immutable final ActivityOptions opts = ActivityOptions.fromBundle(options); if (opts != null) { + if (opts.getPendingIntentCreatorBackgroundActivityStartMode() + != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { + Slog.wtf(TAG, + "Resetting option " + + "setPendingIntentCreatorBackgroundActivityStartMode(" + + opts.getPendingIntentCreatorBackgroundActivityStartMode() + + ") to SYSTEM_DEFINED from the options provided by the " + + "pending intent sender (" + + key.packageName + + ") because this option is meant for the pending intent " + + "creator"); + opts.setPendingIntentCreatorBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED); + } finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); } diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index e4983846f1da..753fdaed2fe6 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -51,11 +51,11 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.CompositeRWLock; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.modules.expresslog.Counter; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TimeoutRecord; import com.android.internal.os.anr.AnrLatencyTracker; import com.android.internal.util.FrameworkStatsLog; +import com.android.modules.expresslog.Counter; import com.android.server.ResourcePressureUtil; import com.android.server.criticalevents.CriticalEventLog; import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot; @@ -456,6 +456,11 @@ class ProcessErrorStateRecord { String currentPsiState = ResourcePressureUtil.currentPsiState(); latencyTracker.currentPsiStateReturned(); report.append(currentPsiState); + // The 'processCpuTracker' variable is a shared resource that might be initialized and + // updated in a different thread. In order to prevent thread visibility issues, which + // can occur when one thread does not immediately see the changes made to + // 'processCpuTracker' by another thread, it is necessary to use synchronization whenever + // 'processCpuTracker' is accessed or modified. ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true); // We push the native pids collection task to the helper thread through @@ -517,12 +522,16 @@ class ProcessErrorStateRecord { } mService.updateCpuStatsNow(); mService.mAppProfiler.printCurrentCpuState(report, anrTime); - info.append(processCpuTracker.printCurrentLoad()); + synchronized (processCpuTracker) { + info.append(processCpuTracker.printCurrentLoad()); + } info.append(report); } report.append(tracesFileException.getBuffer()); - info.append(processCpuTracker.printCurrentState(anrTime)); + synchronized (processCpuTracker) { + info.append(processCpuTracker.printCurrentState(anrTime)); + } Slog.e(TAG, info.toString()); if (tracesFile == null) { diff --git a/services/core/java/com/android/server/am/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java index cf69b535151c..ba0fd17a3f2f 100644 --- a/services/core/java/com/android/server/am/StackTracesDumpHelper.java +++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java @@ -464,28 +464,31 @@ public class StackTracesDumpHelper { latencyTracker.processCpuTrackerMethodsCalled(); } ArrayList<Integer> extraPids = new ArrayList<>(); - processCpuTracker.init(); + synchronized (processCpuTracker) { + processCpuTracker.init(); + } try { Thread.sleep(200); } catch (InterruptedException ignored) { } - processCpuTracker.update(); + synchronized (processCpuTracker) { + processCpuTracker.update(); + // We'll take the stack crawls of just the top apps using CPU. + final int workingStatsNumber = processCpuTracker.countWorkingStats(); + for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) { + ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i); + if (lastPids.indexOfKey(stats.pid) >= 0) { + if (DEBUG_ANR) { + Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid); + } - // We'll take the stack crawls of just the top apps using CPU. - final int workingStatsNumber = processCpuTracker.countWorkingStats(); - for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) { - ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i); - if (lastPids.indexOfKey(stats.pid) >= 0) { - if (DEBUG_ANR) { - Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid); + extraPids.add(stats.pid); + } else { + Slog.i(TAG, + "Skipping next CPU consuming process, not a java proc: " + + stats.pid); } - - extraPids.add(stats.pid); - } else { - Slog.i(TAG, - "Skipping next CPU consuming process, not a java proc: " - + stats.pid); } } if (latencyTracker != null) { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 76a994ec63e9..99c2f8a5cc56 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1972,7 +1972,7 @@ class UserController implements Handler.Callback { } private void dismissUserSwitchDialog(Runnable onDismissed) { - mInjector.dismissUserSwitchingDialog(onDismissed); + mUiHandler.post(() -> mInjector.dismissUserSwitchingDialog(onDismissed)); } private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) { diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index e4a5a3e0ed00..ca15dd79adbc 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -2166,7 +2166,7 @@ public final class GameManagerService extends IGameManagerService.Stub { @Override public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { synchronized (mUidObserverLock) { - if (ActivityManager.isProcStateBackground(procState)) { + if (procState != ActivityManager.PROCESS_STATE_TOP) { disableGameMode(uid); return; } diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 29a19417b8fd..393e43008fd0 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -30,6 +30,8 @@ import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; +import android.media.AudioPlaybackConfiguration; +import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; import android.media.AudioSystem; import android.media.BluetoothProfileConnectionInfo; @@ -289,37 +291,38 @@ import java.util.concurrent.atomic.AtomicBoolean; * @param on * @param eventSource for logging purposes */ - /*package*/ void setSpeakerphoneOn(IBinder cb, int pid, boolean on, String eventSource) { + /*package*/ void setSpeakerphoneOn( + IBinder cb, int uid, boolean on, boolean isPrivileged, String eventSource) { if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid); + Log.v(TAG, "setSpeakerphoneOn, on: " + on + " uid: " + uid); } postSetCommunicationDeviceForClient(new CommunicationDeviceInfo( - cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""), - on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false)); + cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""), + on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false, isPrivileged)); } /** * Select device for use for communication use cases. * @param cb Client binder for death detection - * @param pid Client pid + * @param uid Client uid * @param device Device selected or null to unselect. * @param eventSource for logging purposes */ private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000; - /*package*/ boolean setCommunicationDevice( - IBinder cb, int pid, AudioDeviceInfo device, String eventSource) { + /*package*/ boolean setCommunicationDevice(IBinder cb, int uid, AudioDeviceInfo device, + boolean isPrivileged, String eventSource) { if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "setCommunicationDevice, device: " + device + ", pid: " + pid); + Log.v(TAG, "setCommunicationDevice, device: " + device + ", uid: " + uid); } AudioDeviceAttributes deviceAttr = (device != null) ? new AudioDeviceAttributes(device) : null; - CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, pid, deviceAttr, - device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true); + CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, uid, deviceAttr, + device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true, isPrivileged); postSetCommunicationDeviceForClient(deviceInfo); boolean status; synchronized (deviceInfo) { @@ -353,7 +356,7 @@ import java.util.concurrent.atomic.AtomicBoolean; Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo); } if (!deviceInfo.mOn) { - CommunicationRouteClient client = getCommunicationRouteClientForPid(deviceInfo.mPid); + CommunicationRouteClient client = getCommunicationRouteClientForUid(deviceInfo.mUid); if (client == null || (deviceInfo.mDevice != null && !deviceInfo.mDevice.equals(client.getDevice()))) { return false; @@ -361,22 +364,23 @@ import java.util.concurrent.atomic.AtomicBoolean; } AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null; - setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mPid, device, - deviceInfo.mScoAudioMode, deviceInfo.mEventSource); + setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mUid, device, + deviceInfo.mScoAudioMode, deviceInfo.mIsPrivileged, deviceInfo.mEventSource); return true; } @GuardedBy("mDeviceStateLock") /*package*/ void setCommunicationRouteForClient( - IBinder cb, int pid, AudioDeviceAttributes device, - int scoAudioMode, String eventSource) { + IBinder cb, int uid, AudioDeviceAttributes device, + int scoAudioMode, boolean isPrivileged, String eventSource) { if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "setCommunicationRouteForClient: device: " + device); + Log.v(TAG, "setCommunicationRouteForClient: device: " + device + + ", eventSource: " + eventSource); } AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( - "setCommunicationRouteForClient for pid: " + pid - + " device: " + device + "setCommunicationRouteForClient for uid: " + uid + + " device: " + device + " isPrivileged: " + isPrivileged + " from API: " + eventSource)).printLog(TAG)); final boolean wasBtScoRequested = isBluetoothScoRequested(); @@ -385,16 +389,18 @@ import java.util.concurrent.atomic.AtomicBoolean; // Save previous client route in case of failure to start BT SCO audio AudioDeviceAttributes prevClientDevice = null; - client = getCommunicationRouteClientForPid(pid); + boolean prevPrivileged = false; + client = getCommunicationRouteClientForUid(uid); if (client != null) { prevClientDevice = client.getDevice(); + prevPrivileged = client.isPrivileged(); } if (device != null) { - client = addCommunicationRouteClient(cb, pid, device); + client = addCommunicationRouteClient(cb, uid, device, isPrivileged); if (client == null) { - Log.w(TAG, "setCommunicationRouteForClient: could not add client for pid: " - + pid + " and device: " + device); + Log.w(TAG, "setCommunicationRouteForClient: could not add client for uid: " + + uid + " and device: " + device); } } else { client = removeCommunicationRouteClient(cb, true); @@ -406,11 +412,11 @@ import java.util.concurrent.atomic.AtomicBoolean; boolean isBtScoRequested = isBluetoothScoRequested(); if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) { if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) { - Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for pid: " - + pid); + Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: " + + uid); // clean up or restore previous client selection if (prevClientDevice != null) { - addCommunicationRouteClient(cb, pid, prevClientDevice); + addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged); } else { removeCommunicationRouteClient(cb, true); } @@ -447,11 +453,12 @@ import java.util.concurrent.atomic.AtomicBoolean; @GuardedBy("mDeviceStateLock") private CommunicationRouteClient topCommunicationRouteClient() { for (CommunicationRouteClient crc : mCommunicationRouteClients) { - if (crc.getPid() == mAudioModeOwner.mPid) { + if (crc.getUid() == mAudioModeOwner.mUid) { return crc; } } - if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0) { + if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0 + && mCommunicationRouteClients.get(0).isActive()) { return mCommunicationRouteClients.get(0); } return null; @@ -491,14 +498,48 @@ import java.util.concurrent.atomic.AtomicBoolean; }; /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) { + return isValidCommunicationDeviceType(device.getType()); + } + + private static boolean isValidCommunicationDeviceType(int deviceType) { for (int type : VALID_COMMUNICATION_DEVICE_TYPES) { - if (device.getType() == type) { + if (deviceType == type) { return true; } } return false; } + /*package */ + void postCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) { + if (!isValidCommunicationDeviceType( + AudioDeviceInfo.convertInternalDeviceToDeviceType(device.getInternalType()))) { + return; + } + sendLMsgNoDelay(MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL, SENDMSG_QUEUE, device); + } + + @GuardedBy("mDeviceStateLock") + void onCheckCommunicationDeviceRemoval(@NonNull AudioDeviceAttributes device) { + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "onCheckCommunicationDeviceRemoval device: " + device.toString()); + } + for (CommunicationRouteClient crc : mCommunicationRouteClients) { + if (device.equals(crc.getDevice())) { + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "onCheckCommunicationDeviceRemoval removing client: " + + crc.toString()); + } + // Cancelling the route for this client will remove it from the stack and update + // the communication route. + CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo( + crc.getBinder(), crc.getUid(), device, false, + BtHelper.SCO_MODE_UNDEFINED, "onCheckCommunicationDeviceRemoval", + false, crc.isPrivileged()); + postSetCommunicationDeviceForClient(deviceInfo); + } + } + } /* package */ static List<AudioDeviceInfo> getAvailableCommunicationDevices() { ArrayList<AudioDeviceInfo> commDevices = new ArrayList<>(); AudioDeviceInfo[] allDevices = @@ -1107,26 +1148,26 @@ import java.util.concurrent.atomic.AtomicBoolean; sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info); } - /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode, - @NonNull String eventSource) { + /*package*/ void startBluetoothScoForClient(IBinder cb, int uid, int scoAudioMode, + boolean isPrivileged, @NonNull String eventSource) { if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "startBluetoothScoForClient, pid: " + pid); + Log.v(TAG, "startBluetoothScoForClient, uid: " + uid); } postSetCommunicationDeviceForClient(new CommunicationDeviceInfo( - cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""), - true, scoAudioMode, eventSource, false)); + cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""), + true, scoAudioMode, eventSource, false, isPrivileged)); } /*package*/ void stopBluetoothScoForClient( - IBinder cb, int pid, @NonNull String eventSource) { + IBinder cb, int uid, boolean isPrivileged, @NonNull String eventSource) { if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "stopBluetoothScoForClient, pid: " + pid); + Log.v(TAG, "stopBluetoothScoForClient, uid: " + uid); } postSetCommunicationDeviceForClient(new CommunicationDeviceInfo( - cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""), - false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false)); + cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""), + false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false, isPrivileged)); } /*package*/ int setPreferredDevicesForStrategySync(int strategy, @@ -1367,22 +1408,24 @@ import java.util.concurrent.atomic.AtomicBoolean; /*package*/ static final class CommunicationDeviceInfo { final @NonNull IBinder mCb; // Identifies the requesting client for death handler - final int mPid; // Requester process ID + final int mUid; // Requester UID final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset. final boolean mOn; // true if setting, false if resetting final int mScoAudioMode; // only used for SCO: requested audio mode + final boolean mIsPrivileged; // true if the client app has MODIFY_PHONE_STATE permission final @NonNull String mEventSource; // caller identifier for logging boolean mWaitForStatus; // true if the caller waits for a completion status (API dependent) boolean mStatus = false; // completion status only used if mWaitForStatus is true - CommunicationDeviceInfo(@NonNull IBinder cb, int pid, + CommunicationDeviceInfo(@NonNull IBinder cb, int uid, @Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode, - @NonNull String eventSource, boolean waitForStatus) { + @NonNull String eventSource, boolean waitForStatus, boolean isPrivileged) { mCb = cb; - mPid = pid; + mUid = uid; mDevice = device; mOn = on; mScoAudioMode = scoAudioMode; + mIsPrivileged = isPrivileged; mEventSource = eventSource; mWaitForStatus = waitForStatus; } @@ -1401,16 +1444,17 @@ import java.util.concurrent.atomic.AtomicBoolean; } return mCb.equals(((CommunicationDeviceInfo) o).mCb) - && mPid == ((CommunicationDeviceInfo) o).mPid; + && mUid == ((CommunicationDeviceInfo) o).mUid; } @Override public String toString() { return "CommunicationDeviceInfo mCb=" + mCb.toString() - + " mPid=" + mPid + + " mUid=" + mUid + " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]" + " mOn=" + mOn + " mScoAudioMode=" + mScoAudioMode + + " mIsPrivileged=" + mIsPrivileged + " mEventSource=" + mEventSource + " mWaitForStatus=" + mWaitForStatus + " mStatus=" + mStatus; @@ -1440,7 +1484,7 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, + /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes, boolean connect, @Nullable BluetoothDevice btDevice) { synchronized (mDeviceStateLock) { return mDeviceInventory.handleDeviceConnection( @@ -1507,8 +1551,7 @@ import java.util.concurrent.atomic.AtomicBoolean; pw.println("\n" + prefix + "Communication route clients:"); mCommunicationRouteClients.forEach((cl) -> { - pw.println(" " + prefix + "pid: " + cl.getPid() + " device: " - + cl.getDevice() + " cb: " + cl.getBinder()); }); + pw.println(" " + prefix + cl.toString()); }); pw.println("\n" + prefix + "Computed Preferred communication device: " + preferredCommunicationDevice()); @@ -1850,6 +1893,15 @@ import java.util.concurrent.atomic.AtomicBoolean; final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; BtHelper.onNotifyPreferredAudioProfileApplied(btDevice); } break; + + case MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL: { + synchronized (mSetModeLock) { + synchronized (mDeviceStateLock) { + onCheckCommunicationDeviceRemoval((AudioDeviceAttributes) msg.obj); + } + } + } break; + default: Log.wtf(TAG, "Invalid message " + msg.what); } @@ -1926,6 +1978,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49; private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52; + private static final int MSG_L_CHECK_COMMUNICATION_DEVICE_REMOVAL = 53; private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { @@ -2101,13 +2154,20 @@ import java.util.concurrent.atomic.AtomicBoolean; private class CommunicationRouteClient implements IBinder.DeathRecipient { private final IBinder mCb; - private final int mPid; + private final int mUid; + private final boolean mIsPrivileged; private AudioDeviceAttributes mDevice; + private boolean mPlaybackActive; + private boolean mRecordingActive; - CommunicationRouteClient(IBinder cb, int pid, AudioDeviceAttributes device) { + CommunicationRouteClient(IBinder cb, int uid, AudioDeviceAttributes device, + boolean isPrivileged) { mCb = cb; - mPid = pid; + mUid = uid; mDevice = device; + mIsPrivileged = isPrivileged; + mPlaybackActive = mAudioService.isPlaybackActiveForUid(uid); + mRecordingActive = mAudioService.isRecordingActiveForUid(uid); } public boolean registerDeathRecipient() { @@ -2138,13 +2198,38 @@ import java.util.concurrent.atomic.AtomicBoolean; return mCb; } - int getPid() { - return mPid; + int getUid() { + return mUid; + } + + boolean isPrivileged() { + return mIsPrivileged; } AudioDeviceAttributes getDevice() { return mDevice; } + + public void setPlaybackActive(boolean active) { + mPlaybackActive = active; + } + + public void setRecordingActive(boolean active) { + mRecordingActive = active; + } + + public boolean isActive() { + return mIsPrivileged || mRecordingActive || mPlaybackActive; + } + + @Override + public String toString() { + return "[CommunicationRouteClient: mUid: " + mUid + + " mDevice: " + mDevice.toString() + + " mIsPrivileged: " + mIsPrivileged + + " mPlaybackActive: " + mPlaybackActive + + " mRecordingActive: " + mRecordingActive + "]"; + } } // @GuardedBy("mSetModeLock") @@ -2154,8 +2239,9 @@ import java.util.concurrent.atomic.AtomicBoolean; return; } Log.w(TAG, "Communication client died"); - setCommunicationRouteForClient(client.getBinder(), client.getPid(), null, - BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied"); + setCommunicationRouteForClient(client.getBinder(), client.getUid(), null, + BtHelper.SCO_MODE_UNDEFINED, client.isPrivileged(), + "onCommunicationRouteClientDied"); } /** @@ -2242,8 +2328,8 @@ import java.util.concurrent.atomic.AtomicBoolean; + crc + " eventSource: " + eventSource); } if (crc != null) { - setCommunicationRouteForClient(crc.getBinder(), crc.getPid(), crc.getDevice(), - BtHelper.SCO_MODE_UNDEFINED, eventSource); + setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(), + BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource); } } @@ -2267,6 +2353,7 @@ import java.util.concurrent.atomic.AtomicBoolean; dispatchCommunicationDevice(); } + @GuardedBy("mDeviceStateLock") private CommunicationRouteClient removeCommunicationRouteClient( IBinder cb, boolean unregister) { for (CommunicationRouteClient cl : mCommunicationRouteClients) { @@ -2282,11 +2369,12 @@ import java.util.concurrent.atomic.AtomicBoolean; } @GuardedBy("mDeviceStateLock") - private CommunicationRouteClient addCommunicationRouteClient( - IBinder cb, int pid, AudioDeviceAttributes device) { + private CommunicationRouteClient addCommunicationRouteClient(IBinder cb, int uid, + AudioDeviceAttributes device, boolean isPrivileged) { // always insert new request at first position removeCommunicationRouteClient(cb, true); - CommunicationRouteClient client = new CommunicationRouteClient(cb, pid, device); + CommunicationRouteClient client = + new CommunicationRouteClient(cb, uid, device, isPrivileged); if (client.registerDeathRecipient()) { mCommunicationRouteClients.add(0, client); return client; @@ -2295,9 +2383,9 @@ import java.util.concurrent.atomic.AtomicBoolean; } @GuardedBy("mDeviceStateLock") - private CommunicationRouteClient getCommunicationRouteClientForPid(int pid) { + private CommunicationRouteClient getCommunicationRouteClientForUid(int uid) { for (CommunicationRouteClient cl : mCommunicationRouteClients) { - if (cl.getPid() == pid) { + if (cl.getUid() == uid) { return cl; } } @@ -2330,6 +2418,45 @@ import java.util.concurrent.atomic.AtomicBoolean; return device; } + void updateCommunicationRouteClientsActivity( + List<AudioPlaybackConfiguration> playbackConfigs, + List<AudioRecordingConfiguration> recordConfigs) { + synchronized (mSetModeLock) { + synchronized (mDeviceStateLock) { + boolean updateCommunicationRoute = false; + for (CommunicationRouteClient crc : mCommunicationRouteClients) { + boolean wasActive = crc.isActive(); + if (playbackConfigs != null) { + crc.setPlaybackActive(false); + for (AudioPlaybackConfiguration config : playbackConfigs) { + if (config.getClientUid() == crc.getUid() + && config.isActive()) { + crc.setPlaybackActive(true); + break; + } + } + } + if (recordConfigs != null) { + crc.setRecordingActive(false); + for (AudioRecordingConfiguration config : recordConfigs) { + if (config.getClientUid() == crc.getUid() + && !config.isClientSilenced()) { + crc.setRecordingActive(true); + break; + } + } + } + if (wasActive != crc.isActive()) { + updateCommunicationRoute = true; + } + } + if (updateCommunicationRoute) { + postUpdateCommunicationRouteClient("updateCommunicationRouteClientsActivity"); + } + } + } + } + @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) { synchronized (mDeviceStateLock) { return mDeviceInventory.getDeviceSensorUuid(device); diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 0c7f11f98809..b70e11ddcd91 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -1245,8 +1245,9 @@ public class AudioDeviceInventory { * @param btDevice the corresponding Bluetooth device when relevant. * @return false if an error was reported by AudioSystem */ - /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect, - boolean isForTesting, @Nullable BluetoothDevice btDevice) { + /*package*/ boolean handleDeviceConnection(@NonNull AudioDeviceAttributes attributes, + boolean connect, boolean isForTesting, + @Nullable BluetoothDevice btDevice) { int device = attributes.getInternalType(); String address = attributes.getAddress(); String deviceName = attributes.getName(); @@ -1297,6 +1298,7 @@ public class AudioDeviceInventory { AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); // always remove even if disconnection failed mConnectedDevices.remove(deviceKey); + mDeviceBroker.postCheckCommunicationDeviceRemoval(attributes); status = true; } if (status) { @@ -1801,8 +1803,9 @@ public class AudioDeviceInventory { // device to remove was visible by APM, update APM mDeviceBroker.clearAvrcpAbsoluteVolumeSupported(); - final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address), + AudioDeviceAttributes ada = new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + final int res = mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec); if (res != AudioSystem.AUDIO_STATUS_OK) { @@ -1816,11 +1819,13 @@ public class AudioDeviceInventory { "A2DP device addr=" + address + " made unavailable")).printLog(TAG)); } mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); + // Remove A2DP routes as well setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/); mmi.record(); updateBluetoothPreferredModes_l(null /*connectedDevice*/); purgeDevicesRoles_l(); + mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); } @GuardedBy("mDevicesLock") @@ -1855,12 +1860,14 @@ public class AudioDeviceInventory { @GuardedBy("mDevicesLock") private void makeA2dpSrcUnavailable(String address) { - mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( - AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), + AudioDeviceAttributes ada = new AudioDeviceAttributes( + AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); + mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.remove( DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); + mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); } @GuardedBy("mDevicesLock") @@ -1893,8 +1900,9 @@ public class AudioDeviceInventory { @GuardedBy("mDevicesLock") private void makeHearingAidDeviceUnavailable(String address) { - mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( - AudioSystem.DEVICE_OUT_HEARING_AID, address), + AudioDeviceAttributes ada = new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_HEARING_AID, address); + mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.remove( @@ -1906,6 +1914,7 @@ public class AudioDeviceInventory { .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID)) .record(); + mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); } /** @@ -2002,9 +2011,10 @@ public class AudioDeviceInventory { @GuardedBy("mDevicesLock") private void makeLeAudioDeviceUnavailableNow(String address, int device) { + AudioDeviceAttributes ada = null; if (device != AudioSystem.DEVICE_NONE) { - final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( - device, address), + ada = new AudioDeviceAttributes(device, address); + final int res = AudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); @@ -2024,6 +2034,9 @@ public class AudioDeviceInventory { setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/); updateBluetoothPreferredModes_l(null /*connectedDevice*/); purgeDevicesRoles_l(); + if (ada != null) { + mDeviceBroker.postCheckCommunicationDeviceRemoval(ada); + } } @GuardedBy("mDevicesLock") diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 53ed38edffe4..b70b2b3041e0 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4262,22 +4262,41 @@ public class AudioService extends IAudioService.Stub // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE // and request an audio mode update immediately. Upon any other change, queue the message // and request an audio mode update after a grace period. + updateAudioModeHandlers( + configs /* playbackConfigs */, null /* recordConfigs */); + mDeviceBroker.updateCommunicationRouteClientsActivity( + configs /* playbackConfigs */, null /* recordConfigs */); + } + + void updateAudioModeHandlers(List<AudioPlaybackConfiguration> playbackConfigs, + List<AudioRecordingConfiguration> recordConfigs) { synchronized (mDeviceBroker.mSetModeLock) { boolean updateAudioMode = false; int existingMsgPolicy = SENDMSG_QUEUE; int delay = CHECK_MODE_FOR_UID_PERIOD_MS; for (SetModeDeathHandler h : mSetModeDeathHandlers) { boolean wasActive = h.isActive(); - h.setPlaybackActive(false); - for (AudioPlaybackConfiguration config : configs) { - final int usage = config.getAudioAttributes().getUsage(); - if (config.getClientUid() == h.getUid() - && (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION + if (playbackConfigs != null) { + h.setPlaybackActive(false); + for (AudioPlaybackConfiguration config : playbackConfigs) { + final int usage = config.getAudioAttributes().getUsage(); + if (config.getClientUid() == h.getUid() + && (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) - && config.getPlayerState() - == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) { - h.setPlaybackActive(true); - break; + && config.isActive()) { + h.setPlaybackActive(true); + break; + } + } + } + if (recordConfigs != null) { + h.setRecordingActive(false); + for (AudioRecordingConfiguration config : recordConfigs) { + if (config.getClientUid() == h.getUid() && !config.isClientSilenced() + && config.getAudioSource() == AudioSource.VOICE_COMMUNICATION) { + h.setRecordingActive(true); + break; + } } } if (wasActive != h.isActive()) { @@ -4315,38 +4334,10 @@ public class AudioService extends IAudioService.Stub // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE // and request an audio mode update immediately. Upon any other change, queue the message // and request an audio mode update after a grace period. - synchronized (mDeviceBroker.mSetModeLock) { - boolean updateAudioMode = false; - int existingMsgPolicy = SENDMSG_QUEUE; - int delay = CHECK_MODE_FOR_UID_PERIOD_MS; - for (SetModeDeathHandler h : mSetModeDeathHandlers) { - boolean wasActive = h.isActive(); - h.setRecordingActive(false); - for (AudioRecordingConfiguration config : configs) { - if (config.getClientUid() == h.getUid() - && config.getAudioSource() == AudioSource.VOICE_COMMUNICATION) { - h.setRecordingActive(true); - break; - } - } - if (wasActive != h.isActive()) { - updateAudioMode = true; - if (h.isActive() && h == getAudioModeOwnerHandler()) { - existingMsgPolicy = SENDMSG_REPLACE; - delay = 0; - } - } - } - if (updateAudioMode) { - sendMsg(mAudioHandler, - MSG_UPDATE_AUDIO_MODE, - existingMsgPolicy, - AudioSystem.MODE_CURRENT, - android.os.Process.myPid(), - mContext.getPackageName(), - delay); - } - } + updateAudioModeHandlers( + null /* playbackConfigs */, configs /* recordConfigs */); + mDeviceBroker.updateCommunicationRouteClientsActivity( + null /* playbackConfigs */, configs /* recordConfigs */); } private void dumpAudioMode(PrintWriter pw) { @@ -6299,10 +6290,12 @@ public class AudioService extends IAudioService.Stub ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED) .record(); } - + final boolean isPrivileged = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED; final long ident = Binder.clearCallingIdentity(); try { - return mDeviceBroker.setCommunicationDevice(cb, pid, device, eventSource); + return mDeviceBroker.setCommunicationDevice(cb, uid, device, isPrivileged, eventSource); } finally { Binder.restoreCallingIdentity(ident); } @@ -6348,6 +6341,9 @@ public class AudioService extends IAudioService.Stub if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) { return; } + final boolean isPrivileged = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED; // for logging only final int uid = Binder.getCallingUid(); @@ -6363,9 +6359,10 @@ public class AudioService extends IAudioService.Stub .set(MediaMetrics.Property.STATE, on ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF) .record(); + final long ident = Binder.clearCallingIdentity(); try { - mDeviceBroker.setSpeakerphoneOn(cb, pid, on, eventSource); + mDeviceBroker.setSpeakerphoneOn(cb, uid, on, isPrivileged, eventSource); } finally { Binder.restoreCallingIdentity(ident); } @@ -6490,7 +6487,7 @@ public class AudioService extends IAudioService.Stub .set(MediaMetrics.Property.SCO_AUDIO_MODE, BtHelper.scoAudioModeToString(scoAudioMode)) .record(); - startBluetoothScoInt(cb, pid, scoAudioMode, eventSource); + startBluetoothScoInt(cb, uid, scoAudioMode, eventSource); } @@ -6513,10 +6510,10 @@ public class AudioService extends IAudioService.Stub .set(MediaMetrics.Property.SCO_AUDIO_MODE, BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL)) .record(); - startBluetoothScoInt(cb, pid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource); + startBluetoothScoInt(cb, uid, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource); } - void startBluetoothScoInt(IBinder cb, int pid, int scoAudioMode, @NonNull String eventSource) { + void startBluetoothScoInt(IBinder cb, int uid, int scoAudioMode, @NonNull String eventSource) { MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH) .set(MediaMetrics.Property.EVENT, "startBluetoothScoInt") .set(MediaMetrics.Property.SCO_AUDIO_MODE, @@ -6527,9 +6524,13 @@ public class AudioService extends IAudioService.Stub mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record(); return; } + final boolean isPrivileged = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED; final long ident = Binder.clearCallingIdentity(); try { - mDeviceBroker.startBluetoothScoForClient(cb, pid, scoAudioMode, eventSource); + mDeviceBroker.startBluetoothScoForClient( + cb, uid, scoAudioMode, isPrivileged, eventSource); } finally { Binder.restoreCallingIdentity(ident); } @@ -6547,9 +6548,12 @@ public class AudioService extends IAudioService.Stub final String eventSource = new StringBuilder("stopBluetoothSco()") .append(") from u/pid:").append(uid).append("/") .append(pid).toString(); + final boolean isPrivileged = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED; final long ident = Binder.clearCallingIdentity(); try { - mDeviceBroker.stopBluetoothScoForClient(cb, pid, eventSource); + mDeviceBroker.stopBluetoothScoForClient(cb, uid, isPrivileged, eventSource); } finally { Binder.restoreCallingIdentity(ident); } @@ -9284,8 +9288,8 @@ public class AudioService extends IAudioService.Stub break; } boolean wasActive = h.isActive(); - h.setPlaybackActive(mPlaybackMonitor.isPlaybackActiveForUid(h.getUid())); - h.setRecordingActive(mRecordMonitor.isRecordingActiveForUid(h.getUid())); + h.setPlaybackActive(isPlaybackActiveForUid(h.getUid())); + h.setRecordingActive(isRecordingActiveForUid(h.getUid())); if (wasActive != h.isActive()) { onUpdateAudioMode(AudioSystem.MODE_CURRENT, android.os.Process.myPid(), mContext.getPackageName(), false /*force*/); @@ -12378,6 +12382,16 @@ public class AudioService extends IAudioService.Stub } } + /* package */ + boolean isPlaybackActiveForUid(int uid) { + return mPlaybackMonitor.isPlaybackActiveForUid(uid); + } + + /* package */ + boolean isRecordingActiveForUid(int uid) { + return mRecordMonitor.isRecordingActiveForUid(uid); + } + //====================== // Audio device management //====================== diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 50ffbcb9f4c4..3f4f981dc0b8 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -957,6 +957,7 @@ public final class PlaybackActivityMonitor players = (HashMap<Integer, AudioPlaybackConfiguration>) mPlayers.clone(); } mFadingManager.unfadeOutUid(uid, players); + mDuckingManager.unduckUid(uid, players); } //================================================================= diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java index 652ea5228571..4332fddf3f83 100644 --- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java +++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java @@ -227,8 +227,8 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin synchronized (mRecordStates) { for (RecordingState state : mRecordStates) { // Note: isActiveConfiguration() == true => state.getConfig() != null - if (state.isActiveConfiguration() - && state.getConfig().getClientUid() == uid) { + if (state.isActiveConfiguration() && state.getConfig().getClientUid() == uid + && !state.getConfig().isClientSilenced()) { return true; } } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index eb7fa1069b7b..add94b1bf937 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -172,6 +172,7 @@ public final class DeviceStateManagerService extends SystemService { private DeviceState mRearDisplayState; // TODO(259328837) Generalize for all pending feature requests in the future + @GuardedBy("mLock") @Nullable private OverrideRequest mRearDisplayPendingOverrideRequest; @@ -779,7 +780,7 @@ public final class DeviceStateManagerService extends SystemService { * {@link StatusBarManagerInternal} to notify SystemUI to display the educational dialog. */ @GuardedBy("mLock") - private void showRearDisplayEducationalOverlayLocked(OverrideRequest request) { + private void showRearDisplayEducationalOverlayLocked(@NonNull OverrideRequest request) { mRearDisplayPendingOverrideRequest = request; StatusBarManagerInternal statusBar = @@ -844,8 +845,8 @@ public final class DeviceStateManagerService extends SystemService { * request if it was dismissed in a way that should cancel the feature. */ private void onStateRequestOverlayDismissedInternal(boolean shouldCancelRequest) { - if (mRearDisplayPendingOverrideRequest != null) { - synchronized (mLock) { + synchronized (mLock) { + if (mRearDisplayPendingOverrideRequest != null) { if (shouldCancelRequest) { ProcessRecord processRecord = mProcessRecords.get( mRearDisplayPendingOverrideRequest.getPid()); diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java index 47cde1517450..5b11cfe7ff06 100644 --- a/services/core/java/com/android/server/display/BrightnessRangeController.java +++ b/services/core/java/com/android/server/display/BrightnessRangeController.java @@ -18,29 +18,42 @@ package com.android.server.display; import android.hardware.display.BrightnessInfo; import android.os.IBinder; +import android.provider.DeviceConfigInterface; + +import com.android.server.display.feature.DeviceConfigParameterProvider; import java.io.PrintWriter; import java.util.function.BooleanSupplier; class BrightnessRangeController { - private static final boolean NBM_FEATURE_FLAG = false; - private final HighBrightnessModeController mHbmController; private final NormalBrightnessModeController mNormalBrightnessModeController = new NormalBrightnessModeController(); private final Runnable mModeChangeCallback; + private final boolean mUseNbmController; + BrightnessRangeController(HighBrightnessModeController hbmController, Runnable modeChangeCallback) { + this(hbmController, modeChangeCallback, + new DeviceConfigParameterProvider(DeviceConfigInterface.REAL)); + } + + BrightnessRangeController(HighBrightnessModeController hbmController, + Runnable modeChangeCallback, DeviceConfigParameterProvider configParameterProvider) { mHbmController = hbmController; mModeChangeCallback = modeChangeCallback; + mUseNbmController = configParameterProvider.isNormalBrightnessControllerFeatureEnabled(); } - void dump(PrintWriter pw) { + pw.println("BrightnessRangeController:"); + pw.println(" mUseNormalBrightnessController=" + mUseNbmController); mHbmController.dump(pw); + mNormalBrightnessModeController.dump(pw); + } void onAmbientLuxChange(float ambientLux) { @@ -90,7 +103,7 @@ class BrightnessRangeController { float getCurrentBrightnessMax() { - if (NBM_FEATURE_FLAG && mHbmController.getHighBrightnessMode() + if (mUseNbmController && mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) { return Math.min(mHbmController.getCurrentBrightnessMax(), mNormalBrightnessModeController.getCurrentBrightnessMax()); @@ -111,7 +124,7 @@ class BrightnessRangeController { } private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) { - if (NBM_FEATURE_FLAG) { + if (mUseNbmController) { boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean(); hbmChangesFunc.run(); // if nbm transition changed - trigger callback diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java index cfdcd636904b..c421ec04d6f5 100644 --- a/services/core/java/com/android/server/display/BrightnessThrottler.java +++ b/services/core/java/com/android/server/display/BrightnessThrottler.java @@ -22,7 +22,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.display.BrightnessInfo; -import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.HandlerExecutor; import android.os.IThermalEventListener; @@ -38,6 +37,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel; +import com.android.server.display.feature.DeviceConfigParameterProvider; import java.io.PrintWriter; import java.util.ArrayList; @@ -63,7 +63,7 @@ class BrightnessThrottler { private final Runnable mThrottlingChangeCallback; private final SkinThermalStatusObserver mSkinThermalStatusObserver; private final DeviceConfigListener mDeviceConfigListener; - private final DeviceConfigInterface mDeviceConfig; + private final DeviceConfigParameterProvider mConfigParameterProvider; private int mThrottlingStatus; @@ -118,7 +118,7 @@ class BrightnessThrottler { mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler); mUniqueDisplayId = uniqueDisplayId; - mDeviceConfig = injector.getDeviceConfig(); + mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig()); mDeviceConfigListener = new DeviceConfigListener(); mThermalBrightnessThrottlingDataId = throttlingDataId; mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap; @@ -145,7 +145,7 @@ class BrightnessThrottler { void stop() { mSkinThermalStatusObserver.stopObserving(); - mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener); + mConfigParameterProvider.removeOnPropertiesChangedListener(mDeviceConfigListener); // We're asked to stop throttling, so reset brightness restrictions. mBrightnessCap = PowerManager.BRIGHTNESS_MAX; mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; @@ -248,12 +248,6 @@ class BrightnessThrottler { mSkinThermalStatusObserver.dump(pw); } - private String getThermalBrightnessThrottlingDataString() { - return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, - /* defaultValue= */ null); - } - // The brightness throttling data id may or may not be specified in the string that is passed // in, if there is none specified, we assume it is for the default case. Each string passed in // here must be for one display and one throttling id. @@ -318,7 +312,8 @@ class BrightnessThrottler { private void loadThermalBrightnessThrottlingDataFromDeviceConfig() { HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> tempThrottlingData = new HashMap<>(1); - mThermalBrightnessThrottlingDataString = getThermalBrightnessThrottlingDataString(); + mThermalBrightnessThrottlingDataString = + mConfigParameterProvider.getBrightnessThrottlingData(); boolean validConfig = true; mThermalBrightnessThrottlingDataOverride.clear(); if (mThermalBrightnessThrottlingDataString != null) { @@ -390,8 +385,7 @@ class BrightnessThrottler { public Executor mExecutor = new HandlerExecutor(mDeviceConfigHandler); public void startListening() { - mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - mExecutor, this); + mConfigParameterProvider.addOnPropertiesChangedListener(mExecutor, this); } @Override diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 858800a82151..dbe15b6e2da8 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -24,7 +24,6 @@ import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; import static android.hardware.display.DisplayManager.EventsMask; -import static android.hardware.display.DisplayManager.HDR_OUTPUT_CONTROL_FLAG; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; @@ -42,7 +41,6 @@ import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL; import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPORTED; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.ROOT_UID; -import static android.provider.DeviceConfig.NAMESPACE_DISPLAY_MANAGER; import android.Manifest; import android.annotation.NonNull; @@ -116,7 +114,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; -import android.provider.DeviceConfig; +import android.provider.DeviceConfigInterface; import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; @@ -152,6 +150,7 @@ import com.android.server.SystemService; import com.android.server.UiThread; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.display.DisplayDeviceConfig.SensorData; +import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.layout.Layout; import com.android.server.display.mode.DisplayModeDirector; import com.android.server.display.utils.SensorUtils; @@ -506,6 +505,8 @@ public final class DisplayManagerService extends SystemService { private final BrightnessSynchronizer mBrightnessSynchronizer; + private final DeviceConfigParameterProvider mConfigParameterProvider; + /** * Applications use {@link android.view.Display#getRefreshRate} and * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate. @@ -558,6 +559,7 @@ public final class DisplayManagerService extends SystemService { mWideColorSpace = colorSpaces[1]; mOverlayProperties = SurfaceControl.getOverlaySupport(); mSystemReady = false; + mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); } public void setupSchedulerPolicies() { @@ -694,11 +696,11 @@ public final class DisplayManagerService extends SystemService { synchronized (mSyncRoot) { mSafeMode = safeMode; mSystemReady = true; - mIsHdrOutputControlEnabled = isDeviceConfigHdrOutputControlEnabled(); - DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_DISPLAY_MANAGER, - BackgroundThread.getExecutor(), + mIsHdrOutputControlEnabled = + mConfigParameterProvider.isHdrOutputControlFeatureEnabled(); + mConfigParameterProvider.addOnPropertiesChangedListener(BackgroundThread.getExecutor(), properties -> mIsHdrOutputControlEnabled = - isDeviceConfigHdrOutputControlEnabled()); + mConfigParameterProvider.isHdrOutputControlFeatureEnabled()); // Just in case the top inset changed before the system was ready. At this point, any // relevant configuration should be in place. recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY)); @@ -729,12 +731,6 @@ public final class DisplayManagerService extends SystemService { mContext.registerReceiver(mIdleModeReceiver, filter); } - private boolean isDeviceConfigHdrOutputControlEnabled() { - return DeviceConfig.getBoolean(NAMESPACE_DISPLAY_MANAGER, - HDR_OUTPUT_CONTROL_FLAG, - true); - } - @VisibleForTesting Handler getDisplayHandler() { return mHandler; @@ -3158,8 +3154,7 @@ public final class DisplayManagerService extends SystemService { + "display: " + display.getDisplayIdLocked()); return null; } - if (DeviceConfig.getBoolean("display_manager", - "use_newly_structured_display_power_controller", true)) { + if (mConfigParameterProvider.isNewPowerControllerFeatureEnabled()) { displayPowerController = new DisplayPowerController2( mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler, mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 7701bc6271ae..89d865e5ae39 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -496,7 +496,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { private void loadDisplayDeviceConfig() { // Load display device config final Context context = getOverlayContext(); - mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId, + mDisplayDeviceConfig = mInjector.createDisplayDeviceConfig(context, mPhysicalDisplayId, mIsFirstDisplay); // Load brightness HWC quirk @@ -1336,6 +1336,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { public SurfaceControlProxy getSurfaceControlProxy() { return new SurfaceControlProxy(); } + + public DisplayDeviceConfig createDisplayDeviceConfig(Context context, + long physicalDisplayId, boolean isFirstDisplay) { + return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay); + } } public interface DisplayEventListener { diff --git a/services/core/java/com/android/server/display/NormalBrightnessModeController.java b/services/core/java/com/android/server/display/NormalBrightnessModeController.java index dbabc2441224..135ebd8f4fbf 100644 --- a/services/core/java/com/android/server/display/NormalBrightnessModeController.java +++ b/services/core/java/com/android/server/display/NormalBrightnessModeController.java @@ -21,6 +21,7 @@ import android.os.PowerManager; import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType; +import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; @@ -60,6 +61,14 @@ class NormalBrightnessModeController { return recalculateMaxBrightness(); } + void dump(PrintWriter pw) { + pw.println("NormalBrightnessModeController:"); + pw.println(" mAutoBrightnessEnabled=" + mAutoBrightnessEnabled); + pw.println(" mAmbientLux=" + mAmbientLux); + pw.println(" mMaxBrightness=" + mMaxBrightness); + pw.println(" mMaxBrightnessLimits=" + mMaxBrightnessLimits); + } + private boolean recalculateMaxBrightness() { float foundAmbientBoundary = Float.MAX_VALUE; float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX; diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java new file mode 100644 index 000000000000..feebdf1b9799 --- /dev/null +++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.feature; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.display.DisplayManager; +import android.provider.DeviceConfig; +import android.provider.DeviceConfigInterface; +import android.util.Slog; + +import java.util.concurrent.Executor; + +/** + * Helper class to access all DeviceConfig features for display_manager namespace + * + **/ +public class DeviceConfigParameterProvider { + + private static final String TAG = "DisplayFeatureProvider"; + + private final DeviceConfigInterface mDeviceConfig; + + public DeviceConfigParameterProvider(DeviceConfigInterface deviceConfig) { + mDeviceConfig = deviceConfig; + } + + // feature: revamping_display_power_controller_feature + // parameter: use_newly_structured_display_power_controller + public boolean isNewPowerControllerFeatureEnabled() { + return mDeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_NEW_POWER_CONTROLLER, true); + } + + // feature: hdr_output_control + // parameter: enable_hdr_output_control + public boolean isHdrOutputControlFeatureEnabled() { + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.HDR_OUTPUT_CONTROL_FLAG, true); + } + + // feature: flexible_brightness_range_feature + // parameter: normal_brightness_mode_controller_enabled + public boolean isNormalBrightnessControllerFeatureEnabled() { + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER, false); + } + + // feature: smooth_display_feature + // parameter: peak_refresh_rate_default + public float getPeakRefreshRateDefault() { + return mDeviceConfig.getFloat(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1); + } + + // Test parameters + // usage e.g.: adb shell device_config put display_manager refresh_rate_in_hbm_sunlight 90 + + // allows to customize brightness throttling data + public String getBrightnessThrottlingData() { + return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, null); + } + + public int getRefreshRateInHbmSunlight() { + return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, -1); + } + + public int getRefreshRateInHbmHdr() { + return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR, -1); + } + + + public int getRefreshRateInHighZone() { + return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, -1); + } + + public int getRefreshRateInLowZone() { + return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1); + } + + /** Return null if no such property or wrong format (not comma separated integers). */ + @Nullable + public int[] getHighAmbientBrightnessThresholds() { + return getIntArrayProperty(DisplayManager.DeviceConfig + .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS); + } + + /** Return null if no such property or wrong format (not comma separated integers). */ + @Nullable + public int[] getHighDisplayBrightnessThresholds() { + return getIntArrayProperty(DisplayManager.DeviceConfig + .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS); + } + + /** Return null if no such property or wrong format (not comma separated integers). */ + @Nullable + public int[] getLowDisplayBrightnessThresholds() { + return getIntArrayProperty(DisplayManager.DeviceConfig + .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS); + } + + /** Return null if no such property or wrong format (not comma separated integers). */ + @Nullable + public int[] getLowAmbientBrightnessThresholds() { + return getIntArrayProperty(DisplayManager.DeviceConfig + .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS); + } + + /** add property change listener to DeviceConfig */ + public void addOnPropertiesChangedListener(Executor executor, + DeviceConfig.OnPropertiesChangedListener listener) { + mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + executor, listener); + } + + /** remove property change listener from DeviceConfig */ + public void removeOnPropertiesChangedListener( + DeviceConfig.OnPropertiesChangedListener listener) { + mDeviceConfig.removeOnPropertiesChangedListener(listener); + } + + @Nullable + private int[] getIntArrayProperty(String prop) { + String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop, + null); + + if (strArray != null) { + return parseIntArray(strArray); + } + return null; + } + + @Nullable + private int[] parseIntArray(@NonNull String strArray) { + String[] items = strArray.split(","); + int[] array = new int[items.length]; + + try { + for (int i = 0; i < array.length; i++) { + array[i] = Integer.parseInt(items[i]); + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e); + array = null; + } + + return array; + } +} diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 18895788e4ec..11e35ce09c28 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -20,6 +20,7 @@ import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLU import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE; import static android.os.PowerManager.BRIGHTNESS_INVALID; +import android.annotation.IntegerRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; @@ -68,6 +69,7 @@ import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.utils.AmbientFilter; import com.android.server.display.utils.AmbientFilterFactory; import com.android.server.display.utils.SensorUtils; @@ -84,6 +86,7 @@ import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.concurrent.Callable; +import java.util.function.IntSupplier; /** * The DisplayModeDirector is responsible for determining what modes are allowed to be automatically @@ -117,7 +120,7 @@ public class DisplayModeDirector { private final SensorObserver mSensorObserver; private final HbmObserver mHbmObserver; private final SkinThermalStatusObserver mSkinThermalStatusObserver; - private final DeviceConfigInterface mDeviceConfig; + private final DeviceConfigParameterProvider mConfigParameterProvider; private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; @GuardedBy("mLock") @@ -157,7 +160,7 @@ public class DisplayModeDirector { mSupportedModesByDisplay = new SparseArray<>(); mDefaultModeByDisplay = new SparseArray<>(); mAppRequestObserver = new AppRequestObserver(); - mDeviceConfig = injector.getDeviceConfig(); + mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig()); mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings(); mSettingsObserver = new SettingsObserver(context, handler); mBrightnessObserver = new BrightnessObserver(context, handler, injector); @@ -681,9 +684,9 @@ public class DisplayModeDirector { synchronized (mLock) { mDefaultDisplayDeviceConfig = displayDeviceConfig; mSettingsObserver.setRefreshRates(displayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ true); + /* attemptReadFromFeatureParams= */ true); mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ true); + /* attemptReadFromFeatureParams= */ true); mBrightnessObserver.reloadLightSensor(displayDeviceConfig); mHbmObserver.setupHdrRefreshRates(displayDeviceConfig); } @@ -1087,7 +1090,7 @@ public class DisplayModeDirector { // reading from the DeviceConfig is an intensive IO operation and having it in the // startup phase where we thrive to keep the latency very low has significant impact. setRefreshRates(/* displayDeviceConfig= */ null, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); } /** @@ -1095,8 +1098,8 @@ public class DisplayModeDirector { * if missing from DisplayDeviceConfig, and finally fallback to config.xml. */ public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - setDefaultPeakRefreshRate(displayDeviceConfig, attemptLoadingFromDeviceConfig); + boolean attemptReadFromFeatureParams) { + setDefaultPeakRefreshRate(displayDeviceConfig, attemptReadFromFeatureParams); mDefaultRefreshRate = (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger( R.integer.config_defaultRefreshRate) @@ -1113,9 +1116,9 @@ public class DisplayModeDirector { cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/, this); - Float deviceConfigDefaultPeakRefresh = - mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate(); - if (deviceConfigDefaultPeakRefresh != null) { + float deviceConfigDefaultPeakRefresh = + mConfigParameterProvider.getPeakRefreshRateDefault(); + if (deviceConfigDefaultPeakRefresh != -1) { mDefaultPeakRefreshRate = deviceConfigDefaultPeakRefresh; } @@ -1137,7 +1140,7 @@ public class DisplayModeDirector { synchronized (mLock) { if (defaultPeakRefreshRate == null) { setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); updateRefreshRateSettingLocked(); } else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) { mDefaultPeakRefreshRate = defaultPeakRefreshRate; @@ -1171,18 +1174,17 @@ public class DisplayModeDirector { } private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - Float defaultPeakRefreshRate = null; + boolean attemptReadFromFeatureParams) { + float defaultPeakRefreshRate = -1; - if (attemptLoadingFromDeviceConfig) { + if (attemptReadFromFeatureParams) { try { - defaultPeakRefreshRate = - mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate(); + defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault(); } catch (Exception exception) { // Do nothing } } - if (defaultPeakRefreshRate == null) { + if (defaultPeakRefreshRate == -1) { defaultPeakRefreshRate = (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger( R.integer.config_defaultPeakRefreshRate) @@ -1528,7 +1530,7 @@ public class DisplayModeDirector { mHandler = handler; mInjector = injector; updateBlockingZoneThresholds(/* displayDeviceConfig= */ null, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); mRefreshRateInHighZone = context.getResources().getInteger( R.integer.config_fixedRefreshRateInHighZone); } @@ -1537,10 +1539,10 @@ public class DisplayModeDirector { * This is used to update the blocking zone thresholds from the DeviceConfig, which * if missing from DisplayDeviceConfig, and finally fallback to config.xml. */ - public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - loadLowBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig); - loadHighBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig); + public void updateBlockingZoneThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig, + boolean attemptReadFromFeatureParams) { + loadLowBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams); + loadHighBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams); } @VisibleForTesting @@ -1579,20 +1581,20 @@ public class DisplayModeDirector { return mRefreshRateInLowZone; } - private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - loadRefreshRateInHighZone(displayDeviceConfig, attemptLoadingFromDeviceConfig); - loadRefreshRateInLowZone(displayDeviceConfig, attemptLoadingFromDeviceConfig); + private void loadLowBrightnessThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig, + boolean attemptReadFromFeatureParams) { + loadRefreshRateInHighZone(displayDeviceConfig, attemptReadFromFeatureParams); + loadRefreshRateInLowZone(displayDeviceConfig, attemptReadFromFeatureParams); mLowDisplayBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(), + () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(), () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(), R.array.config_brightnessThresholdsOfPeakRefreshRate, - displayDeviceConfig, attemptLoadingFromDeviceConfig); + displayDeviceConfig, attemptReadFromFeatureParams); mLowAmbientBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(), + () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(), () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(), R.array.config_ambientThresholdsOfPeakRefreshRate, - displayDeviceConfig, attemptLoadingFromDeviceConfig); + displayDeviceConfig, attemptReadFromFeatureParams); if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) { throw new RuntimeException("display low brightness threshold array and ambient " + "brightness threshold array have different length: " @@ -1604,55 +1606,55 @@ public class DisplayModeDirector { } private void loadRefreshRateInLowZone(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - int refreshRateInLowZone = - (displayDeviceConfig == null) ? mContext.getResources().getInteger( - R.integer.config_defaultRefreshRateInZone) - : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(); - if (attemptLoadingFromDeviceConfig) { + boolean attemptReadFromFeatureParams) { + int refreshRateInLowZone = -1; + if (attemptReadFromFeatureParams) { try { - refreshRateInLowZone = mDeviceConfig.getInt( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, - refreshRateInLowZone); + refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone(); } catch (Exception exception) { // Do nothing } } + if (refreshRateInLowZone == -1) { + refreshRateInLowZone = (displayDeviceConfig == null) + ? mContext.getResources().getInteger( + R.integer.config_defaultRefreshRateInZone) + : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(); + } mRefreshRateInLowZone = refreshRateInLowZone; } private void loadRefreshRateInHighZone(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - int refreshRateInHighZone = - (displayDeviceConfig == null) ? mContext.getResources().getInteger( - R.integer.config_fixedRefreshRateInHighZone) : displayDeviceConfig - .getDefaultHighBlockingZoneRefreshRate(); - if (attemptLoadingFromDeviceConfig) { + boolean attemptReadFromFeatureParams) { + int refreshRateInHighZone = -1; + if (attemptReadFromFeatureParams) { try { - refreshRateInHighZone = mDeviceConfig.getInt( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, - refreshRateInHighZone); + refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone(); } catch (Exception exception) { // Do nothing } } + if (refreshRateInHighZone == -1) { + refreshRateInHighZone = (displayDeviceConfig == null) + ? mContext.getResources().getInteger( + R.integer.config_fixedRefreshRateInHighZone) + : displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(); + } mRefreshRateInHighZone = refreshRateInHighZone; } private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { + boolean attemptReadFromFeatureParams) { mHighDisplayBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(), + () -> mConfigParameterProvider.getHighDisplayBrightnessThresholds(), () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(), R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate, - displayDeviceConfig, attemptLoadingFromDeviceConfig); + displayDeviceConfig, attemptReadFromFeatureParams); mHighAmbientBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(), + () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(), () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(), R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate, - displayDeviceConfig, attemptLoadingFromDeviceConfig); + displayDeviceConfig, attemptReadFromFeatureParams); if (mHighDisplayBrightnessThresholds.length != mHighAmbientBrightnessThresholds.length) { throw new RuntimeException("display high brightness threshold array and ambient " @@ -1668,10 +1670,10 @@ public class DisplayModeDirector { Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable, Callable<int[]> loadFromDisplayDeviceConfigCallable, int brightnessThresholdOfFixedRefreshRateKey, - DisplayDeviceConfig displayDeviceConfig, boolean attemptLoadingFromDeviceConfig) { + DisplayDeviceConfig displayDeviceConfig, boolean attemptReadFromFeatureParams) { int[] brightnessThresholds = null; - if (attemptLoadingFromDeviceConfig) { + if (attemptReadFromFeatureParams) { try { brightnessThresholds = loadFromDeviceConfigDisplaySettingsCallable.call(); @@ -1715,9 +1717,9 @@ public class DisplayModeDirector { // DeviceConfig is accessible after system ready. int[] lowDisplayBrightnessThresholds = - mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(); + mConfigParameterProvider.getLowDisplayBrightnessThresholds(); int[] lowAmbientBrightnessThresholds = - mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(); + mConfigParameterProvider.getLowAmbientBrightnessThresholds(); if (lowDisplayBrightnessThresholds != null && lowAmbientBrightnessThresholds != null && lowDisplayBrightnessThresholds.length @@ -1727,9 +1729,9 @@ public class DisplayModeDirector { } int[] highDisplayBrightnessThresholds = - mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(); + mConfigParameterProvider.getHighDisplayBrightnessThresholds(); int[] highAmbientBrightnessThresholds = - mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(); + mConfigParameterProvider.getHighAmbientBrightnessThresholds(); if (highDisplayBrightnessThresholds != null && highAmbientBrightnessThresholds != null && highDisplayBrightnessThresholds.length @@ -1738,14 +1740,12 @@ public class DisplayModeDirector { mHighAmbientBrightnessThresholds = highAmbientBrightnessThresholds; } - final int refreshRateInLowZone = mDeviceConfigDisplaySettings - .getRefreshRateInLowZone(); + final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone(); if (refreshRateInLowZone != -1) { mRefreshRateInLowZone = refreshRateInLowZone; } - final int refreshRateInHighZone = mDeviceConfigDisplaySettings - .getRefreshRateInHighZone(); + final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone(); if (refreshRateInHighZone != -1) { mRefreshRateInHighZone = refreshRateInHighZone; } @@ -1799,15 +1799,15 @@ public class DisplayModeDirector { displayDeviceConfig = mDefaultDisplayDeviceConfig; } mLowDisplayBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(), + () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(), () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(), R.array.config_brightnessThresholdsOfPeakRefreshRate, - displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + displayDeviceConfig, /* attemptReadFromFeatureParams= */ false); mLowAmbientBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(), + () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(), () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(), R.array.config_ambientThresholdsOfPeakRefreshRate, - displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + displayDeviceConfig, /* attemptReadFromFeatureParams= */ false); } restartObserver(); } @@ -1822,7 +1822,7 @@ public class DisplayModeDirector { // from there. synchronized (mLock) { loadRefreshRateInLowZone(mDefaultDisplayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); } restartObserver(); } else if (refreshRate != mRefreshRateInLowZone) { @@ -1843,15 +1843,15 @@ public class DisplayModeDirector { displayDeviceConfig = mDefaultDisplayDeviceConfig; } mHighDisplayBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(), + () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(), () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(), R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate, - displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + displayDeviceConfig, /* attemptReadFromFeatureParams= */ false); mHighAmbientBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(), + () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(), () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(), R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate, - displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + displayDeviceConfig, /* attemptReadFromFeatureParams= */ false); } restartObserver(); } @@ -1866,7 +1866,7 @@ public class DisplayModeDirector { // from there. synchronized (mLock) { loadRefreshRateInHighZone(mDefaultDisplayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); } restartObserver(); } else if (refreshRate != mRefreshRateInHighZone) { @@ -2675,113 +2675,55 @@ public class DisplayModeDirector { private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener { public void startListening() { - mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + mConfigParameterProvider.addOnPropertiesChangedListener( BackgroundThread.getExecutor(), this); } - /* - * Return null if no such property or wrong format (not comma separated integers). - */ - public int[] getLowDisplayBrightnessThresholds() { - return getIntArrayProperty( - DisplayManager.DeviceConfig - .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS); - } - - /* - * Return null if no such property or wrong format (not comma separated integers). - */ - public int[] getLowAmbientBrightnessThresholds() { - return getIntArrayProperty( - DisplayManager.DeviceConfig - .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS); - } - - public int getRefreshRateInLowZone() { - return mDeviceConfig.getInt( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1); - - } - - /* - * Return null if no such property or wrong format (not comma separated integers). - */ - public int[] getHighDisplayBrightnessThresholds() { - return getIntArrayProperty( - DisplayManager.DeviceConfig - .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS); - } - - /* - * Return null if no such property or wrong format (not comma separated integers). - */ - public int[] getHighAmbientBrightnessThresholds() { - return getIntArrayProperty( - DisplayManager.DeviceConfig - .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS); + private int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) { + return getRefreshRate( + () -> mConfigParameterProvider.getRefreshRateInHbmHdr(), + () -> displayDeviceConfig.getDefaultRefreshRateInHbmHdr(), + R.integer.config_defaultRefreshRateInHbmHdr, + displayDeviceConfig + ); } - public int getRefreshRateInHighZone() { - return mDeviceConfig.getInt( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, - -1); + private int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) { + return getRefreshRate( + () -> mConfigParameterProvider.getRefreshRateInHbmSunlight(), + () -> displayDeviceConfig.getDefaultRefreshRateInHbmSunlight(), + R.integer.config_defaultRefreshRateInHbmSunlight, + displayDeviceConfig + ); } - public int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) { - int refreshRate = - (displayDeviceConfig == null) ? mContext.getResources().getInteger( - R.integer.config_defaultRefreshRateInHbmHdr) - : displayDeviceConfig.getDefaultRefreshRateInHbmHdr(); + private int getRefreshRate(IntSupplier fromConfigPram, IntSupplier fromDisplayDeviceConfig, + @IntegerRes int configKey, DisplayDeviceConfig displayDeviceConfig) { + int refreshRate = -1; try { - refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR, - refreshRate); - } catch (NullPointerException e) { + refreshRate = fromConfigPram.getAsInt(); + } catch (NullPointerException npe) { // Do Nothing } - return refreshRate; - } - - public int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) { - int refreshRate = - (displayDeviceConfig == null) ? mContext.getResources() - .getInteger(R.integer.config_defaultRefreshRateInHbmSunlight) - : displayDeviceConfig.getDefaultRefreshRateInHbmSunlight(); - try { - refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, - refreshRate); - } catch (NullPointerException e) { - // Do Nothing + if (refreshRate == -1) { + refreshRate = (displayDeviceConfig == null) + ? mContext.getResources().getInteger(configKey) + : fromDisplayDeviceConfig.getAsInt(); } return refreshRate; } - /* - * Return null if no such property - */ - public Float getDefaultPeakRefreshRate() { - float defaultPeakRefreshRate = mDeviceConfig.getFloat( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1); - - if (defaultPeakRefreshRate == -1) { - return null; - } - return defaultPeakRefreshRate; - } - @Override public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { - Float defaultPeakRefreshRate = getDefaultPeakRefreshRate(); + float defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault(); mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED, - defaultPeakRefreshRate).sendToTarget(); + defaultPeakRefreshRate == -1 ? null : defaultPeakRefreshRate).sendToTarget(); - int[] lowDisplayBrightnessThresholds = getLowDisplayBrightnessThresholds(); - int[] lowAmbientBrightnessThresholds = getLowAmbientBrightnessThresholds(); - final int refreshRateInLowZone = getRefreshRateInLowZone(); + int[] lowDisplayBrightnessThresholds = + mConfigParameterProvider.getLowDisplayBrightnessThresholds(); + int[] lowAmbientBrightnessThresholds = + mConfigParameterProvider.getLowAmbientBrightnessThresholds(); + final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone(); mHandler.obtainMessage(MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED, new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds)) @@ -2790,9 +2732,11 @@ public class DisplayModeDirector { mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone, 0).sendToTarget(); - int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds(); - int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds(); - final int refreshRateInHighZone = getRefreshRateInHighZone(); + int[] highDisplayBrightnessThresholds = + mConfigParameterProvider.getHighDisplayBrightnessThresholds(); + int[] highAmbientBrightnessThresholds = + mConfigParameterProvider.getHighAmbientBrightnessThresholds(); + final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone(); mHandler.obtainMessage(MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED, new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds)) @@ -2814,33 +2758,6 @@ public class DisplayModeDirector { .sendToTarget(); } } - - private int[] getIntArrayProperty(String prop) { - String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop, - null); - - if (strArray != null) { - return parseIntArray(strArray); - } - - return null; - } - - private int[] parseIntArray(@NonNull String strArray) { - String[] items = strArray.split(","); - int[] array = new int[items.length]; - - try { - for (int i = 0; i < array.length; i++) { - array[i] = Integer.parseInt(items[i]); - } - } catch (NumberFormatException e) { - Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e); - array = null; - } - - return array; - } } interface Injector { diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index 6d70d21e3b84..633bf73120e1 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -16,11 +16,11 @@ package com.android.server.dreams; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import android.app.ActivityTaskManager; import android.app.BroadcastOptions; +import android.app.IAppTask; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -214,6 +214,27 @@ final class DreamController { } /** + * Provides an appTask for the dream with token {@code dreamToken}, so that the dream controller + * can stop the dream task when necessary. + */ + void setDreamAppTask(Binder dreamToken, IAppTask appTask) { + if (mCurrentDream == null || mCurrentDream.mToken != dreamToken + || mCurrentDream.mAppTask != null) { + Slog.e(TAG, "Illegal dream activity start. mCurrentDream.mToken = " + + mCurrentDream.mToken + ", illegal dreamToken = " + dreamToken + + ". Ending this dream activity."); + try { + appTask.finishAndRemoveTask(); + } catch (RemoteException | RuntimeException e) { + Slog.e(TAG, "Unable to stop illegal dream activity."); + } + return; + } + + mCurrentDream.mAppTask = appTask; + } + + /** * Stops dreaming. * * The current dream, if any, and any unstopped previous dreams are stopped. The device stops @@ -303,8 +324,14 @@ final class DreamController { mSentStartBroadcast = false; } - mActivityTaskManager.removeRootTasksWithActivityTypes( - new int[] {ACTIVITY_TYPE_DREAM}); + if (mCurrentDream != null && mCurrentDream.mAppTask != null) { + // Finish the dream task in case it hasn't finished by itself already. + try { + mCurrentDream.mAppTask.finishAndRemoveTask(); + } catch (RemoteException | RuntimeException e) { + Slog.e(TAG, "Unable to stop dream activity."); + } + } mListener.onDreamStopped(dream.mToken); } @@ -364,6 +391,7 @@ final class DreamController { public final boolean mIsPreviewMode; public final boolean mCanDoze; public final int mUserId; + public IAppTask mAppTask; public PowerManager.WakeLock mWakeLock; public boolean mBound; diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 68cf59f0ae1f..d88fe8a6c201 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -27,6 +27,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.IAppTask; import android.app.TaskInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -37,6 +38,7 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManagerInternal; import android.content.pm.ServiceInfo; import android.database.ContentObserver; import android.hardware.display.AmbientDisplayConfiguration; @@ -116,6 +118,7 @@ public final class DreamManagerService extends SystemService { private final PowerManagerInternal mPowerManagerInternal; private final PowerManager.WakeLock mDozeWakeLock; private final ActivityTaskManagerInternal mAtmInternal; + private final PackageManagerInternal mPmInternal; private final UserManager mUserManager; private final UiEventLogger mUiEventLogger; private final DreamUiEventLogger mDreamUiEventLogger; @@ -216,6 +219,7 @@ public final class DreamManagerService extends SystemService { mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mPowerManagerInternal = getLocalService(PowerManagerInternal.class); mAtmInternal = getLocalService(ActivityTaskManagerInternal.class); + mPmInternal = getLocalService(PackageManagerInternal.class); mUserManager = context.getSystemService(UserManager.class); mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, DOZE_WAKE_LOCK_TAG); mDozeConfig = new AmbientDisplayConfiguration(mContext); @@ -1064,6 +1068,64 @@ public final class DreamManagerService extends SystemService { Binder.restoreCallingIdentity(ident); } } + + @Override // Binder call + public void startDreamActivity(@NonNull Intent intent) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + // We post here, because startDreamActivity and setDreamAppTask have to run + // synchronously and DreamController#setDreamAppTask has to run on mHandler. + mHandler.post(() -> { + final Binder dreamToken; + final String dreamPackageName; + synchronized (mLock) { + if (mCurrentDream == null) { + Slog.e(TAG, "Attempt to start DreamActivity, but the device is not " + + "dreaming. Aborting without starting the DreamActivity."); + return; + } + dreamToken = mCurrentDream.token; + dreamPackageName = mCurrentDream.name.getPackageName(); + } + + if (!canLaunchDreamActivity(dreamPackageName, intent.getPackage(), + callingUid)) { + Slog.e(TAG, "The dream activity can be started only when the device is dreaming" + + " and only by the active dream package."); + return; + } + + final IAppTask appTask = mAtmInternal.startDreamActivity(intent, callingUid, + callingPid); + if (appTask == null) { + Slog.e(TAG, "Could not start dream activity."); + stopDreamInternal(true, "DreamActivity not started"); + return; + } + mController.setDreamAppTask(dreamToken, appTask); + }); + } + + boolean canLaunchDreamActivity(String dreamPackageName, String packageName, + int callingUid) { + if (dreamPackageName == null || packageName == null) { + Slog.e(TAG, "Cannot launch dream activity due to invalid state. dream component= " + + dreamPackageName + ", packageName=" + packageName); + return false; + } + if (!mPmInternal.isSameApp(packageName, callingUid, UserHandle.getUserId(callingUid))) { + Slog.e(TAG, "Cannot launch dream activity because package=" + + packageName + " does not match callingUid=" + callingUid); + return false; + } + if (packageName.equals(dreamPackageName)) { + return true; + } + Slog.e(TAG, "Dream packageName does not match active dream. Package " + packageName + + " does not match " + dreamPackageName); + return false; + } + } private final class LocalService extends DreamManagerInternal { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index b0b1d676bc4b..ba9e280be49d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -352,7 +352,6 @@ final class InputMethodBindingController { clearCurMethodAndSessions(); mService.clearInputShownLocked(); mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME); - mService.resetSystemUiLocked(); } } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 4dbd82065a66..1ab83f7c5fe5 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -48,6 +48,7 @@ import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState; import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult; +import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME; import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT; import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed; @@ -633,9 +634,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private InputMethodSubtype mCurrentSubtype; /** - * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController}. + * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController} */ - @GuardedBy("ImfLock.class") private boolean mCurPerceptible; /** @@ -749,26 +749,33 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub SparseArray<AccessibilitySessionState> mEnabledAccessibilitySessions = new SparseArray<>(); /** - * {@code true} if the device is currently interactive with the user, initially true. - * - * @see #handleSetInteractive + * True if the device is currently interactive with user. The value is true initially. */ - @GuardedBy("ImfLock.class") boolean mIsInteractive = true; - @GuardedBy("ImfLock.class") - @InputMethodService.BackDispositionMode int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; /** - * The {@link InputMethodService.ImeWindowVisibility} of the currently bound IME, - * or {@code 0} if no IME is bound. + * A set of status bits regarding the active IME. * - * <p><em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and + * <p>This value is a combination of following two bits:</p> + * <dl> + * <dt>{@link InputMethodService#IME_ACTIVE}</dt> + * <dd> + * If this bit is ON, connected IME is ready to accept touch/key events. + * </dd> + * <dt>{@link InputMethodService#IME_VISIBLE}</dt> + * <dd> + * If this bit is ON, some of IME view, e.g. software input, candidate view, is visible. + * </dd> + * <dt>{@link InputMethodService#IME_INVISIBLE}</dt> + * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is + * currently invisible. + * </dd> + * </dl> + * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and * {@link InputMethodBindingController#unbindCurrentMethod()}.</em> */ - @GuardedBy("ImfLock.class") - @InputMethodService.ImeWindowVisibility int mImeWindowVis; private LocaleList mLastSystemLocales; @@ -1529,6 +1536,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Uh oh, current input method is no longer around! // Pick another one... Slog.i(TAG, "Current input method removed: " + curInputMethodId); + updateSystemUiLocked(0 /* vis */, mBackDisposition); if (!chooseNewDefaultIMELocked()) { changed = true; curIm = null; @@ -2360,6 +2368,28 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } + /** + * Called when {@link #resetCurrentMethodAndClientLocked(int)} invoked for clean-up states + * before unbinding the current method. + */ + @GuardedBy("ImfLock.class") + void onUnbindCurrentMethodByReset() { + final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull( + mCurFocusedWindow); + if (winState != null && !winState.isRequestedImeVisible() + && !mVisibilityStateComputer.isInputShown()) { + // Normally, the focus window will apply the IME visibility state to + // WindowManager when the IME has applied it. But it would be too late when + // switching IMEs in between different users. (Since the focused IME will + // first unbind the service to switch to bind the next user of the IME + // service, that wouldn't make the attached IME token validity check in time) + // As a result, we have to notify WM to apply IME visibility before clearing the + // binding states in the first place. + mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, mCurStatsToken, + STATE_HIDE_IME); + } + } + /** {@code true} when a {@link ClientState} has attached from starting the input connection. */ @GuardedBy("ImfLock.class") boolean hasAttachedClient() { @@ -2831,6 +2861,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) { setSelectedMethodIdLocked(null); + // Callback before clean-up binding states. + onUnbindCurrentMethodByReset(); mBindingController.unbindCurrentMethod(); unbindCurrentClientLocked(unbindClientReason); } @@ -2931,6 +2963,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub sessionState.mSession.finishSession(); } catch (RemoteException e) { Slog.w(TAG, "Session failed to close due to remote exception", e); + updateSystemUiLocked(0 /* vis */, mBackDisposition); } sessionState.mSession = null; } @@ -3040,8 +3073,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - private boolean shouldShowImeSwitcherLocked( - @InputMethodService.ImeWindowVisibility int visibility) { + private boolean shouldShowImeSwitcherLocked(int visibility) { if (!mShowOngoingImeSwitcherForPhones) return false; // When the IME switcher dialog is shown, the IME switcher button should be hidden. if (mMenuController.getSwitchingDialogLocked() != null) return false; @@ -3053,7 +3085,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId())) { return false; } - if ((visibility & InputMethodService.IME_ACTIVE) == 0) { + if ((visibility & InputMethodService.IME_ACTIVE) == 0 + || (visibility & InputMethodService.IME_INVISIBLE) != 0) { return false; } if (mWindowManagerInternal.isHardKeyboardAvailable()) { @@ -3112,9 +3145,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @SuppressWarnings("deprecation") - private void setImeWindowStatus(@NonNull IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition) { + private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) { final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId(); synchronized (ImfLock.class) { @@ -3131,7 +3162,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } mImeWindowVis = vis; mBackDisposition = backDisposition; - updateSystemUiLocked(mImeWindowVis, mBackDisposition); + updateSystemUiLocked(vis, backDisposition); } final boolean dismissImeOnBackKeyPressed; @@ -3166,46 +3197,37 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private void updateImeWindowStatus(boolean disableImeIcon) { synchronized (ImfLock.class) { - // TODO(b/285109020): disableImeIcon should be stored in a property like - // mIsSwitcherIconDisabled, but it is currently not reliably cleared. - updateSystemUiLocked(disableImeIcon ? 0 : mImeWindowVis, mBackDisposition); + if (disableImeIcon) { + updateSystemUiLocked(0, mBackDisposition); + } else { + updateSystemUiLocked(); + } } } @GuardedBy("ImfLock.class") void updateSystemUiLocked() { - // This is only used by InputMethodMenuController to trigger the IME switcher icon - // visibility, by having {@code shouldShowImeSwitcherLocked} called, which depends on the - // visibility of the IME switcher dialog. updateSystemUiLocked(mImeWindowVis, mBackDisposition); } // Caution! This method is called in this class. Handle multi-user carefully @GuardedBy("ImfLock.class") - private void updateSystemUiLocked(@InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition) { + private void updateSystemUiLocked(int vis, int backDisposition) { if (getCurTokenLocked() == null) { return; } if (DEBUG) { Slog.d(TAG, "IME window vis: " + vis - + " active: " + ((vis & InputMethodService.IME_ACTIVE) != 0) - + " visible: " + ((vis & InputMethodService.IME_VISIBLE) != 0) - + " backDisposition: " + backDisposition - + " isInteractive: " + mIsInteractive - + " curPerceptible: " + mCurPerceptible + + " active: " + (vis & InputMethodService.IME_ACTIVE) + + " inv: " + (vis & InputMethodService.IME_INVISIBLE) + " displayId: " + mCurTokenDisplayId); } // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure - // all updateSystemUi happens on system privilege. + // all updateSystemUi happens on system privilege. final long ident = Binder.clearCallingIdentity(); try { - if (!mIsInteractive) { - // When we are not interactive, - // the visibility should be 0 (no IME icons should be shown). - vis = 0; - } else if (!mCurPerceptible) { + if (!mCurPerceptible) { if ((vis & InputMethodService.IME_VISIBLE) != 0) { vis &= ~InputMethodService.IME_VISIBLE; vis |= InputMethodService.IME_VISIBLE_IMPERCEPTIBLE; @@ -3540,7 +3562,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return; } mCurPerceptible = perceptible; - updateSystemUiLocked(mImeWindowVis, mBackDisposition); + updateSystemUiLocked(); } }); } @@ -5102,11 +5124,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private void handleSetInteractive(final boolean interactive) { synchronized (ImfLock.class) { - if (mIsInteractive == interactive) { - return; - } mIsInteractive = interactive; - updateSystemUiLocked(mImeWindowVis, mBackDisposition); + updateSystemUiLocked(interactive ? mImeWindowVis : 0, mBackDisposition); // Inform the current client of the change in active status if (mCurClient == null || mCurClient.mClient == null) { @@ -6741,8 +6760,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void setImeWindowStatusAsync(@InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition) { + public void setImeWindowStatusAsync(int vis, int backDisposition) { mImms.setImeWindowStatus(mToken, vis, backDisposition); } diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 65e34e682724..398e470d9fda 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -147,7 +147,7 @@ public final class MediaProjectionManagerService extends SystemService mInjector = injector; mClock = injector.createClock(); mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>(); - mCallbackDelegate = new CallbackDelegate(); + mCallbackDelegate = new CallbackDelegate(injector.createCallbackLooper()); mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mPackageManager = mContext.getPackageManager(); @@ -182,6 +182,11 @@ public final class MediaProjectionManagerService extends SystemService Clock createClock() { return SystemClock::uptimeMillis; } + + /** Creates the {@link Looper} to be used when notifying callbacks. */ + Looper createCallbackLooper() { + return Looper.getMainLooper(); + } } @Override @@ -268,7 +273,8 @@ public final class MediaProjectionManagerService extends SystemService dispatchStop(projection); } - private void addCallback(final IMediaProjectionWatcherCallback callback) { + @VisibleForTesting + void addCallback(final IMediaProjectionWatcherCallback callback) { IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { @@ -315,6 +321,12 @@ public final class MediaProjectionManagerService extends SystemService mCallbackDelegate.dispatchStop(projection); } + private void dispatchSessionSet( + @NonNull MediaProjectionInfo projectionInfo, + @Nullable ContentRecordingSession session) { + mCallbackDelegate.dispatchSession(projectionInfo, session); + } + /** * Returns {@code true} when updating the current mirroring session on WM succeeded, and * {@code false} otherwise. @@ -335,6 +347,7 @@ public final class MediaProjectionManagerService extends SystemService if (mProjectionGrant != null) { // Cache the session details. mProjectionGrant.mSession = incomingSession; + dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession); } return true; } @@ -1155,8 +1168,8 @@ public final class MediaProjectionManagerService extends SystemService private Handler mHandler; private final Object mLock = new Object(); - public CallbackDelegate() { - mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/); + CallbackDelegate(Looper callbackLooper) { + mHandler = new Handler(callbackLooper, null, true /*async*/); mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>(); mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>(); } @@ -1219,6 +1232,16 @@ public final class MediaProjectionManagerService extends SystemService } } + public void dispatchSession( + @NonNull MediaProjectionInfo projectionInfo, + @Nullable ContentRecordingSession session) { + synchronized (mLock) { + for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) { + mHandler.post(new WatcherSessionCallback(callback, projectionInfo, session)); + } + } + } + public void dispatchResize(MediaProjection projection, int width, int height) { if (projection == null) { Slog.e(TAG, @@ -1335,6 +1358,29 @@ public final class MediaProjectionManagerService extends SystemService } } + private static final class WatcherSessionCallback implements Runnable { + private final IMediaProjectionWatcherCallback mCallback; + private final MediaProjectionInfo mProjectionInfo; + private final ContentRecordingSession mSession; + + WatcherSessionCallback( + @NonNull IMediaProjectionWatcherCallback callback, + @NonNull MediaProjectionInfo projectionInfo, + @Nullable ContentRecordingSession session) { + mCallback = callback; + mProjectionInfo = projectionInfo; + mSession = session; + } + + @Override + public void run() { + try { + mCallback.onRecordingSessionSet(mProjectionInfo, mSession); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify content recording session changed", e); + } + } + } private static String typeToString(int type) { switch (type) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 5b3227388460..68f49fd0481f 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5595,6 +5595,11 @@ public class NotificationManagerService extends SystemService { boolean granted, boolean userSet) { Objects.requireNonNull(listener); checkNotificationListenerAccess(); + if (granted && listener.flattenToString().length() + > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { + throw new IllegalArgumentException( + "Component name too long: " + listener.flattenToString()); + } if (!userSet && isNotificationListenerAccessUserSet(listener)) { // Don't override user's choice return; diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 5f52c16d319c..c1d5af589e8c 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -610,7 +610,8 @@ final class DeletePackageHelper { PackageManager.UNINSTALL_REASON_UNKNOWN, null /*harmfulAppWarning*/, null /*splashScreenTheme*/, - 0 /*firstInstallTime*/); + 0 /*firstInstallTime*/, + PackageManager.USER_MIN_ASPECT_RATIO_UNSET); } mPm.mSettings.writeKernelMappingLPr(ps); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index db47306ad58e..2fc22bf79d91 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -30,6 +30,7 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET; import static android.os.Process.INVALID_UID; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.os.storage.StorageManager.FLAG_STORAGE_CE; @@ -5239,6 +5240,20 @@ public class PackageManagerService implements PackageSender, TestUtilityService } @Override + @PackageManager.UserMinAspectRatio + public int getUserMinAspectRatio(@NonNull String packageName, int userId) { + final Computer snapshot = snapshotComputer(); + final int callingUid = Binder.getCallingUid(); + snapshot.enforceCrossUserPermission( + callingUid, userId, false /* requireFullPermission */, + false /* checkShell */, "getUserMinAspectRatio"); + final PackageStateInternal packageState = snapshot + .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId); + return packageState == null ? USER_MIN_ASPECT_RATIO_UNSET + : packageState.getUserStateOrDefault(userId).getMinAspectRatio(); + } + + @Override public Bundle getSuspendedPackageAppExtras(String packageName, int userId) { final int callingUid = Binder.getCallingUid(); final Computer snapshot = snapshot(); @@ -6201,6 +6216,32 @@ public class PackageManagerService implements PackageSender, TestUtilityService return true; } + @android.annotation.EnforcePermission(android.Manifest.permission.INSTALL_PACKAGES) + @Override + public void setUserMinAspectRatio(@NonNull String packageName, int userId, + @PackageManager.UserMinAspectRatio int aspectRatio) { + setUserMinAspectRatio_enforcePermission(); + final int callingUid = Binder.getCallingUid(); + final Computer snapshot = snapshotComputer(); + snapshot.enforceCrossUserPermission(callingUid, userId, + false /* requireFullPermission */, false /* checkShell */, + "setUserMinAspectRatio"); + enforceOwnerRights(snapshot, packageName, callingUid); + + final PackageStateInternal packageState = snapshot + .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId); + if (packageState == null) { + return; + } + + if (packageState.getUserStateOrDefault(userId).getMinAspectRatio() == aspectRatio) { + return; + } + + commitPackageStateMutation(null, packageName, state -> + state.userState(userId).setMinAspectRatio(aspectRatio)); + } + @Override @SuppressWarnings("GuardedBy") public void setRuntimePermissionsVersion(int version, @UserIdInt int userId) { diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 7fda092a218d..3e9ccac2993a 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -337,7 +337,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal final long previousFirstInstallTime = replacedPkgSetting.getUserStateOrDefault(userId).getFirstInstallTimeMillis(); if (previousFirstInstallTime != 0) { - modifyUserState(userId).setFirstInstallTime(previousFirstInstallTime); + modifyUserState(userId).setFirstInstallTimeMillis(previousFirstInstallTime); } } onChanged(); @@ -352,10 +352,10 @@ public class PackageSetting extends SettingBase implements PackageStateInternal if (userId == UserHandle.USER_ALL) { int userStateCount = mUserStates.size(); for (int i = 0; i < userStateCount; i++) { - mUserStates.valueAt(i).setFirstInstallTime(firstInstallTime); + mUserStates.valueAt(i).setFirstInstallTimeMillis(firstInstallTime); } } else { - modifyUserState(userId).setFirstInstallTime(firstInstallTime); + modifyUserState(userId).setFirstInstallTimeMillis(firstInstallTime); } onChanged(); return this; @@ -875,7 +875,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal ArraySet<String> enabledComponents, ArraySet<String> disabledComponents, int installReason, int uninstallReason, String harmfulAppWarning, String splashScreenTheme, - long firstInstallTime) { + long firstInstallTime, int aspectRatio) { modifyUserState(userId) .setSuspendParams(suspendParams) .setCeDataInode(ceDataInode) @@ -894,7 +894,8 @@ public class PackageSetting extends SettingBase implements PackageStateInternal .setVirtualPreload(virtualPreload) .setHarmfulAppWarning(harmfulAppWarning) .setSplashScreenTheme(splashScreenTheme) - .setFirstInstallTime(firstInstallTime); + .setFirstInstallTimeMillis(firstInstallTime) + .setMinAspectRatio(aspectRatio); onChanged(); } @@ -912,7 +913,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal ? null : otherState.getDisabledComponentsNoCopy().untrackedStorage(), otherState.getInstallReason(), otherState.getUninstallReason(), otherState.getHarmfulAppWarning(), otherState.getSplashScreenTheme(), - otherState.getFirstInstallTimeMillis()); + otherState.getFirstInstallTimeMillis(), otherState.getMinAspectRatio()); } WatchedArraySet<String> getEnabledComponents(int userId) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index aaf13ebeff2a..532ae718c030 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -351,6 +351,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile private static final String ATTR_VIRTUAL_PRELOAD = "virtual-preload"; private static final String ATTR_HARMFUL_APP_WARNING = "harmful-app-warning"; private static final String ATTR_SPLASH_SCREEN_THEME = "splash-screen-theme"; + private static final String ATTR_MIN_ASPECT_RATIO = "min-aspect-ratio"; private static final String ATTR_PACKAGE_NAME = "packageName"; private static final String ATTR_BUILD_FINGERPRINT = "buildFingerprint"; @@ -1122,7 +1123,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile PackageManager.UNINSTALL_REASON_UNKNOWN, null /*harmfulAppWarning*/, null /*splashscreenTheme*/, - 0 /*firstInstallTime*/ + 0 /*firstInstallTime*/, + PackageManager.USER_MIN_ASPECT_RATIO_UNSET ); } } @@ -1776,7 +1778,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile PackageManager.UNINSTALL_REASON_UNKNOWN, null /*harmfulAppWarning*/, null /* splashScreenTheme*/, - 0 /*firstInstallTime*/ + 0 /*firstInstallTime*/, + PackageManager.USER_MIN_ASPECT_RATIO_UNSET ); } return; @@ -1871,6 +1874,9 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile ATTR_SPLASH_SCREEN_THEME); final long firstInstallTime = parser.getAttributeLongHex(null, ATTR_FIRST_INSTALL_TIME, 0); + final int minAspectRatio = parser.getAttributeInt(null, + ATTR_MIN_ASPECT_RATIO, + PackageManager.USER_MIN_ASPECT_RATIO_UNSET); ArraySet<String> enabledComponents = null; ArraySet<String> disabledComponents = null; @@ -1947,7 +1953,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile enabledCaller, enabledComponents, disabledComponents, installReason, uninstallReason, harmfulAppWarning, splashScreenTheme, firstInstallTime != 0 ? firstInstallTime : - origFirstInstallTimes.getOrDefault(name, 0L)); + origFirstInstallTimes.getOrDefault(name, 0L), + minAspectRatio); mDomainVerificationManager.setLegacyUserState(name, userId, verifState); } else if (tagName.equals("preferred-activities")) { @@ -2242,6 +2249,11 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME, ustate.getSplashScreenTheme()); } + if (ustate.getMinAspectRatio() + != PackageManager.USER_MIN_ASPECT_RATIO_UNSET) { + serializer.attributeInt(null, ATTR_MIN_ASPECT_RATIO, + ustate.getMinAspectRatio()); + } if (ustate.isSuspended()) { for (int i = 0; i < ustate.getSuspendParams().size(); i++) { final String suspendingPackage = ustate.getSuspendParams().keyAt(i); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 5b3514c01f9f..710e0b72ecfb 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -15,6 +15,7 @@ */ package com.android.server.pm; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; import android.Manifest.permission; @@ -24,6 +25,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.IUidObserver; import android.app.IUriGrantsManager; @@ -4407,8 +4409,11 @@ public class ShortcutService extends IShortcutService.Stub { return; } try { + ActivityOptions options = ActivityOptions.makeBasic() + .setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_DENIED); intentSender.sendIntent(mContext, /* code= */ 0, extras, - /* onFinished=*/ null, /* handler= */ null); + /* onFinished=*/ null, /* handler= */ null, null, options.toBundle()); } catch (SendIntentException e) { Slog.w(TAG, "sendIntent failed().", e); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 7e88e13e1788..f8bd3289a0b3 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1037,7 +1037,7 @@ public class UserManagerService extends IUserManager.Stub { final UserData userData = mUsers.valueAt(i); final int userId = userData.info.id; if (userId != currentUser && userData.info.isFull() && !userData.info.partial - && !mRemovingUserIds.get(userId)) { + && userData.info.isEnabled() && !mRemovingUserIds.get(userId)) { final long userEnteredTime = userData.mLastEnteredForegroundTimeMillis; if (userEnteredTime > latestEnteredTime) { latestEnteredTime = userEnteredTime; @@ -2927,14 +2927,14 @@ public class UserManagerService extends IUserManager.Stub { Preconditions.checkState(mCachedEffectiveUserRestrictions.getRestrictions(userId) != newBaseRestrictions); - if (mBaseUserRestrictions.updateRestrictions(userId, newBaseRestrictions)) { + if (mBaseUserRestrictions.updateRestrictions(userId, new Bundle(newBaseRestrictions))) { scheduleWriteUser(userId); } } final Bundle effective = computeEffectiveUserRestrictionsLR(userId); - mCachedEffectiveUserRestrictions.updateRestrictions(userId, effective); + mCachedEffectiveUserRestrictions.updateRestrictions(userId, new Bundle(effective)); // Apply the new restrictions. if (DBG) { @@ -5589,8 +5589,14 @@ public class UserManagerService extends IUserManager.Stub { } } - @GuardedBy("mUsersLock") @VisibleForTesting + void addRemovingUserId(@UserIdInt int userId) { + synchronized (mUsersLock) { + addRemovingUserIdLocked(userId); + } + } + + @GuardedBy("mUsersLock") void addRemovingUserIdLocked(@UserIdInt int userId) { // We remember deleted user IDs to prevent them from being // reused during the current boot; they can still be reused diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java index 91a25db3710e..3d056e89ba78 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java @@ -377,6 +377,8 @@ public class PackageStateImpl implements PackageState { private final int mUninstallReason; @Nullable private final String mSplashScreenTheme; + @PackageManager.UserMinAspectRatio + private final int mMinAspectRatio; private final long mFirstInstallTimeMillis; private UserStateImpl(@NonNull PackageUserState userState) { @@ -392,6 +394,7 @@ public class PackageStateImpl implements PackageState { mSharedLibraryOverlayPaths = userState.getSharedLibraryOverlayPaths(); mUninstallReason = userState.getUninstallReason(); mSplashScreenTheme = userState.getSplashScreenTheme(); + mMinAspectRatio = userState.getMinAspectRatio(); setBoolean(Booleans.HIDDEN, userState.isHidden()); setBoolean(Booleans.INSTALLED, userState.isInstalled()); setBoolean(Booleans.INSTANT_APP, userState.isInstantApp()); @@ -543,6 +546,11 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated.Member + public @PackageManager.UserMinAspectRatio int getMinAspectRatio() { + return mMinAspectRatio; + } + + @DataClass.Generated.Member public long getFirstInstallTimeMillis() { return mFirstInstallTimeMillis; } @@ -554,10 +562,10 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated( - time = 1671671043891L, + time = 1687938966108L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java", - inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final long mFirstInstallTimeMillis\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") + inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate final long mFirstInstallTimeMillis\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java index 2048d651e11e..f75d214c5128 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java @@ -217,4 +217,12 @@ public interface PackageUserState { */ @Nullable String getSplashScreenTheme(); + + /** + * @return the min aspect ratio setting of the package which by default is unset + * unless it has been set by the user + * @hide + */ + @PackageManager.UserMinAspectRatio + int getMinAspectRatio(); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java index 73fb67298aec..1fb12a8ce218 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java @@ -136,6 +136,11 @@ class PackageUserStateDefault implements PackageUserStateInternal { } @Override + public @PackageManager.UserMinAspectRatio int getMinAspectRatio() { + return PackageManager.USER_MIN_ASPECT_RATIO_UNSET; + } + + @Override public long getFirstInstallTimeMillis() { return 0; } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java index ed4aab9d15ef..d911ac1cfcd8 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java @@ -16,6 +16,7 @@ package com.android.server.pm.pkg; +import android.annotation.CurrentTimeMillisLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -61,6 +62,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt private int mDistractionFlags; private boolean mInstantApp; private boolean mVirtualPreload; + @PackageManager.EnabledState private int mEnabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; @PackageManager.InstallReason private int mInstallReason = PackageManager.INSTALL_REASON_UNKNOWN; @@ -81,6 +83,9 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt @Nullable private String mSplashScreenTheme; + @PackageManager.UserMinAspectRatio + private int mMinAspectRatio = PackageManager.USER_MIN_ASPECT_RATIO_UNSET; + /** * Suspending package to suspend params */ @@ -90,7 +95,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt @Nullable private WatchedArrayMap<ComponentName, Pair<String, Integer>> mComponentLabelIconOverrideMap; - private long mFirstInstallTime; + private @CurrentTimeMillisLong long mFirstInstallTimeMillis; // TODO(b/239050028): Remove, enforce notifying parent through PMS commit method @Nullable @@ -144,10 +149,11 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt mHarmfulAppWarning = other.mHarmfulAppWarning; mLastDisableAppCaller = other.mLastDisableAppCaller; mSplashScreenTheme = other.mSplashScreenTheme; + mMinAspectRatio = other.mMinAspectRatio; mSuspendParams = other.mSuspendParams == null ? null : other.mSuspendParams.snapshot(); mComponentLabelIconOverrideMap = other.mComponentLabelIconOverrideMap == null ? null : other.mComponentLabelIconOverrideMap.snapshot(); - mFirstInstallTime = other.mFirstInstallTime; + mFirstInstallTimeMillis = other.mFirstInstallTimeMillis; mSnapshot = new SnapshotCache.Sealed<>(); } @@ -506,6 +512,19 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } /** + * Sets user min aspect ratio override value + * @see PackageManager.UserMinAspectRatio + */ + public @NonNull PackageUserStateImpl setMinAspectRatio( + @PackageManager.UserMinAspectRatio int value) { + mMinAspectRatio = value; + com.android.internal.util.AnnotationValidations.validate( + PackageManager.UserMinAspectRatio.class, null, mMinAspectRatio); + onChanged(); + return this; + } + + /** * Suspending package to suspend params */ public @NonNull PackageUserStateImpl setSuspendParams( @@ -538,8 +557,8 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt return this; } - public @NonNull PackageUserStateImpl setFirstInstallTime(long value) { - mFirstInstallTime = value; + public @NonNull PackageUserStateImpl setFirstInstallTimeMillis(long value) { + mFirstInstallTimeMillis = value; onChanged(); return this; } @@ -643,7 +662,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } @DataClass.Generated.Member - public int getEnabledState() { + public @PackageManager.EnabledState int getEnabledState() { return mEnabledState; } @@ -677,6 +696,11 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt return mSplashScreenTheme; } + @DataClass.Generated.Member + public @PackageManager.UserMinAspectRatio int getMinAspectRatio() { + return mMinAspectRatio; + } + /** * Suspending package to suspend params */ @@ -691,8 +715,8 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } @DataClass.Generated.Member - public long getFirstInstallTimeMillis() { - return mFirstInstallTime; + public @CurrentTimeMillisLong long getFirstInstallTimeMillis() { + return mFirstInstallTimeMillis; } @DataClass.Generated.Member @@ -764,9 +788,10 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt && Objects.equals(mOverlayPaths, that.mOverlayPaths) && Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths) && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme) + && mMinAspectRatio == that.mMinAspectRatio && Objects.equals(mSuspendParams, that.mSuspendParams) && Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap) - && mFirstInstallTime == that.mFirstInstallTime + && mFirstInstallTimeMillis == that.mFirstInstallTimeMillis && watchableEquals(that.mWatchable) && snapshotEquals(that.mSnapshot); } @@ -796,19 +821,20 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt _hash = 31 * _hash + Objects.hashCode(mOverlayPaths); _hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths); _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme); + _hash = 31 * _hash + mMinAspectRatio; _hash = 31 * _hash + Objects.hashCode(mSuspendParams); _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap); - _hash = 31 * _hash + Long.hashCode(mFirstInstallTime); + _hash = 31 * _hash + Long.hashCode(mFirstInstallTimeMillis); _hash = 31 * _hash + watchableHashCode(); _hash = 31 * _hash + snapshotHashCode(); return _hash; } @DataClass.Generated( - time = 1668033772891L, + time = 1687938397579L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java", - inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate long mFirstInstallTime\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") + inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java index 8125b0f662aa..8430cf7a0d11 100644 --- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java +++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java @@ -439,6 +439,16 @@ public class PackageStateMutator { } return null; } + + @NonNull + @Override + public PackageUserStateWrite setMinAspectRatio( + @PackageManager.UserMinAspectRatio int aspectRatio) { + if (mUserState != null) { + mUserState.setMinAspectRatio(aspectRatio); + } + return this; + } } } } diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java index 11d6d97d3920..0c6c6723b79b 100644 --- a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java +++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.overlay.OverlayPaths; +import com.android.server.pm.pkg.PackageUserStateImpl; import com.android.server.pm.pkg.SuspendParams; public interface PackageUserStateWrite { @@ -68,4 +69,8 @@ public interface PackageUserStateWrite { @NonNull PackageUserStateWrite setComponentLabelIcon(@NonNull ComponentName componentName, @Nullable String nonLocalizedLabel, @Nullable Integer icon); + + /** @see PackageUserStateImpl#setMinAspectRatio(int) */ + @NonNull + PackageUserStateWrite setMinAspectRatio(@PackageManager.UserMinAspectRatio int aspectRatio); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index a792b9c0dbdb..9402fca9574f 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -708,8 +708,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { finishKeyguardDrawn(); break; case MSG_WINDOW_MANAGER_DRAWN_COMPLETE: - if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mWindowManagerDrawComplete"); - finishWindowsDrawn(msg.arg1); + final int displayId = msg.arg1; + if (DEBUG_WAKEUP) Slog.w(TAG, "All windows drawn on display " + displayId); + Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, + TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, displayId /* cookie */); + finishWindowsDrawn(displayId); break; case MSG_HIDE_BOOT_MESSAGE: handleHideBootMessage(); @@ -3618,19 +3621,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - public void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition) { - if (mKeyguardDelegate != null && waitAppTransition) { + public void onKeyguardOccludedChangedLw(boolean occluded) { + if (mKeyguardDelegate != null) { mPendingKeyguardOccluded = occluded; mKeyguardOccludedChanged = true; - } else { - setKeyguardOccludedLw(occluded); } } @Override public int applyKeyguardOcclusionChange() { if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded commit occluded=" - + mPendingKeyguardOccluded); + + mPendingKeyguardOccluded + " changed=" + mKeyguardOccludedChanged); // TODO(b/276433230): Explicitly save before/after for occlude state in each // Transition so we don't need to update SysUI every time. @@ -5046,15 +5047,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { // ... eventually calls finishWindowsDrawn which will finalize our screen turn on // as well as enabling the orientation change logic/sensor. Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - mWindowManagerInternal.waitForAllWindowsDrawn(() -> { - if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for every display"); - mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE, - INVALID_DISPLAY, 0)); - - Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - }, WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY); + TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, INVALID_DISPLAY /* cookie */); + mWindowManagerInternal.waitForAllWindowsDrawn(mHandler.obtainMessage( + MSG_WINDOW_MANAGER_DRAWN_COMPLETE, INVALID_DISPLAY, 0), + WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY); } // Called on the DisplayManager's DisplayPowerController thread. @@ -5134,15 +5130,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { mScreenOnListeners.put(displayId, screenOnListener); Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - mWindowManagerInternal.waitForAllWindowsDrawn(() -> { - if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display: " + displayId); - mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE, - displayId, 0)); - - Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - }, WAITING_FOR_DRAWN_TIMEOUT, displayId); + TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, displayId /* cookie */); + mWindowManagerInternal.waitForAllWindowsDrawn(mHandler.obtainMessage( + MSG_WINDOW_MANAGER_DRAWN_COMPLETE, displayId, 0), + WAITING_FOR_DRAWN_TIMEOUT, displayId); } } diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 887f9461bdce..03a7bd3b68b3 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -169,7 +169,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * * @param occluded Whether Keyguard is currently occluded or not. */ - void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition); + void onKeyguardOccludedChangedLw(boolean occluded); /** * Commit any queued changes to keyguard occlude status that had been deferred during the diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING index cf1bfc3b555f..fbfe291b1659 100644 --- a/services/core/java/com/android/server/power/TEST_MAPPING +++ b/services/core/java/com/android/server/power/TEST_MAPPING @@ -20,6 +20,7 @@ "name": "FrameworksServicesTests", "options": [ {"include-filter": "com.android.server.power"}, + {"exclude-filter": "com.android.server.power.BatteryStatsTests"}, {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] @@ -38,7 +39,8 @@ { "name": "FrameworksServicesTests", "options": [ - {"include-filter": "com.android.server.power"} + {"include-filter": "com.android.server.power"}, + {"exclude-filter": "com.android.server.power.BatteryStatsTests"} ] } ] diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 4a57592aa1ae..27329e20bc8d 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -14613,17 +14613,13 @@ public class BatteryStatsImpl extends BatteryStats { // Inform StatsLog of setBatteryState changes. private void reportChangesToStatsLog(final int status, final int plugType, final int level) { - if (!mHaveBatteryLevel) { - return; - } - - if (mBatteryStatus != status) { + if (!mHaveBatteryLevel || mBatteryStatus != status) { FrameworkStatsLog.write(FrameworkStatsLog.CHARGING_STATE_CHANGED, status); } - if (mBatteryPlugType != plugType) { + if (!mHaveBatteryLevel || mBatteryPlugType != plugType) { FrameworkStatsLog.write(FrameworkStatsLog.PLUGGED_STATE_CHANGED, plugType); } - if (mBatteryLevel != level) { + if (!mHaveBatteryLevel || mBatteryLevel != level) { FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_LEVEL_CHANGED, level); } } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index 6e9a22c7872b..efd8b6d9a943 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -19,7 +19,6 @@ package com.android.server.statusbar; import android.annotation.Nullable; import android.app.ITransientNotificationCallback; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; -import android.inputmethodservice.InputMethodService; import android.os.Bundle; import android.os.IBinder; import android.view.WindowInsets.Type.InsetsType; @@ -55,13 +54,13 @@ public interface StatusBarManagerInternal { * @param displayId The display to which the IME is bound to. * @param token The IME token. * @param vis Bit flags about the IME visibility. + * (e.g. {@link android.inputmethodservice.InputMethodService#IME_ACTIVE}) * @param backDisposition Bit flags about the IME back disposition. + * (e.g. {@link android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT}) * @param showImeSwitcher {@code true} when the IME switcher button should be shown. */ - void setImeWindowStatus(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, - boolean showImeSwitcher); + void setImeWindowStatus(int displayId, IBinder token, int vis, + int backDisposition, boolean showImeSwitcher); /** * See {@link android.app.StatusBarManager#setIcon(String, int, int, String)}. diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 2c381ca3bd41..cc849b6fbf91 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -59,7 +59,6 @@ import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; -import android.inputmethodservice.InputMethodService; import android.media.INearbyMediaDevicesProvider; import android.media.MediaRoute2Info; import android.net.Uri; @@ -533,9 +532,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void setImeWindowStatus(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, + public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { StatusBarManagerService.this.setImeWindowStatus(displayId, token, vis, backDisposition, showImeSwitcher); @@ -1267,14 +1264,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void setImeWindowStatus(int displayId, final IBinder token, - @InputMethodService.ImeWindowVisibility final int vis, - @InputMethodService.BackDispositionMode final int backDisposition, - final boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, final IBinder token, final int vis, + final int backDisposition, final boolean showImeSwitcher) { enforceStatusBar(); if (SPEW) { - Slog.d(TAG, "setImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition); + Slog.d(TAG, "swetImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition); } synchronized(mLock) { @@ -1337,9 +1332,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private String mPackageName = "none"; private int mDisabled1 = 0; private int mDisabled2 = 0; - @InputMethodService.ImeWindowVisibility private int mImeWindowVis = 0; - @InputMethodService.BackDispositionMode private int mImeBackDisposition = 0; private boolean mShowImeSwitcher = false; private IBinder mImeToken = null; @@ -1384,8 +1377,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D return mDisabled1 == disabled1 && mDisabled2 == disabled2; } - private void setImeWindowState(@InputMethodService.ImeWindowVisibility final int vis, - @InputMethodService.BackDispositionMode final int backDisposition, + private void setImeWindowState(final int vis, final int backDisposition, final boolean showImeSwitcher, final IBinder token) { mImeWindowVis = vis; mImeBackDisposition = backDisposition; diff --git a/services/core/java/com/android/server/utils/AlarmQueue.java b/services/core/java/com/android/server/utils/AlarmQueue.java index 09ba19508476..83605ae09e97 100644 --- a/services/core/java/com/android/server/utils/AlarmQueue.java +++ b/services/core/java/com/android/server/utils/AlarmQueue.java @@ -151,6 +151,10 @@ public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener { @GuardedBy("mLock") @ElapsedRealtimeLong private long mTriggerTimeElapsed = NOT_SCHEDULED; + /** The last time an alarm went off (ie. the last time {@link #onAlarm()} was called}). */ + @GuardedBy("mLock") + @ElapsedRealtimeLong + private long mLastFireTimeElapsed; /** * @param alarmTag The tag to use when scheduling the alarm with AlarmManager. @@ -284,7 +288,7 @@ public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener { /** Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue after now. */ @GuardedBy("mLock") private void setNextAlarmLocked() { - setNextAlarmLocked(mInjector.getElapsedRealtime()); + setNextAlarmLocked(mLastFireTimeElapsed + mMinTimeBetweenAlarmsMs); } /** @@ -334,6 +338,7 @@ public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener { final ArraySet<K> expired = new ArraySet<>(); synchronized (mLock) { final long nowElapsed = mInjector.getElapsedRealtime(); + mLastFireTimeElapsed = nowElapsed; while (mAlarmPriorityQueue.size() > 0) { final Pair<K, Long> alarm = mAlarmPriorityQueue.peek(); if (alarm.second <= nowElapsed) { diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index b296ef2a1443..1ff01a6c70bf 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -1049,7 +1049,11 @@ public class VrManagerService extends SystemService for (ComponentName c : possibleServices) { if (Objects.equals(c.getPackageName(), pkg)) { - nm.setNotificationListenerAccessGrantedForUser(c, userId, true); + try { + nm.setNotificationListenerAccessGrantedForUser(c, userId, true); + } catch (Exception e) { + Slog.w(TAG, "Could not grant NLS access to package " + pkg, e); + } } } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 9b2cdd72a338..ee7dc5007d97 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -3113,7 +3113,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (which == FLAG_SYSTEM && systemIsStatic && systemIsBoth) { Slog.i(TAG, "Migrating current wallpaper to be lock-only before" + " updating system wallpaper"); - migrateStaticSystemToLockWallpaperLocked(userId); + if (!migrateStaticSystemToLockWallpaperLocked(userId) + && !isLockscreenLiveWallpaperEnabled()) { + which |= FLAG_LOCK; + } } wallpaper = getWallpaperSafeLocked(userId, which); @@ -3141,13 +3144,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private void migrateStaticSystemToLockWallpaperLocked(int userId) { + private boolean migrateStaticSystemToLockWallpaperLocked(int userId) { WallpaperData sysWP = mWallpaperMap.get(userId); if (sysWP == null) { if (DEBUG) { Slog.i(TAG, "No system wallpaper? Not tracking for lock-only"); } - return; + return true; } // We know a-priori that there is no lock-only wallpaper currently @@ -3174,9 +3177,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub SELinux.restorecon(lockWP.getWallpaperFile()); mLastLockWallpaper = lockWP; } + return true; } catch (ErrnoException e) { - Slog.e(TAG, "Can't migrate system wallpaper: " + e.getMessage()); + // can happen when migrating default wallpaper (which is not stored in wallpaperFile) + Slog.w(TAG, "Couldn't migrate system wallpaper: " + e.getMessage()); clearWallpaperBitmaps(lockWP); + return false; } } @@ -3388,7 +3394,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // therefore it's a shared system+lock image that we need to migrate. Slog.i(TAG, "Migrating current wallpaper to be lock-only before" + "updating system wallpaper"); - migrateStaticSystemToLockWallpaperLocked(userId); + if (!migrateStaticSystemToLockWallpaperLocked(userId)) { + which |= FLAG_LOCK; + } } } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index f3001133338a..b3ae2ee30f22 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1280,45 +1280,53 @@ final class AccessibilityController { } void drawIfNeeded(SurfaceControl.Transaction t) { + // Drawing variables (alpha, dirty rect, and bounds) access is synchronized + // using WindowManagerGlobalLock. Grab copies of these values before + // drawing on the canvas so that drawing can be performed outside of the lock. + int alpha; + Rect drawingRect = null; + Region drawingBounds = null; synchronized (mService.mGlobalLock) { if (!mInvalidated) { return; } mInvalidated = false; - if (mAlpha > 0) { - Canvas canvas = null; - try { - // Empty dirty rectangle means unspecified. - if (mDirtyRect.isEmpty()) { - mBounds.getBounds(mDirtyRect); - } - mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth); - canvas = mSurface.lockCanvas(mDirtyRect); - if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect); - } - } catch (IllegalArgumentException iae) { - /* ignore */ - } catch (Surface.OutOfResourcesException oore) { - /* ignore */ - } - if (canvas == null) { - return; + + alpha = mAlpha; + if (alpha > 0) { + drawingBounds = new Region(mBounds); + // Empty dirty rectangle means unspecified. + if (mDirtyRect.isEmpty()) { + mBounds.getBounds(mDirtyRect); } + mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth); + drawingRect = new Rect(mDirtyRect); if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "Bounds: " + mBounds); + Slog.i(LOG_TAG, "ViewportWindow bounds: " + mBounds); + Slog.i(LOG_TAG, "ViewportWindow dirty rect: " + mDirtyRect); } - canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); - mPaint.setAlpha(mAlpha); - Path path = mBounds.getBoundaryPath(); - canvas.drawPath(path, mPaint); - - mSurface.unlockCanvasAndPost(canvas); - t.show(mSurfaceControl); - } else { - t.hide(mSurfaceControl); } } + + // Draw without holding WindowManagerGlobalLock. + if (alpha > 0) { + Canvas canvas = null; + try { + canvas = mSurface.lockCanvas(drawingRect); + } catch (IllegalArgumentException | OutOfResourcesException e) { + /* ignore */ + } + if (canvas == null) { + return; + } + canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); + mPaint.setAlpha(alpha); + canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint); + mSurface.unlockCanvasAndPost(canvas); + t.show(mSurfaceControl); + } else { + t.hide(mSurfaceControl); + } } void releaseSurface() { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 788bfbcd65c9..0994fa4464db 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4560,6 +4560,26 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } task.forAllActivities(fromActivity -> { if (fromActivity == this) return true; + // The snapshot starting window could remove itself when receive resized request without + // redraw, so transfer it to a different size activity could only cause flicker. + // By schedule remove snapshot starting window, the remove process will happen when + // transition ready, transition ready means the app window is drawn. + final StartingData tmpStartingData = fromActivity.mStartingData; + if (tmpStartingData != null && tmpStartingData.mAssociatedTask == null + && mTransitionController.isCollecting(fromActivity) + && tmpStartingData instanceof SnapshotStartingData) { + final Rect fromBounds = fromActivity.getBounds(); + final Rect myBounds = getBounds(); + if (!fromBounds.equals(myBounds)) { + // Mark as no animation, so these changes won't merge into playing transition. + if (mTransitionController.inPlayingTransition(fromActivity)) { + mTransitionController.setNoAnimation(this); + mTransitionController.setNoAnimation(fromActivity); + } + fromActivity.removeStartingWindow(); + return true; + } + } return !fromActivity.isVisibleRequested() && transferStartingWindow(fromActivity); }); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 32f1f42aacb9..a2547fd437d1 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -23,6 +23,7 @@ import android.app.ActivityManager; import android.app.AppProtoEnums; import android.app.BackgroundStartPrivileges; import android.app.IActivityManager; +import android.app.IAppTask; import android.app.IApplicationThread; import android.app.ITaskStackListener; import android.app.ProfilerInfo; @@ -308,6 +309,12 @@ public abstract class ActivityTaskManagerInternal { public abstract void notifyActiveDreamChanged(@Nullable ComponentName activeDreamComponent); /** + * Starts a dream activity in the DreamService's process. + */ + public abstract IAppTask startDreamActivity(@NonNull Intent intent, int callingUid, + int callingPid); + + /** * Set a uid that is allowed to bypass stopped app switches, launching an app * whenever it wants. * diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 10ff3a3b2e9b..78da5def43d4 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1467,23 +1467,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return false; } - private void enforceCallerIsDream(String callerPackageName) { - final long origId = Binder.clearCallingIdentity(); - try { - if (!canLaunchDreamActivity(callerPackageName)) { - throw new SecurityException("The dream activity can be started only when the device" - + " is dreaming and only by the active dream package."); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - - @Override - public boolean startDreamActivity(@NonNull Intent intent) { - assertPackageMatchesCallingUid(intent.getPackage()); - enforceCallerIsDream(intent.getPackage()); - + private IAppTask startDreamActivityInternal(@NonNull Intent intent, int callingUid, + int callingPid) { final ActivityInfo a = new ActivityInfo(); a.theme = com.android.internal.R.style.Theme_Dream; a.exported = true; @@ -1501,7 +1486,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { options.setLaunchActivityType(ACTIVITY_TYPE_DREAM); synchronized (mGlobalLock) { - final WindowProcessController process = mProcessMap.getProcess(Binder.getCallingPid()); + final WindowProcessController process = mProcessMap.getProcess(callingPid); a.packageName = process.mInfo.packageName; a.applicationInfo = process.mInfo; @@ -1509,26 +1494,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { a.uiOptions = process.mInfo.uiOptions; a.taskAffinity = "android:" + a.packageName + "/dream"; - final int callingUid = Binder.getCallingUid(); - final int callingPid = Binder.getCallingPid(); - final long origId = Binder.clearCallingIdentity(); - try { - getActivityStartController().obtainStarter(intent, "dream") - .setCallingUid(callingUid) - .setCallingPid(callingPid) - .setCallingPackage(intent.getPackage()) - .setActivityInfo(a) - .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options)) - // To start the dream from background, we need to start it from a persistent - // system process. Here we set the real calling uid to the system server uid - .setRealCallingUid(Binder.getCallingUid()) - .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL) - .execute(); - return true; - } finally { - Binder.restoreCallingIdentity(origId); - } + final ActivityRecord[] outActivity = new ActivityRecord[1]; + getActivityStartController().obtainStarter(intent, "dream") + .setCallingUid(callingUid) + .setCallingPid(callingPid) + .setCallingPackage(intent.getPackage()) + .setActivityInfo(a) + .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options)) + .setOutActivity(outActivity) + // To start the dream from background, we need to start it from a persistent + // system process. Here we set the real calling uid to the system server uid + .setRealCallingUid(Binder.getCallingUid()) + .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL) + .execute(); + + final ActivityRecord started = outActivity[0]; + final IAppTask appTask = started == null ? null : + new AppTaskImpl(this, started.getTask().mTaskId, callingUid); + return appTask; } } @@ -5926,6 +5910,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public IAppTask startDreamActivity(@NonNull Intent intent, int callingUid, int callingPid) { + return startDreamActivityInternal(intent, callingUid, callingPid); + } + + @Override public void setAllowAppSwitches(@NonNull String type, int uid, int userId) { if (!mAmInternal.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) { return; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 12b5f5f774de..bfd2a10a8882 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3701,6 +3701,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInputMonitor.dump(pw, " "); pw.println(); mInsetsStateController.dump(prefix, pw); + mInsetsPolicy.dump(prefix, pw); mDwpcHelper.dump(prefix, pw); pw.println(); } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index a02fd11ba832..98c15dd26013 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -327,8 +327,6 @@ public class DisplayPolicy { private WindowState mTopFullscreenOpaqueWindowState; private boolean mTopIsFullscreen; private int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED; - private boolean mForceConsumeSystemBars; - private boolean mForceShowSystemBars; /** * Windows that provides gesture insets. If multiple windows provide gesture insets at the same @@ -1287,20 +1285,6 @@ public class DisplayPolicy { } /** - * @return true if the system bars are forced to be consumed - */ - public boolean areSystemBarsForcedConsumedLw() { - return mForceConsumeSystemBars; - } - - /** - * @return true if the system bars are forced to stay visible - */ - public boolean areSystemBarsForcedShownLw() { - return mForceShowSystemBars; - } - - /** * Computes the frames of display (its logical size, rotation and cutout should already be set) * used to layout window. This method only changes the given display frames, insets state and * some temporal states, but doesn't change the window frames used to show on screen. @@ -1694,7 +1678,8 @@ public class DisplayPolicy { * @return Whether the top fullscreen app hides the given type of system bar. */ boolean topAppHidesSystemBar(@InsetsType int type) { - if (mTopFullscreenOpaqueWindowState == null || mForceShowSystemBars) { + if (mTopFullscreenOpaqueWindowState == null + || getInsetsPolicy().areTypesForciblyShowing(type)) { return false; } return !mTopFullscreenOpaqueWindowState.isRequestedVisible(type); @@ -2371,14 +2356,7 @@ public class DisplayPolicy { final boolean freeformRootTaskVisible = defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM); - // We need to force showing system bars when adjacent tasks or freeform roots visible. - mForceShowSystemBars = adjacentTasksVisible || freeformRootTaskVisible; - // We need to force the consumption of the system bars if they are force shown or if they - // are controlled by a remote insets controller. - mForceConsumeSystemBars = mForceShowSystemBars - || getInsetsPolicy().remoteInsetsControllerControlsSystemBars(win) - || getInsetsPolicy().forcesShowingNavigationBars(win); - mDisplayContent.getInsetsPolicy().updateBarControlTarget(win); + getInsetsPolicy().updateSystemBars(win, adjacentTasksVisible, freeformRootTaskVisible); final boolean topAppHidesStatusBar = topAppHidesSystemBar(Type.statusBars()); if (getStatusBar() != null) { diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 798dc85ec11b..4620266c981e 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -64,6 +64,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.DisplayThread; import com.android.server.statusbar.StatusBarManagerInternal; +import java.io.PrintWriter; + /** * Policy that implements who gets control over the windows generating insets. */ @@ -114,6 +116,7 @@ class InsetsPolicy { private final BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR); private final BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR); private @InsetsType int mShowingTransientTypes; + private @InsetsType int mForcedShowingTypes; private boolean mAnimatingShown; private final boolean mHideNavBarForKeyboard; @@ -127,7 +130,6 @@ class InsetsPolicy { mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard); } - /** Updates the target which can control system bars. */ void updateBarControlTarget(@Nullable WindowState focusedWin) { if (mFocusedWin != focusedWin) { @@ -514,7 +516,7 @@ class InsetsPolicy { component, focusedWin.getRequestedVisibleTypes()); return mDisplayContent.mRemoteInsetsControlTarget; } - if (mPolicy.areSystemBarsForcedShownLw()) { + if (areTypesForciblyShowing(Type.statusBars())) { // Status bar is forcibly shown. We don't want the client to control the status bar, and // we will dispatch the real visibility of status bar to the client. return null; @@ -567,13 +569,6 @@ class InsetsPolicy { return focusedWin; } } - if (forcesShowingNavigationBars(focusedWin)) { - // When "force show navigation bar" is enabled, it means both force visible is true, and - // we are in 3-button navigation. In this mode, the navigation bar is forcibly shown - // when activity type is ACTIVITY_TYPE_STANDARD which means Launcher or Recent could - // still control the navigation bar in this mode. - return null; - } if (remoteInsetsControllerControlsSystemBars(focusedWin)) { ComponentName component = focusedWin.mActivityRecord != null ? focusedWin.mActivityRecord.mActivityComponent : null; @@ -581,7 +576,7 @@ class InsetsPolicy { component, focusedWin.getRequestedVisibleTypes()); return mDisplayContent.mRemoteInsetsControlTarget; } - if (mPolicy.areSystemBarsForcedShownLw()) { + if (areTypesForciblyShowing(Type.navigationBars())) { // Navigation bar is forcibly shown. We don't want the client to control the navigation // bar, and we will dispatch the real visibility of navigation bar to the client. return null; @@ -603,7 +598,32 @@ class InsetsPolicy { return focusedWin; } - boolean forcesShowingNavigationBars(WindowState win) { + boolean areTypesForciblyShowing(@InsetsType int types) { + return (mForcedShowingTypes & types) == types; + } + + void updateSystemBars(WindowState win, boolean inSplitScreenMode, boolean inFreeformMode) { + mForcedShowingTypes = (inSplitScreenMode || inFreeformMode) + ? (Type.statusBars() | Type.navigationBars()) + : forceShowingNavigationBars(win) + ? Type.navigationBars() + : 0; + + // The client app won't be able to control these types of system bars. Here makes the client + // forcibly consume these types to prevent the app content from getting obscured. + mStateController.setForcedConsumingTypes( + mForcedShowingTypes | (remoteInsetsControllerControlsSystemBars(win) + ? (Type.statusBars() | Type.navigationBars()) + : 0)); + + updateBarControlTarget(win); + } + + private boolean forceShowingNavigationBars(WindowState win) { + // When "force show navigation bar" is enabled, it means both force visible is true, and + // we are in 3-button navigation. In this mode, the navigation bar is forcibly shown + // when activity type is ACTIVITY_TYPE_STANDARD which means Launcher or Recent could + // still control the navigation bar in this mode. return mPolicy.isForceShowNavigationBarEnabled() && win != null && win.getActivityType() == ACTIVITY_TYPE_STANDARD; } @@ -696,6 +716,21 @@ class InsetsPolicy { wereRevealedFromSwipeOnSystemBar); } + void dump(String prefix, PrintWriter pw) { + pw.println(prefix + "InsetsPolicy"); + prefix = prefix + " "; + pw.println(prefix + "status: " + StatusBarManager.windowStateToString(mStatusBar.mState)); + pw.println(prefix + "nav: " + StatusBarManager.windowStateToString(mNavBar.mState)); + if (mShowingTransientTypes != 0) { + pw.println(prefix + "mShowingTransientTypes=" + + WindowInsets.Type.toString(mShowingTransientTypes)); + } + if (mForcedShowingTypes != 0) { + pw.println(prefix + "mForcedShowingTypes=" + + WindowInsets.Type.toString(mForcedShowingTypes)); + } + } + private class BarWindow { private final int mId; diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index e1c865bb85be..2b8312c3ea60 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -40,6 +40,7 @@ import android.graphics.Rect; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.InsetsSource; +import android.view.InsetsSource.Flags; import android.view.InsetsSourceControl; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -81,6 +82,8 @@ class InsetsSourceProvider { private final Rect mSourceFrame = new Rect(); private final Rect mLastSourceFrame = new Rect(); private @NonNull Insets mInsetsHint = Insets.NONE; + private @Flags int mFlagsFromFrameProvider; + private @Flags int mFlagsFromServer; private final Consumer<Transaction> mSetLeashPositionConsumer = t -> { if (mControl != null) { @@ -189,6 +192,16 @@ class InsetsSourceProvider { } } + boolean setFlags(@Flags int flags, @Flags int mask) { + mFlagsFromServer = (mFlagsFromServer & ~mask) | (flags & mask); + final @Flags int mergedFlags = mFlagsFromFrameProvider | mFlagsFromServer; + if (mSource.getFlags() != mergedFlags) { + mSource.setFlags(mergedFlags); + return true; + } + return false; + } + /** * The source frame can affect the layout of other windows, so this should be called once the * window container gets laid out. @@ -217,11 +230,11 @@ class InsetsSourceProvider { mSourceFrame.set(frame); if (mFrameProvider != null) { - final int flags = mFrameProvider.apply( + mFlagsFromFrameProvider = mFrameProvider.apply( mWindowContainer.getDisplayContent().mDisplayFrames, mWindowContainer, mSourceFrame); - mSource.setFlags(flags); + mSource.setFlags(mFlagsFromFrameProvider | mFlagsFromServer); } updateSourceFrameForServerVisibility(); diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index addb5219c663..081ebe0e7cbd 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; import static android.view.InsetsSource.ID_IME; import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.ime; @@ -85,6 +86,8 @@ class InsetsStateController { } }; + private @InsetsType int mForcedConsumingTypes; + InsetsStateController(DisplayContent displayContent) { mDisplayContent = displayContent; } @@ -122,6 +125,11 @@ class InsetsStateController { provider = id == ID_IME ? new ImeInsetsSourceProvider(source, this, mDisplayContent) : new InsetsSourceProvider(source, this, mDisplayContent); + provider.setFlags( + (mForcedConsumingTypes & type) != 0 + ? FLAG_FORCE_CONSUMING + : 0, + FLAG_FORCE_CONSUMING); mProviders.put(id, provider); return provider; } @@ -137,6 +145,24 @@ class InsetsStateController { } } + void setForcedConsumingTypes(@InsetsType int types) { + if (mForcedConsumingTypes != types) { + mForcedConsumingTypes = types; + boolean changed = false; + for (int i = mProviders.size() - 1; i >= 0; i--) { + final InsetsSourceProvider provider = mProviders.valueAt(i); + changed |= provider.setFlags( + (types & provider.getSource().getType()) != 0 + ? FLAG_FORCE_CONSUMING + : 0, + FLAG_FORCE_CONSUMING); + } + if (changed) { + notifyInsetsChanged(); + } + } + } + /** * Called when a layout pass has occurred. */ @@ -391,6 +417,10 @@ class InsetsStateController { for (int i = mProviders.size() - 1; i >= 0; i--) { mProviders.valueAt(i).dump(pw, prefix + " "); } + if (mForcedConsumingTypes != 0) { + pw.println(prefix + "mForcedConsumingTypes=" + + WindowInsets.Type.toString(mForcedConsumingTypes)); + } } void dumpDebug(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) { diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index ad9c3b274267..83fd725aafdf 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -418,13 +418,17 @@ class KeyguardController { return; } - final boolean waitAppTransition = isKeyguardLocked(displayId); - mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY), - waitAppTransition); - if (waitAppTransition) { - mService.deferWindowLayout(); - try { - if (isDisplayOccluded(DEFAULT_DISPLAY)) { + final TransitionController tc = mRootWindowContainer.mTransitionController; + + final boolean occluded = isDisplayOccluded(displayId); + final boolean performTransition = isKeyguardLocked(displayId); + final boolean executeTransition = performTransition && !tc.isCollecting(); + + mWindowManager.mPolicy.onKeyguardOccludedChangedLw(occluded); + mService.deferWindowLayout(); + try { + if (isKeyguardLocked(displayId)) { + if (occluded) { mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare( TRANSIT_KEYGUARD_OCCLUDE, TRANSIT_FLAG_KEYGUARD_OCCLUDING, @@ -434,11 +438,19 @@ class KeyguardController { TRANSIT_KEYGUARD_UNOCCLUDE, TRANSIT_FLAG_KEYGUARD_UNOCCLUDING); } - updateKeyguardSleepToken(DEFAULT_DISPLAY); + } else { + if (tc.inTransition()) { + tc.mStateValidators.add(mWindowManager.mPolicy::applyKeyguardOcclusionChange); + } else { + mWindowManager.mPolicy.applyKeyguardOcclusionChange(); + } + } + updateKeyguardSleepToken(displayId); + if (performTransition && executeTransition) { mWindowManager.executeAppTransition(); - } finally { - mService.continueWindowLayout(); } + } finally { + mService.continueWindowLayout(); } } @@ -485,6 +497,9 @@ class KeyguardController { } } + /** + * @return true if Keyguard is occluded or the device is dreaming. + */ boolean isDisplayOccluded(int displayId) { return getDisplayState(displayId).mOccluded; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 54fec3e2f844..d9a954f1973b 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1061,8 +1061,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent> displayHasContent = true; } else if (displayContent != null && (!mObscureApplicationContentOnSecondaryDisplays + || displayContent.isKeyguardAlwaysUnlocked() || (obscured && type == TYPE_KEYGUARD_DIALOG))) { - // Allow full screen keyguard presentation dialogs to be seen. + // Allow full screen keyguard presentation dialogs to be seen, or simply ignore the + // keyguard if this display is always unlocked. displayHasContent = true; } if ((privateflags & PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE) != 0) { diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index 586077658a26..c914fa10687f 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -48,6 +48,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.Slog; import android.view.RemoteAnimationAdapter; +import android.window.RemoteTransition; import android.window.WindowContainerToken; import com.android.internal.annotations.VisibleForTesting; @@ -385,6 +386,18 @@ public class SafeActivityOptions { throw new SecurityException(msg); } + // Check permission for remote transitions + final RemoteTransition transition = options.getRemoteTransition(); + if (transition != null && supervisor.mService.checkPermission( + CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, callingPid, callingUid) + != PERMISSION_GRANTED) { + final String msg = "Permission Denial: starting " + getIntentString(intent) + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ") with remoteTransition"; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + // If launched from bubble is specified, then ensure that the caller is system or sysui. if (options.getLaunchedFromBubble() && !isSystemOrSystemUI(callingPid, callingUid)) { final String msg = "Permission Denial: starting " + getIntentString(intent) diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 39772dda4792..9c23beb21a92 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3473,6 +3473,11 @@ class Task extends TaskFragment { top.mLetterboxUiController.getLetterboxPositionForVerticalReachability(); } } + // User Aspect Ratio Settings is enabled if the app is not in SCM + info.topActivityEligibleForUserAspectRatioButton = + mWmService.mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled() + && top != null && !info.topActivityInSizeCompat; + info.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed(); } /** diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 79839ef98d56..1b27bb17f599 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -27,6 +27,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; +import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -77,6 +78,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; +import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; @@ -613,12 +615,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } if (mParticipants.contains(wc)) return; - // Wallpaper is like in a static drawn state unless display may have changes, so exclude - // the case to reduce transition latency waiting for the unchanged wallpaper to redraw. - final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent)) - // Transient-hide may be hidden later, so no need to request redraw. - && !isInTransientHide(wc); - if (needSync) { + // Transient-hide may be hidden later, so no need to request redraw. + if (!isInTransientHide(wc)) { mSyncEngine.addToSyncSet(mSyncId, wc); } ChangeInfo info = mChanges.get(wc); @@ -1101,6 +1099,16 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final Task task = ar.getTask(); if (task == null) continue; boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar); + // visibleAtTransitionEnd is used to guard against pre-maturely committing + // invisible on a window which is actually hidden by a later transition and not this + // one. However, for a transient launch, we can't use this mechanism because the + // visibility is determined at finish. Instead, use a different heuristic: don't + // commit invisible if the window is already in a later transition. That later + // transition will then handle the commit. + if (isTransientLaunch(ar) && !ar.isVisibleRequested() + && mController.inCollectingTransition(ar)) { + visibleAtTransitionEnd = true; + } // We need both the expected visibility AND current requested-visibility to be // false. If it is expected-visible but not currently visible, it means that // another animation is queued-up to animate this to invisibility, so we can't @@ -2657,7 +2665,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } private void validateKeyguardOcclusion() { - if ((mFlags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { + if ((mFlags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) { mController.mStateValidators.add( mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange); } @@ -2686,6 +2694,26 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { }); } + /** + * Returns {@code true} if the transition and the corresponding transaction should be applied + * on display thread. Currently, this only checks for display rotation change because the order + * of dispatching the new display info will be after requesting the windows to sync drawing. + * That avoids potential flickering of screen overlays (e.g. cutout, rounded corner). Also, + * because the display thread has a higher priority, it is faster to perform the configuration + * changes and window hierarchy traversal. + */ + boolean shouldApplyOnDisplayThread() { + for (int i = mParticipants.size() - 1; i >= 0; --i) { + final DisplayContent dc = mParticipants.valueAt(i).asDisplayContent(); + if (dc == null) continue; + final ChangeInfo changeInfo = mChanges.get(dc); + if (changeInfo != null && changeInfo.mRotation != dc.getRotation()) { + return Looper.myLooper() != mController.mAtm.mWindowManager.mH.getLooper(); + } + } + return false; + } + /** Applies the new configuration for the changed displays. */ void applyDisplayChangeIfNeeded() { for (int i = mParticipants.size() - 1; i >= 0; --i) { diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index a539a4893d4f..79cb61be5948 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -588,15 +588,6 @@ class TransitionController { /** Sets the sync method for the display change. */ private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange, @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) { - final int startRotation = displayChange.getStartRotation(); - final int endRotation = displayChange.getEndRotation(); - if (startRotation != endRotation && (startRotation + endRotation) % 2 == 0) { - // 180 degrees rotation change may not change screen size. So the clients may draw - // some frames before and after the display projection transaction is applied by the - // remote player. That may cause some buffers to show in different rotation. So use - // sync method to pause clients drawing until the projection transaction is applied. - mSyncEngine.setSyncMethod(displayTransition.getSyncId(), BLASTSyncEngine.METHOD_BLAST); - } final Rect startBounds = displayChange.getStartAbsBounds(); final Rect endBounds = displayChange.getEndAbsBounds(); if (startBounds == null || endBounds == null) return; diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 9e7df004806b..805e7ffe7d76 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -30,6 +30,7 @@ import android.graphics.Region; import android.hardware.display.DisplayManagerInternal; import android.os.Bundle; import android.os.IBinder; +import android.os.Message; import android.util.Pair; import android.view.ContentRecordingSession; import android.view.Display; @@ -515,12 +516,13 @@ public abstract class WindowManagerInternal { * Invalidate all visible windows on a given display, and report back on the callback when all * windows have redrawn. * - * @param callback reporting callback to be called when all windows have redrawn. + * @param message The message will be sent when all windows have redrawn. Note that the message + * must be obtained from handler, otherwise it will throw NPE. * @param timeout calls the callback anyway after the timeout. * @param displayId waits for the windows on the given display, INVALID_DISPLAY to wait for all * windows on all displays. */ - public abstract void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId); + public abstract void waitForAllWindowsDrawn(Message message, long timeout, int displayId); /** * Overrides the display size. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index bb3d10912724..68a6384bc109 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -601,7 +601,7 @@ public class WindowManagerService extends IWindowManager.Stub * The callbacks to make when the windows all have been drawn for a given * {@link WindowContainer}. */ - final HashMap<WindowContainer, Runnable> mWaitingForDrawnCallbacks = new HashMap<>(); + final ArrayMap<WindowContainer<?>, Message> mWaitingForDrawnCallbacks = new ArrayMap<>(); /** List of window currently causing non-system overlay windows to be hidden. */ private ArrayList<WindowState> mHidingNonSystemOverlayWindows = new ArrayList<>(); @@ -1789,9 +1789,6 @@ public class WindowManagerService extends IWindowManager.Stub winAnimator.mEnterAnimationPending = true; winAnimator.mEnteringAnimation = true; - if (displayPolicy.areSystemBarsForcedConsumedLw()) { - res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS; - } if (displayContent.isInTouchMode()) { res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE; } @@ -2494,9 +2491,6 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mActivityRecord != null) { win.mActivityRecord.updateReportedVisibilityLocked(); } - if (displayPolicy.areSystemBarsForcedConsumedLw()) { - result |= WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS; - } if (!win.isGoneForLayout()) { win.mResizedWhileGone = false; } @@ -5369,8 +5363,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int CLIENT_FREEZE_TIMEOUT = 30; public static final int NOTIFY_ACTIVITY_DRAWN = 32; - public static final int ALL_WINDOWS_DRAWN = 33; - public static final int NEW_ANIMATOR_SCALE = 34; public static final int SHOW_EMULATOR_DISPLAY_OVERLAY = 36; @@ -5492,7 +5484,7 @@ public class WindowManagerService extends IWindowManager.Stub } case WAITING_FOR_DRAWN_TIMEOUT: { - Runnable callback = null; + final Message callback; final WindowContainer<?> container = (WindowContainer<?>) msg.obj; synchronized (mGlobalLock) { ProtoLog.w(WM_ERROR, "Timeout waiting for drawn: undrawn=%s", @@ -5506,7 +5498,7 @@ public class WindowManagerService extends IWindowManager.Stub callback = mWaitingForDrawnCallbacks.remove(container); } if (callback != null) { - callback.run(); + callback.sendToTarget(); } break; } @@ -5530,17 +5522,6 @@ public class WindowManagerService extends IWindowManager.Stub } break; } - case ALL_WINDOWS_DRAWN: { - Runnable callback; - final WindowContainer container = (WindowContainer) msg.obj; - synchronized (mGlobalLock) { - callback = mWaitingForDrawnCallbacks.remove(container); - } - if (callback != null) { - callback.run(); - } - break; - } case NEW_ANIMATOR_SCALE: { float scale = getCurrentAnimatorScale(); ValueAnimator.setDurationScale(scale); @@ -6098,7 +6079,8 @@ public class WindowManagerService extends IWindowManager.Stub if (mWaitingForDrawnCallbacks.isEmpty()) { return; } - mWaitingForDrawnCallbacks.forEach((container, callback) -> { + for (int i = mWaitingForDrawnCallbacks.size() - 1; i >= 0; i--) { + final WindowContainer<?> container = mWaitingForDrawnCallbacks.keyAt(i); for (int j = container.mWaitingForDrawn.size() - 1; j >= 0; j--) { final WindowState win = (WindowState) container.mWaitingForDrawn.get(j); ProtoLog.i(WM_DEBUG_SCREEN_ON, @@ -6124,9 +6106,9 @@ public class WindowManagerService extends IWindowManager.Stub if (container.mWaitingForDrawn.isEmpty()) { ProtoLog.d(WM_DEBUG_SCREEN_ON, "All windows drawn!"); mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, container); - mH.sendMessage(mH.obtainMessage(H.ALL_WINDOWS_DRAWN, container)); + mWaitingForDrawnCallbacks.removeAt(i).sendToTarget(); } - }); + } } private void traceStartWaitingForWindowDrawn(WindowState window) { @@ -7842,13 +7824,14 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId) { + public void waitForAllWindowsDrawn(Message message, long timeout, int displayId) { + Objects.requireNonNull(message.getTarget()); final WindowContainer<?> container = displayId == INVALID_DISPLAY ? mRoot : mRoot.getDisplayContent(displayId); if (container == null) { // The waiting container doesn't exist, no need to wait to run the callback. Run and // return; - callback.run(); + message.sendToTarget(); return; } boolean allWindowsDrawn = false; @@ -7865,13 +7848,13 @@ public class WindowManagerService extends IWindowManager.Stub } } - mWaitingForDrawnCallbacks.put(container, callback); + mWaitingForDrawnCallbacks.put(container, message); mH.sendNewMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, container, timeout); checkDrawnWindowsLocked(); } } if (allWindowsDrawn) { - callback.run(); + message.sendToTarget(); } } @@ -9081,7 +9064,7 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public boolean getWindowInsets(int displayId, IBinder token, InsetsState outInsetsState) { + public void getWindowInsets(int displayId, IBinder token, InsetsState outInsetsState) { final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { @@ -9092,7 +9075,6 @@ public class WindowManagerService extends IWindowManager.Stub } final WindowToken winToken = dc.getWindowToken(token); dc.getInsetsPolicy().getInsetsForWindowMetrics(winToken, outInsetsState); - return dc.getDisplayPolicy().areSystemBarsForcedConsumedLw(); } } finally { Binder.restoreCallingIdentity(origId); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index a2f7ba499fdf..31918f478e44 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -321,9 +321,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller); return transition.getToken(); } - transition.start(); transition.mLogger.mStartWCT = wct; - applyTransaction(wct, -1 /*syncId*/, transition, caller); + if (transition.shouldApplyOnDisplayThread()) { + mService.mH.post(() -> { + synchronized (mService.mGlobalLock) { + transition.start(); + applyTransaction(wct, -1 /* syncId */, transition, caller); + } + }); + } else { + transition.start(); + applyTransaction(wct, -1 /* syncId */, transition, caller); + } // Since the transition is already provided, it means WMCore is determining the // "readiness lifecycle" outside the provided transaction, so don't set ready here. return transition.getToken(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index cf1e51fb4e94..ef44702041a5 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3732,8 +3732,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final boolean isDragResizeChanged = isDragResizeChanged(); final boolean forceRelayout = syncWithBuffers || isDragResizeChanged; final DisplayContent displayContent = getDisplayContent(); - final boolean alwaysConsumeSystemBars = - displayContent.getDisplayPolicy().areSystemBarsForcedConsumedLw(); final int displayId = displayContent.getDisplayId(); if (isDragResizeChanged) { @@ -3745,7 +3743,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP try { mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration, - getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId, + getCompatInsetsState(), forceRelayout, displayId, syncWithBuffers ? mSyncSeqId : -1, isDragResizing); if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration .getMergedConfiguration().windowConfiguration.getRotation()) { @@ -5665,7 +5663,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } if (mIsWallpaper) { // TODO(b/233286785): Add sync support to wallpaper. - return false; + return true; } // In the WindowContainer implementation we immediately mark ready // since a generic WindowContainer only needs to wait for its diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index c065cb5f4ebe..e76cbe448f15 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -971,7 +971,7 @@ void NativeInputManager::notifyDropWindow(const sp<IBinder>& token, float x, flo void NativeInputManager::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, const std::set<gui::Uid>& uids) { static const bool ENABLE_INPUT_DEVICE_USAGE_METRICS = - sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true); + sysprop::InputProperties::enable_input_device_usage_metrics().value_or(false); if (!ENABLE_INPUT_DEVICE_USAGE_METRICS) return; mInputManager->getMetricsCollector().notifyDeviceInteraction(deviceId, timestamp, uids); diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java index 46c90b4bc731..bafa4a56b28a 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java @@ -126,6 +126,7 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption mElementKeys = new HashSet<>(requestOption .getCredentialRetrievalData() .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS)); + mStatus = Status.PENDING; } protected ProviderRegistryGetSession(@NonNull Context context, @@ -143,6 +144,7 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption mElementKeys = new HashSet<>(requestOption .getCredentialRetrievalData() .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS)); + mStatus = Status.PENDING; } private List<Entry> prepareUiCredentialEntries( diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 417fc3949f2c..fe913b9807cf 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -16229,6 +16229,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override + public ComponentName getDeviceOwnerComponent(boolean callingUserOnly) { + return DevicePolicyManagerService.this.getDeviceOwnerComponent(callingUserOnly); + } + + @Override public int getDeviceOwnerUserId() { return DevicePolicyManagerService.this.getDeviceOwnerUserId(); } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java index a6ada4d77253..869497c28def 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java @@ -20,6 +20,7 @@ import static android.inputmethodservice.InputMethodService.IME_ACTIVE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE; import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT; +import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER; import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT; import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME; import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT; @@ -43,6 +44,7 @@ import android.view.inputmethod.InputMethodManager; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; @@ -165,6 +167,29 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe verify(mMockImeTargetVisibilityPolicy).removeImeScreenshot(eq(Display.DEFAULT_DISPLAY)); } + @Test + public void testApplyImeVisibility_hideImeWhenUnbinding() { + mInputMethodManagerService.setAttachedClientForTesting(null); + startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE); + ExtendedMockito.spyOn(mVisibilityApplier); + + synchronized (ImfLock.class) { + // Simulate the system hides the IME when switching IME services in different users. + // (e.g. unbinding the IME from the current user to the profile user) + final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked(); + mInputMethodManagerService.hideCurrentInputLocked(mWindowToken, null, 0, null, + HIDE_SWITCH_USER); + mInputMethodManagerService.onUnbindCurrentMethodByReset(); + + // Expects applyImeVisibility() -> hideIme() will be called to notify WM for syncing + // the IME hidden state. + verify(mVisibilityApplier).applyImeVisibility(eq(mWindowToken), any(), + eq(STATE_HIDE_IME)); + verify(mInputMethodManagerService.mWindowManagerInternal).hideIme( + eq(mWindowToken), eq(displayIdToShowIme), eq(null)); + } + } + private InputBindResult startInputOrWindowGainedFocus(IBinder windowToken, int softInputMode) { return mInputMethodManagerService.startInputOrWindowGainedFocus( StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */, diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java index e33ca7775e22..b7a0cf389396 100644 --- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java +++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java @@ -138,6 +138,14 @@ public class CrossUserPackageVisibilityTests { } @Test + public void testGetUserMinAspectRatio_withCrossUserId() { + final int crossUserId = UserHandle.myUserId() + 1; + assertThrows(SecurityException.class, + () -> mIPackageManager.getUserMinAspectRatio( + mInstrumentation.getContext().getPackageName(), crossUserId)); + } + + @Test public void testIsPackageSignedByKeySet_cannotDetectCrossUserPkg() throws Exception { final KeySet keySet = mIPackageManager.getSigningKeySet(mContext.getPackageName()); assertThrows(IllegalArgumentException.class, diff --git a/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 Binary files differindex 30bb6478d18d..6feebb8c833c 100644 --- a/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 +++ b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java index 435f0d7f5f15..a805e5c9f87e 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java @@ -20,6 +20,7 @@ import static com.android.compatibility.common.util.ShellUtils.runShellCommand; import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import static java.lang.reflect.Modifier.isFinal; @@ -138,7 +139,7 @@ public class PackageManagerServiceTest { .setSecondaryCpuAbiString("secondaryCpuAbiString") .setCpuAbiOverrideString("cpuAbiOverrideString") .build(); - pri.populateUsers(new int[] { + pri.populateUsers(new int[]{ 1, 2, 3, 4, 5 }, setting); Assert.assertNotNull(pri.mBroadcastUsers); @@ -150,7 +151,7 @@ public class PackageManagerServiceTest { pri.mBroadcastUsers = null; final int EXCLUDED_USER_ID = 4; setting.setInstantApp(true, EXCLUDED_USER_ID); - pri.populateUsers(new int[] { + pri.populateUsers(new int[]{ 1, 2, 3, EXCLUDED_USER_ID, 5 }, setting); Assert.assertNotNull(pri.mBroadcastUsers); @@ -164,8 +165,8 @@ public class PackageManagerServiceTest { @Test public void testPartitions() { - String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" }; - String[] appdir = { "app", "priv-app" }; + String[] partitions = {"system", "vendor", "odm", "oem", "product", "system_ext"}; + String[] appdir = {"app", "priv-app"}; for (int i = 0; i < partitions.length; i++) { final ScanPartition scanPartition = PackageManagerService.SYSTEM_PARTITIONS.get(i); @@ -425,10 +426,10 @@ public class PackageManagerServiceTest { private String displayName(Method m) { String r = m.getName(); String p = Arrays.toString(m.getGenericParameterTypes()) - .replaceAll("([a-zA-Z0-9]+\\.)+", "") - .replace("class ", "") - .replaceAll("^\\[", "(") - .replaceAll("\\]$", ")"); + .replaceAll("([a-zA-Z0-9]+\\.)+", "") + .replace("class ", "") + .replaceAll("^\\[", "(") + .replaceAll("\\]$", ")"); return r + p; } @@ -612,4 +613,22 @@ public class PackageManagerServiceTest { runShellCommand("pm uninstall " + TEST_PKG_NAME); } } + + @Test + public void testSetUserMinAspectRatio_samePackage_succeeds() throws Exception { + mIPackageManager.setUserMinAspectRatio(PACKAGE_NAME, UserHandle.myUserId(), + PackageManager.USER_MIN_ASPECT_RATIO_UNSET); + // Invoking setUserMinAspectRatio on the same package shouldn't get any exception. + } + + @Test + public void testSetUserMinAspectRatio_differentPackage_fails() { + final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK); + runShellCommand("pm install " + testApk); + assertThrows(SecurityException.class, () -> { + mIPackageManager.setUserMinAspectRatio(TEST_PKG_NAME, UserHandle.myUserId(), + PackageManager.USER_MIN_ASPECT_RATIO_UNSET); + }); + runShellCommand("pm uninstall " + TEST_PKG_NAME); + } } diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index 836f8581e8eb..16fb012b2f40 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -971,7 +971,7 @@ public class PackageManagerSettingsTests { origPkgSetting01.setUserState(0, 100, 1, true, false, false, false, 0, null, false, false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}), new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning", - "splashScreenTheme", 1000L); + "splashScreenTheme", 1000L, PackageManager.USER_MIN_ASPECT_RATIO_UNSET); final PersistableBundle appExtras1 = createPersistableBundle( PACKAGE_NAME_1, 1L, 0.01, true, "appString1"); final PersistableBundle launcherExtras1 = createPersistableBundle( @@ -1638,7 +1638,8 @@ public class PackageManagerSettingsTests { : oldUserState.getSharedLibraryOverlayPaths() == null) && userState.getSplashScreenTheme().equals( oldUserState.getSplashScreenTheme()) - && userState.getUninstallReason() == oldUserState.getUninstallReason(); + && userState.getUninstallReason() == oldUserState.getUninstallReason() + && userState.getMinAspectRatio() == oldUserState.getMinAspectRatio(); } private SharedUserSetting createSharedUserSetting(Settings settings, String userName, diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp index b242ec2db420..f1ff33809184 100644 --- a/services/tests/displayservicetests/Android.bp +++ b/services/tests/displayservicetests/Android.bp @@ -22,11 +22,20 @@ android_test { "src/**/*.java", ], + libs: [ + "android.test.mock", + ], + static_libs: [ - "services.core", - "androidx.test.runner", - "androidx.test.rules", + "androidx.test.ext.junit", + "display-core-libs", + "frameworks-base-testutils", + "junit", + "junit-params", + "platform-compat-test-rules", "platform-test-annotations", + "services.core", + "servicestests-utils", ], defaults: [ @@ -47,3 +56,10 @@ android_test { enabled: false, }, } + +java_library { + name: "display-core-libs", + srcs: [ + "src/com/android/server/display/TestUtils.java", + ], +} diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml index c2e417429eac..d2bd10dd18dc 100644 --- a/services/tests/displayservicetests/AndroidManifest.xml +++ b/services/tests/displayservicetests/AndroidManifest.xml @@ -21,6 +21,16 @@ Insert permissions here. eg: <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> --> + <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" /> + <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> + <uses-permission android:name="android.permission.DEVICE_POWER" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" /> + <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" /> + <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> + <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application android:debuggable="true" android:testOnly="true"> diff --git a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java index 2c4fe536b75c..7333bc75fe9d 100644 --- a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java @@ -11,7 +11,7 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ package com.android.server.display; diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index a6acd60f3bd7..a6acd60f3bd7 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java index 5f81869903c3..ee7826f13578 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java @@ -11,7 +11,7 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ package com.android.server.display; @@ -206,11 +206,11 @@ public class BrightnessMappingStrategyTest { BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); strategy.setBrightnessConfiguration(null); - final int N = DISPLAY_LEVELS_BACKLIGHT.length; + final int n = DISPLAY_LEVELS_BACKLIGHT.length; final float expectedBrightness = - (float) DISPLAY_LEVELS_BACKLIGHT[N - 1] / PowerManager.BRIGHTNESS_ON; + (float) DISPLAY_LEVELS_BACKLIGHT[n - 1] / PowerManager.BRIGHTNESS_ON; assertEquals(expectedBrightness, - strategy.getBrightness(LUX_LEVELS[N - 1]), 0.0001f /*tolerance*/); + strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/); } @Test @@ -270,10 +270,10 @@ public class BrightnessMappingStrategyTest { // Check that null returns us to the default configuration. strategy.setBrightnessConfiguration(null); - final int N = DISPLAY_LEVELS_NITS.length; - final float expectedBrightness = DISPLAY_LEVELS_NITS[N - 1] / DISPLAY_RANGE_NITS[1]; + final int n = DISPLAY_LEVELS_NITS.length; + final float expectedBrightness = DISPLAY_LEVELS_NITS[n - 1] / DISPLAY_RANGE_NITS[1]; assertEquals(expectedBrightness, - strategy.getBrightness(LUX_LEVELS[N - 1]), 0.0001f /*tolerance*/); + strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/); } @Test diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java index 46956d74cc5c..8faaf5998d13 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java @@ -159,7 +159,7 @@ public class BrightnessThrottlerTest { @Test public void testThermalThrottlingSingleLevel() throws Exception { final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, - 0.25f); + 0.25f); List<ThrottlingLevel> levels = new ArrayList<>(); levels.add(level); @@ -184,7 +184,7 @@ public class BrightnessThrottlerTest { assertEquals(level.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Set status more than high enough to trigger throttling listener.notifyThrottling(getSkinTemp(level.thermalStatus + 1)); @@ -192,7 +192,7 @@ public class BrightnessThrottlerTest { assertEquals(level.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Return to the lower throttling level listener.notifyThrottling(getSkinTemp(level.thermalStatus)); @@ -200,7 +200,7 @@ public class BrightnessThrottlerTest { assertEquals(level.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Cool down listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1)); @@ -208,15 +208,15 @@ public class BrightnessThrottlerTest { assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); assertFalse(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); } @Test public void testThermalThrottlingMultiLevel() throws Exception { final ThrottlingLevel levelLo = new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, - 0.62f); + 0.62f); final ThrottlingLevel levelHi = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, - 0.25f); + 0.25f); List<ThrottlingLevel> levels = new ArrayList<>(); levels.add(levelLo); @@ -242,7 +242,7 @@ public class BrightnessThrottlerTest { assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Set status to an intermediate throttling level listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1)); @@ -250,7 +250,7 @@ public class BrightnessThrottlerTest { assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Set status to the highest configured throttling level listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus)); @@ -258,7 +258,7 @@ public class BrightnessThrottlerTest { assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Set status to exceed the highest configured throttling level listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus + 1)); @@ -266,7 +266,7 @@ public class BrightnessThrottlerTest { assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Return to an intermediate throttling level listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1)); @@ -274,7 +274,7 @@ public class BrightnessThrottlerTest { assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Return to the lowest configured throttling level listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus)); @@ -282,7 +282,7 @@ public class BrightnessThrottlerTest { assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Cool down listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1)); diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java index 021f2d1df835..44c7dec7633e 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java @@ -11,7 +11,7 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ package com.android.server.display; @@ -395,8 +395,8 @@ public class BrightnessTrackerTest { final long currentTime = mInjector.currentTimeMillis(); notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1000.0f}, new long[] {TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos())}); - List<BrightnessChangeEvent> eventsNoPackage - = mTracker.getEvents(0, false).getList(); + List<BrightnessChangeEvent> eventsNoPackage = + mTracker.getEvents(0, false).getList(); List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList(); mTracker.stop(); @@ -1037,9 +1037,9 @@ public class BrightnessTrackerTest { } void setBrightnessMode(boolean isBrightnessModeAutomatic) { - mIsBrightnessModeAutomatic = isBrightnessModeAutomatic; - mContentObserver.dispatchChange(false, null); - waitForHandler(); + mIsBrightnessModeAutomatic = isBrightnessModeAutomatic; + mContentObserver.dispatchChange(false, null); + waitForHandler(); } void sendScreenChange(boolean screenOn) { @@ -1184,8 +1184,8 @@ public class BrightnessTrackerTest { @Override public int getNightDisplayColorTemperature(Context context) { - return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, - mDefaultNightModeColorTemperature); + return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, + mDefaultNightModeColorTemperature); } @Override diff --git a/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java b/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java index 53d8de0c2bbb..53d8de0c2bbb 100644 --- a/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java index 130e6ad91b49..130e6ad91b49 100644 --- a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index 8b04eca69132..8b04eca69132 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 5db9d1f6f5bd..d16c9c59bb1b 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -142,7 +142,7 @@ public class DisplayManagerServiceTest { private static final float FLOAT_TOLERANCE = 0.01f; private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display"; - private static final String PACKAGE_NAME = "com.android.frameworks.servicestests"; + private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests"; private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; @@ -238,7 +238,7 @@ public class DisplayManagerServiceTest { boolean getHdrOutputConversionSupport() { return true; } - } + } private final DisplayManagerService.Injector mBasicInjector = new BasicInjector(); diff --git a/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/HbmEventTest.java index 24fc34849829..24fc34849829 100644 --- a/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/HbmEventTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java index e2a66f03f5ca..76e6ec7f6780 100644 --- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java @@ -483,8 +483,10 @@ public class HighBrightnessModeControllerTest { // Verify Stats HBM_ON_HDR verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/, 0, 0, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/); @@ -492,8 +494,10 @@ public class HighBrightnessModeControllerTest { // Verify Stats HBM_OFF verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); @@ -501,16 +505,20 @@ public class HighBrightnessModeControllerTest { // Verify Stats HBM_ON_SUNLIGHT verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); hbmc.onAmbientLuxChange(1); advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1); // Verify Stats HBM_OFF verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP)); } @Test @@ -527,8 +535,8 @@ public class HighBrightnessModeControllerTest { // Verify Stats HBM_ON_HDR not report verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), - anyInt()); + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), + anyInt()); } @Test @@ -545,8 +553,8 @@ public class HighBrightnessModeControllerTest { // Verify Stats HBM_ON_SUNLIGHT not report verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), - anyInt()); + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + anyInt()); } // Test reporting of thermal throttling when triggered externally through @@ -565,8 +573,10 @@ public class HighBrightnessModeControllerTest { BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE); advanceTime(1); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); // Brightness is thermally throttled, HBM brightness denied (NBM brightness granted) hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness, @@ -578,8 +588,8 @@ public class HighBrightnessModeControllerTest { // the HBM transition point. assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode()); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT)); + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT)); } @Test @@ -592,15 +602,17 @@ public class HighBrightnessModeControllerTest { hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(0); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); // Use up all the time in the window. advanceTime(TIME_WINDOW_MILLIS + 1); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT)); + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT)); } @Test @@ -613,13 +625,17 @@ public class HighBrightnessModeControllerTest { hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(0); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF)); } @Test @@ -632,16 +648,18 @@ public class HighBrightnessModeControllerTest { hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(0); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/, DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/); advanceTime(0); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING)); + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING)); } @Test @@ -657,15 +675,17 @@ public class HighBrightnessModeControllerTest { assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode()); // verify HBM_ON_SUNLIGHT verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN); // verify HBM_SV_OFF due to LOW_REQUESTED_BRIGHTNESS verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), - eq(FrameworkStatsLog - .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS)); + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS)); } private void assertState(HighBrightnessModeController hbmc, diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataTest.java index ede54e096ad0..ede54e096ad0 100644 --- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java index 1eec70da3d20..1eec70da3d20 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java index 30ff8ba6e331..20654797a5d2 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java @@ -40,6 +40,7 @@ import androidx.test.filters.SmallTest; import com.android.server.display.layout.Layout; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.io.InputStream; @@ -121,7 +122,9 @@ public class LogicalDisplayTest { assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); } + // TODO: b/288880734 - fix test after display tests migration @Test + @Ignore public void testDisplayInputFlags() { SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); diff --git a/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java index c379d6b79ee7..c379d6b79ee7 100644 --- a/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java index 642f54c25a46..9f91916a4046 100644 --- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java @@ -11,7 +11,7 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ package com.android.server.display; diff --git a/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java index 5b10dc4e0bab..5b10dc4e0bab 100644 --- a/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/utils/SensorUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/SensorUtilsTest.java index 4494b0c412dc..4494b0c412dc 100644 --- a/services/tests/servicestests/src/com/android/server/display/utils/SensorUtilsTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/SensorUtilsTest.java diff --git a/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java b/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java new file mode 100644 index 000000000000..8b45145b160f --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.input.InputSensorInfo; +import android.os.Parcel; +import android.os.SystemClock; +import android.view.DisplayAddress; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public final class TestUtils { + + public static SensorEvent createSensorEvent(Sensor sensor, int value) throws Exception { + final Constructor<SensorEvent> constructor = + SensorEvent.class.getDeclaredConstructor(int.class); + constructor.setAccessible(true); + final SensorEvent event = constructor.newInstance(1); + event.sensor = sensor; + event.values[0] = value; + event.timestamp = SystemClock.elapsedRealtimeNanos(); + return event; + } + + + public static void setSensorType(Sensor sensor, int type, String strType) throws Exception { + Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE); + setter.setAccessible(true); + setter.invoke(sensor, type); + if (strType != null) { + Field f = sensor.getClass().getDeclaredField("mStringType"); + f.setAccessible(true); + f.set(sensor, strType); + } + } + + public static void setMaximumRange(Sensor sensor, float maximumRange) throws Exception { + Method setter = Sensor.class.getDeclaredMethod("setRange", Float.TYPE, Float.TYPE); + setter.setAccessible(true); + setter.invoke(sensor, maximumRange, 1); + } + + public static Sensor createSensor(int type, String strType) throws Exception { + Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor(); + constr.setAccessible(true); + Sensor sensor = constr.newInstance(); + setSensorType(sensor, type, strType); + return sensor; + } + + public static Sensor createSensor(int type, String strType, float maximumRange) + throws Exception { + Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor(); + constr.setAccessible(true); + Sensor sensor = constr.newInstance(); + setSensorType(sensor, type, strType); + setMaximumRange(sensor, maximumRange); + return sensor; + } + + public static Sensor createSensor(String type, String name) { + return new Sensor(new InputSensorInfo( + name, "vendor", 0, 0, 0, 1f, 1f, 1, 1, 1, 1, + type, "", 0, 0, 0)); + } + + /** + * Create a custom {@link DisplayAddress} to ensure we're not relying on any specific + * display-address implementation in our code. Intentionally uses default object (reference) + * equality rules. + */ + public static class TestDisplayAddress extends DisplayAddress { + @Override + public void writeToParcel(Parcel out, int flags) { } + } +} diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java index c0c63c69add8..c0c63c69add8 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java index e58b3e891b70..e58b3e891b70 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java index c4f483810478..c4f483810478 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java index a9e616d766c6..a9e616d766c6 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java index b652576a75c8..b652576a75c8 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java index c4346317a6ef..c4346317a6ef 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java index d60caf6efb7a..d60caf6efb7a 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java index 081f19d19f75..081f19d19f75 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java index 530245dacd8b..530245dacd8b 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java index 7147aa8d3701..7147aa8d3701 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java index 9830edbea645..9830edbea645 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/AppSaturationControllerTest.java index a525814435ea..a525814435ea 100644 --- a/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/AppSaturationControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/CctEvaluatorTest.java index b96666ae40a3..b96666ae40a3 100644 --- a/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/CctEvaluatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java index 618ab1b75587..c7c09b5deb35 100644 --- a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java @@ -1093,15 +1093,15 @@ public class ColorDisplayServiceTest { @Test public void compositionColorSpaces_invalidResources() { when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes)) - .thenReturn(new int[] { - ColorDisplayManager.COLOR_MODE_NATURAL, - // Missing second color mode - }); + .thenReturn(new int[] { + ColorDisplayManager.COLOR_MODE_NATURAL, + // Missing second color mode + }); when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces)) - .thenReturn(new int[] { - Display.COLOR_MODE_SRGB, - Display.COLOR_MODE_DISPLAY_P3 - }); + .thenReturn(new int[] { + Display.COLOR_MODE_SRGB, + Display.COLOR_MODE_DISPLAY_P3 + }); setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); startService(); verify(mDisplayTransformManager).setColorMode( @@ -1111,13 +1111,13 @@ public class ColorDisplayServiceTest { @Test public void compositionColorSpaces_validResources_validColorMode() { when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes)) - .thenReturn(new int[] { - ColorDisplayManager.COLOR_MODE_NATURAL - }); + .thenReturn(new int[] { + ColorDisplayManager.COLOR_MODE_NATURAL + }); when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces)) - .thenReturn(new int[] { - Display.COLOR_MODE_SRGB, - }); + .thenReturn(new int[] { + Display.COLOR_MODE_SRGB, + }); setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); startService(); verify(mDisplayTransformManager).setColorMode( @@ -1127,13 +1127,13 @@ public class ColorDisplayServiceTest { @Test public void compositionColorSpaces_validResources_invalidColorMode() { when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes)) - .thenReturn(new int[] { - ColorDisplayManager.COLOR_MODE_NATURAL - }); + .thenReturn(new int[] { + ColorDisplayManager.COLOR_MODE_NATURAL + }); when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces)) - .thenReturn(new int[] { - Display.COLOR_MODE_SRGB, - }); + .thenReturn(new int[] { + Display.COLOR_MODE_SRGB, + }); setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED); startService(); verify(mDisplayTransformManager).setColorMode( @@ -1143,7 +1143,7 @@ public class ColorDisplayServiceTest { @Test public void getColorMode_noAvailableModes_returnsNotSet() { when(mResourcesSpy.getIntArray(R.array.config_availableColorModes)) - .thenReturn(new int[] {}); + .thenReturn(new int[] {}); startService(); verify(mDisplayTransformManager, never()).setColorMode(anyInt(), any(), anyInt()); assertThat(mBinderService.getColorMode()).isEqualTo(-1); diff --git a/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java index e0bef1a83821..e0bef1a83821 100644 --- a/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java index 4f0cb324f17f..4f0cb324f17f 100644 --- a/services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java index 35014dcb7492..35014dcb7492 100644 --- a/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index 04273d6f4ed6..04273d6f4ed6 100644 --- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java index 9ab6ee5bd230..9ab6ee5bd230 100644 --- a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java index 287fdd5c344b..287fdd5c344b 100644 --- a/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/utils/AmbientFilterTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/AmbientFilterTest.java index 9b76b13d2ede..9b76b13d2ede 100644 --- a/services/tests/servicestests/src/com/android/server/display/utils/AmbientFilterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/utils/AmbientFilterTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java index 4d2551087c59..4d2551087c59 100644 --- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java +++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java index ac97911027bf..f975b6fd1d6f 100644 --- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java @@ -268,9 +268,9 @@ public final class AmbientLuxTest { controller.mBrightnessFilter = spy(new AmbientFilterStubber()); for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) { - setEstimatedBrightnessAndUpdate(controller, luxOverride); - assertEquals(controller.mPendingAmbientColorTemperature, - ambientColorTemperature, 0.001); + setEstimatedBrightnessAndUpdate(controller, luxOverride); + assertEquals(controller.mPendingAmbientColorTemperature, + ambientColorTemperature, 0.001); } } @@ -286,9 +286,9 @@ public final class AmbientLuxTest { controller.mBrightnessFilter = spy(new AmbientFilterStubber()); for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) { - setEstimatedBrightnessAndUpdate(controller, luxOverride); - assertEquals(controller.mPendingAmbientColorTemperature, - ambientColorTemperature, 0.001); + setEstimatedBrightnessAndUpdate(controller, luxOverride); + assertEquals(controller.mPendingAmbientColorTemperature, + ambientColorTemperature, 0.001); } } @@ -366,22 +366,22 @@ public final class AmbientLuxTest { @Test public void testSpline_InvalidCombinations() throws Exception { - setBrightnesses(100.0f, 200.0f); - setBiases(0.0f, 1.0f); - setHighLightBrightnesses(150.0f, 250.0f); - setHighLightBiases(0.0f, 1.0f); - - DisplayWhiteBalanceController controller = - DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy); - final float ambientColorTemperature = 8000.0f; - setEstimatedColorTemperature(controller, ambientColorTemperature); - controller.mBrightnessFilter = spy(new AmbientFilterStubber()); - - for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) { - setEstimatedBrightnessAndUpdate(controller, luxOverride); - assertEquals(controller.mPendingAmbientColorTemperature, - ambientColorTemperature, 0.001); - } + setBrightnesses(100.0f, 200.0f); + setBiases(0.0f, 1.0f); + setHighLightBrightnesses(150.0f, 250.0f); + setHighLightBiases(0.0f, 1.0f); + + DisplayWhiteBalanceController controller = + DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy); + final float ambientColorTemperature = 8000.0f; + setEstimatedColorTemperature(controller, ambientColorTemperature); + controller.mBrightnessFilter = spy(new AmbientFilterStubber()); + + for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) { + setEstimatedBrightnessAndUpdate(controller, luxOverride); + assertEquals(controller.mPendingAmbientColorTemperature, + ambientColorTemperature, 0.001); + } } @Test @@ -486,7 +486,7 @@ public final class AmbientLuxTest { private void mockResourcesFloat(int id, float floatValue) { doAnswer(new Answer<Void>() { public Void answer(InvocationOnMock invocation) { - TypedValue value = (TypedValue)invocation.getArgument(1); + TypedValue value = (TypedValue) invocation.getArgument(1); value.type = TypedValue.TYPE_FLOAT; value.data = Float.floatToIntBits(floatValue); return null; diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientSensorTest.java index 3e3e535df986..3e3e535df986 100644 --- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientSensorTest.java diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index 32243f04f6e8..212a243c6a9e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -2221,7 +2221,7 @@ public class GameManagerServiceTests { String[] packages = {mPackageName}; when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true); } @@ -2238,12 +2238,12 @@ public class GameManagerServiceTests { doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1))) .when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean()); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); assertTrue(powerState.get(Mode.GAME)); gameManagerService.mUidObserver.onUidStateChanged( DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0); assertTrue(powerState.get(Mode.GAME)); gameManagerService.mUidObserver.onUidStateChanged( somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); @@ -2260,13 +2260,13 @@ public class GameManagerServiceTests { int somePackageId = DEFAULT_PACKAGE_UID + 1; when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true); verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false); } @@ -2277,9 +2277,9 @@ public class GameManagerServiceTests { String[] packages = {mPackageName}; when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false); } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index f89f73c98cfd..aa0a2fea1a5a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -1257,6 +1257,17 @@ public class LocalDisplayAdapterTest { public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { return mSurfaceControlProxy; } + + // Instead of using DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay) + // we should use DisplayDeviceConfig.create(context, isFirstDisplay) for the test to ensure + // that real device DisplayDeviceConfig is not loaded for FakeDisplay and we are getting + // consistent behaviour. Please also note that context passed to this method, is + // mMockContext and values will be loaded from mMockResources. + @Override + public DisplayDeviceConfig createDisplayDeviceConfig(Context context, + long physicalDisplayId, boolean isFirstDisplay) { + return DisplayDeviceConfig.create(context, isFirstDisplay); + } } private class TestListener implements DisplayAdapter.Listener { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index e7b3e6f88dce..617e4ebf11ad 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -325,6 +325,83 @@ public final class UserManagerServiceTest { () -> mUmi.getBootUser(/* waitUntilSet= */ false)); } + @Test + public void testGetPreviousFullUserToEnterForeground() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + assertWithMessage("getPreviousFullUserToEnterForeground") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(OTHER_USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsCurrentUser() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mockCurrentUser(OTHER_USER_ID); + assertWithMessage("getPreviousFullUserToEnterForeground should skip current user") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsNonFullUsers() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mUsers.get(OTHER_USER_ID).info.flags &= ~UserInfo.FLAG_FULL; + assertWithMessage("getPreviousFullUserToEnterForeground should skip non-full users") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsPartialUsers() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mUsers.get(OTHER_USER_ID).info.partial = true; + assertWithMessage("getPreviousFullUserToEnterForeground should skip partial users") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsDisabledUsers() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mUsers.get(OTHER_USER_ID).info.flags |= UserInfo.FLAG_DISABLED; + assertWithMessage("getPreviousFullUserToEnterForeground should skip disabled users") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsRemovingUsers() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mUms.addRemovingUserId(OTHER_USER_ID); + assertWithMessage("getPreviousFullUserToEnterForeground should skip removing users") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + private void mockCurrentUser(@UserIdInt int userId) { mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal); diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java index a3a49d7035d9..f3aa4274b3cd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java @@ -259,6 +259,29 @@ public class AlarmQueueTest { } @Test + public void testMinTimeBetweenAlarms_freshAlarm() { + final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 5 * MINUTE_IN_MILLIS); + final long fixedTimeElapsed = mInjector.getElapsedRealtime(); + + InOrder inOrder = inOrder(mAlarmManager); + + final String pkg1 = "com.android.test.1"; + final String pkg2 = "com.android.test.2"; + alarmQueue.addAlarm(pkg1, fixedTimeElapsed + MINUTE_IN_MILLIS); + inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( + anyInt(), eq(fixedTimeElapsed + MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any()); + + advanceElapsedClock(MINUTE_IN_MILLIS); + + alarmQueue.onAlarm(); + // Minimum of 5 minutes between alarms, so the next alarm should be 5 minutes after the + // first. + alarmQueue.addAlarm(pkg2, fixedTimeElapsed + 2 * MINUTE_IN_MILLIS); + inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( + anyInt(), eq(fixedTimeElapsed + 6 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any()); + } + + @Test public void testOnAlarm() { final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0); final long nowElapsed = mInjector.getElapsedRealtime(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java index e6ef044d4791..5b5c8d415f84 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -202,6 +203,7 @@ public class FullScreenMagnificationControllerTest { assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_0)); assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_1)); + // Once for each display on unregister verify(mMockThumbnail, times(2)).hideThumbnail(); } @@ -543,7 +545,11 @@ public class FullScreenMagnificationControllerTest { // The first time is triggered when the thumbnail is just created. // The second time is triggered when the magnification region changed. verify(mMockThumbnail, times(2)).setThumbnailBounds( - any(), anyFloat(), anyFloat(), anyFloat()); + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } @Test @@ -681,6 +687,9 @@ public class FullScreenMagnificationControllerTest { checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId); assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2)); checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId); + + // Once on init before it's activated and once for reset + verify(mMockThumbnail, times(2)).hideThumbnail(); } @Test @@ -783,6 +792,9 @@ public class FullScreenMagnificationControllerTest { mMessageCapturingHandler.sendAllMessages(); checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_0); checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_1); + + // Twice for each display: once on init before it's activated and once for screen off + verify(mMockThumbnail, times(4)).hideThumbnail(); } @Test @@ -847,6 +859,15 @@ public class FullScreenMagnificationControllerTest { mMessageCapturingHandler.sendAllMessages(); checkActivatedAndMagnifying( /* activated= */ expectedActivated, /* magnifying= */ false, displayId); + + if (expectedActivated) { + verify(mMockThumbnail, times(2)).setThumbnailBounds( + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); + } } @Test @@ -950,6 +971,13 @@ public class FullScreenMagnificationControllerTest { INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale); assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets))); verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec))); + + verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds( + /* currentBounds= */ any(), + eq(scale), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } @Test @@ -984,6 +1012,13 @@ public class FullScreenMagnificationControllerTest { INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale); assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets))); verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec))); + + verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds( + /* currentBounds= */ any(), + eq(scale), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } @Test @@ -1246,6 +1281,13 @@ public class FullScreenMagnificationControllerTest { callbacks.onImeWindowVisibilityChanged(true); mMessageCapturingHandler.sendAllMessages(); verify(mRequestObserver).onImeWindowVisibilityChanged(eq(DISPLAY_0), eq(true)); + + verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds( + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } @Test @@ -1270,6 +1312,15 @@ public class FullScreenMagnificationControllerTest { mFullScreenMagnificationController.onUserContextChanged(DISPLAY_0); verify(mRequestObserver).onFullScreenMagnificationActivationState(eq(DISPLAY_0), eq(false)); + verify(mMockThumbnail).setThumbnailBounds( + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); + + // Once on init before it's activated and once for reset + verify(mMockThumbnail, times(2)).hideThumbnail(); } @Test @@ -1281,6 +1332,12 @@ public class FullScreenMagnificationControllerTest { assertEquals(1.0f, mFullScreenMagnificationController.getScale(DISPLAY_0), 0); assertTrue(mFullScreenMagnificationController.isActivated(DISPLAY_0)); + verify(mMockThumbnail).setThumbnailBounds( + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } private void setScaleToMagnifying() { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java index 3baa102b882b..8faddf8ff541 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java @@ -187,6 +187,29 @@ public class MagnificationThumbnailTest { .addView(eq(mMagnificationThumbnail.mThumbnailLayout), any()); verify(mMockWindowManager, never()) .removeView(eq(mMagnificationThumbnail.mThumbnailLayout)); + verify(mMockWindowManager, never()) + .updateViewLayout(eq(mMagnificationThumbnail.mThumbnailLayout), any()); + } + + @Test + public void whenVisible_setBoundsUpdatesLayout() throws InterruptedException { + runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail( + /* scale= */ 2f, + /* centerX= */ 5, + /* centerY= */ 10 + )); + runOnMainSync(() -> mMagnificationThumbnail.setThumbnailBounds( + new Rect(), + /* scale= */ 2f, + /* centerX= */ 5, + /* centerY= */ 10 + )); + idle(); + + verify(mMockWindowManager).updateViewLayout( + eq(mMagnificationThumbnail.mThumbnailLayout), + /* params= */ any() + ); } private static void idle() { diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index dccacb4d301a..24a628eb4331 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -1342,6 +1342,10 @@ public class UserControllerTest { Message copy = new Message(); copy.copyFrom(msg); mMessages.add(copy); + if (msg.getCallback() != null) { + msg.getCallback().run(); + msg.setCallback(null); + } return super.sendMessageAtTime(msg, uptimeMillis); } } diff --git a/services/tests/servicestests/src/com/android/server/display/OWNERS b/services/tests/servicestests/src/com/android/server/display/OWNERS deleted file mode 100644 index 6ce1ee4d3de2..000000000000 --- a/services/tests/servicestests/src/com/android/server/display/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /services/core/java/com/android/server/display/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING deleted file mode 100644 index 92d8abd4f173..000000000000 --- a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING +++ /dev/null @@ -1,13 +0,0 @@ -{ - "presubmit": [ - { - "name": "FrameworksServicesTests", - "options": [ - {"include-filter": "com.android.server.display"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] - } - ] -} diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index c42928eba85f..bb8b986c6f61 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.never; 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 static org.testng.Assert.assertThrows; import android.app.ActivityManagerInternal; @@ -49,10 +50,14 @@ import android.content.pm.PackageManager.ApplicationInfoFlags; import android.content.pm.PackageManager.NameNotFoundException; import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; +import android.media.projection.IMediaProjectionWatcherCallback; import android.media.projection.ReviewGrantedConsentResult; +import android.os.Binder; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; +import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.view.ContentRecordingSession; @@ -86,6 +91,7 @@ public class MediaProjectionManagerServiceTest { private static final int UID = 10; private static final String PACKAGE_NAME = "test.package"; private final ApplicationInfo mAppInfo = new ApplicationInfo(); + private final TestLooper mTestLooper = new TestLooper(); private static final ContentRecordingSession DISPLAY_SESSION = ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY); // Callback registered by an app on a MediaProjection instance. @@ -110,6 +116,14 @@ public class MediaProjectionManagerServiceTest { } }; + private final MediaProjectionManagerService.Injector mTestLooperInjector = + new MediaProjectionManagerService.Injector() { + @Override + Looper createCallbackLooper() { + return mTestLooper.getLooper(); + } + }; + private Context mContext; private MediaProjectionManagerService mService; private OffsettableClock mClock; @@ -122,12 +136,15 @@ public class MediaProjectionManagerServiceTest { private WindowManagerInternal mWindowManagerInternal; @Mock private PackageManager mPackageManager; + @Mock + private IMediaProjectionWatcherCallback mWatcherCallback; @Captor private ArgumentCaptor<ContentRecordingSession> mSessionCaptor; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); + when(mWatcherCallback.asBinder()).thenReturn(new Binder()); LocalServices.removeServiceForTest(ActivityManagerInternal.class); LocalServices.addService(ActivityManagerInternal.class, mAmInternal); @@ -671,6 +688,59 @@ public class MediaProjectionManagerServiceTest { assertThat(mService.isCurrentProjection(projection)).isTrue(); } + @Test + public void setContentRecordingSession_successful_notifiesListeners() + throws Exception { + mService.addCallback(mWatcherCallback); + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + + doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( + any(ContentRecordingSession.class)); + mService.setContentRecordingSession(DISPLAY_SESSION); + + verify(mWatcherCallback).onRecordingSessionSet( + projection.getProjectionInfo(), + DISPLAY_SESSION + ); + } + + @Test + public void setContentRecordingSession_notifiesListenersOnCallbackLooper() + throws Exception { + mService = new MediaProjectionManagerService(mContext, mTestLooperInjector); + mService.addCallback(mWatcherCallback); + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( + any(ContentRecordingSession.class)); + + mService.setContentRecordingSession(DISPLAY_SESSION); + // Callback not notified yet, as test looper hasn't dispatched the message yet + verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any()); + + mTestLooper.dispatchAll(); + // Message dispatched on test looper. Callback should now be notified. + verify(mWatcherCallback).onRecordingSessionSet( + projection.getProjectionInfo(), + DISPLAY_SESSION + ); + } + + @Test + public void setContentRecordingSession_failure_doesNotNotifyListeners() + throws Exception { + mService.addCallback(mWatcherCallback); + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + + doReturn(false).when(mWindowManagerInternal).setContentRecordingSession( + any(ContentRecordingSession.class)); + mService.setContentRecordingSession(DISPLAY_SESSION); + + verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any()); + } + private void verifySetSessionWithContent(@ContentRecordingSession.RecordContent int content) { verify(mWindowManagerInternal, atLeastOnce()).setContentRecordingSession( mSessionCaptor.capture()); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java index fdf94bec8c18..39cc6537c759 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java @@ -182,7 +182,7 @@ public class UserManagerServiceCreateProfileTest { UserInfo secondaryUser = addUser(); UserInfo profile = addProfile(secondaryUser); // Add the profile it to the users being removed. - mUserManagerService.addRemovingUserIdLocked(profile.id); + mUserManagerService.addRemovingUserId(profile.id); // We should reuse the badge from the profile being removed. assertEquals("Badge index not reused while removing a user", 0, mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id, diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java index 1f4c9f8cd343..b6fd65e5d3b6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java @@ -111,7 +111,7 @@ public class UserManagerServiceIdRecyclingTest { private void removeUser(int userId) { mUserManagerService.removeUserInfo(userId); - mUserManagerService.addRemovingUserIdLocked(userId); + mUserManagerService.addRemovingUserId(userId); } private void assertNoNextIdAvailable(String message) { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java index 4af0323122ca..592be2d83aee 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -70,6 +70,9 @@ public class UserManagerServiceTest { LocalServices.removeServiceForTest(UserManagerInternal.class); mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext()); + // Put the current user to mUsers. UMS can't find userlist.xml, and fallbackToSingleUserLP. + mUserManagerService.putUserInfo( + new UserInfo(ActivityManager.getCurrentUser(), "Current User", 0)); restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml"); restrictionsFile.delete(); 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 eaf483869be4..ba0743980077 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4150,6 +4150,30 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testSetListenerAccessForUser_grantWithNameTooLong_throws() { + UserHandle user = UserHandle.of(mContext.getUserId() + 10); + ComponentName c = new ComponentName("com.example.package", + com.google.common.base.Strings.repeat("Blah", 150)); + + assertThrows(IllegalArgumentException.class, + () -> mBinderService.setNotificationListenerAccessGrantedForUser( + c, user.getIdentifier(), /* enabled= */ true, true)); + } + + @Test + public void testSetListenerAccessForUser_revokeWithNameTooLong_okay() throws Exception { + UserHandle user = UserHandle.of(mContext.getUserId() + 10); + ComponentName c = new ComponentName("com.example.package", + com.google.common.base.Strings.repeat("Blah", 150)); + + mBinderService.setNotificationListenerAccessGrantedForUser( + c, user.getIdentifier(), /* enabled= */ false, true); + + verify(mListeners).setPackageOrComponentEnabled( + c.flattenToString(), user.getIdentifier(), true, /* enabled= */ false, true); + } + + @Test public void testSetAssistantAccessForUser() throws Exception { UserInfo ui = new UserInfo(); ui.id = mContext.getUserId() + 10; 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 3db53eb08ea1..2e055e8a665e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -3354,8 +3354,7 @@ public class ActivityRecordTests extends WindowTestsBase { // to client if the app didn't request IME visible. assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput); verify(app2.mClient, atLeastOnce()).resized(any(), anyBoolean(), any(), - insetsStateCaptor.capture(), anyBoolean(), anyBoolean(), anyInt(), anyInt(), - anyBoolean()); + insetsStateCaptor.capture(), anyBoolean(), anyInt(), anyInt(), anyBoolean()); assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 5369b93c9534..bdd178b0b317 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -2046,6 +2046,7 @@ public class DisplayContentTests extends WindowTestsBase { // Once transition starts, rotation is applied and transition shows DC rotating. testPlayer.startTransition(); + waitUntilHandlersIdle(); assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation()); assertNotNull(testPlayer.mLastReady); assertTrue(testPlayer.mController.isPlaying()); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 28241d30e168..f332b6988da0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -563,6 +563,36 @@ public class TaskTests extends WindowTestsBase { assertEquals(freeformBounds, task.getBounds()); } + @Test + public void testTopActivityEligibleForUserAspectRatioButton() { + DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay(); + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); + final Task task = rootTask.getBottomMostTask(); + final ActivityRecord root = task.getTopNonFinishingActivity(); + spyOn(mWm.mLetterboxConfiguration); + + // When device config flag is disabled the button is not enabled + doReturn(false).when(mWm.mLetterboxConfiguration) + .isUserAppAspectRatioSettingsEnabled(); + doReturn(false).when(mWm.mLetterboxConfiguration) + .isTranslucentLetterboxingEnabled(); + assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton); + + // The flag is enabled + doReturn(true).when(mWm.mLetterboxConfiguration) + .isUserAppAspectRatioSettingsEnabled(); + spyOn(root); + doReturn(task).when(root).getOrganizedTask(); + // When the flag is enabled and the top activity is not in size compat mode. + doReturn(false).when(root).inSizeCompatMode(); + assertTrue(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton); + + // When in size compat mode the button is not enabled + doReturn(true).when(root).inSizeCompatMode(); + assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton); + } + /** * Tests that a task with forced orientation has orientation-consistent bounds within the * parent. diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java index 3f8acc651110..40f86ddb611c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java @@ -46,8 +46,7 @@ public class TestIWindow extends IWindow.Stub { @Override public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfig, InsetsState insetsState, boolean forceLayout, - boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing) - throws RemoteException { + int displayId, int seqId, boolean dragResizing) throws RemoteException { } @Override 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 adf3f3976f38..bd111ada8550 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -233,7 +233,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition) { + public void onKeyguardOccludedChangedLw(boolean occluded) { } public void setSafeMode(boolean safeMode) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 197ee92aa7eb..64330d89984e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -81,6 +81,7 @@ import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.util.DisplayMetrics; import android.util.MergedConfiguration; +import android.view.ContentRecordingSession; import android.view.IWindow; import android.view.IWindowSessionCallback; import android.view.InputChannel; @@ -101,6 +102,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.compatibility.common.util.AdoptShellPermissionsRule; import com.android.internal.os.IResultReceiver; +import com.android.server.LocalServices; import org.junit.Rule; import org.junit.Test; @@ -761,6 +763,63 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test + public void setContentRecordingSession_sessionNull_returnsTrue() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + + boolean result = wmInternal.setContentRecordingSession(/* incomingSession= */ null); + + assertThat(result).isTrue(); + } + + @Test + public void setContentRecordingSession_sessionContentDisplay_returnsTrue() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + ContentRecordingSession session = ContentRecordingSession.createDisplaySession( + DEFAULT_DISPLAY); + + boolean result = wmInternal.setContentRecordingSession(session); + + assertThat(result).isTrue(); + } + + @Test + public void setContentRecordingSession_sessionContentTask_noMatchingTask_returnsFalse() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + IBinder launchCookie = new Binder(); + ContentRecordingSession session = ContentRecordingSession.createTaskSession(launchCookie); + + boolean result = wmInternal.setContentRecordingSession(session); + + assertThat(result).isFalse(); + } + + @Test + public void setContentRecordingSession_sessionContentTask_matchingTask_returnsTrue() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + ActivityRecord activityRecord = createActivityRecord(createTask(mDefaultDisplay)); + ContentRecordingSession session = ContentRecordingSession.createTaskSession( + activityRecord.mLaunchCookie); + + boolean result = wmInternal.setContentRecordingSession(session); + + assertThat(result).isTrue(); + } + + @Test + public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + Task task = createTask(mDefaultDisplay); + ActivityRecord activityRecord = createActivityRecord(task); + ContentRecordingSession session = ContentRecordingSession.createTaskSession( + activityRecord.mLaunchCookie); + + wmInternal.setContentRecordingSession(session); + + assertThat(session.getTokenToRecord()).isEqualTo( + task.mRemoteToken.toWindowContainerToken().asBinder()); + } + + @Test public void testisLetterboxBackgroundMultiColored() { assertThat(setupLetterboxConfigurationWithBackgroundType( LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 0ddd3135506e..c13c2c3e35d0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -778,8 +778,8 @@ public class WindowStateTests extends WindowTestsBase { doThrow(new RemoteException("test")).when(win.mClient).resized(any() /* frames */, anyBoolean() /* reportDraw */, any() /* mergedConfig */, any() /* insetsState */, anyBoolean() /* forceLayout */, - anyBoolean() /* alwaysConsumeSystemBars */, anyInt() /* displayId */, - anyInt() /* seqId */, anyBoolean() /* dragResizing */); + anyInt() /* displayId */, anyInt() /* seqId */, + anyBoolean() /* dragResizing */); } catch (RemoteException ignored) { } win.reportResized(); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 13945a119e6f..997015ff1c08 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -407,7 +407,9 @@ public class SoundTriggerService extends SystemService { var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE, "SoundTriggerSessionLogs for package: " + Objects.requireNonNull(originatorIdentity.packageName) - + "#" + sessionId); + + "#" + sessionId + + " - " + originatorIdentity.uid + + "|" + originatorIdentity.pid); return new SoundTriggerSessionStub(client, newSoundTriggerHelper(moduleProperties, eventLogger), eventLogger); } @@ -428,7 +430,9 @@ public class SoundTriggerService extends SystemService { var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE, "SoundTriggerSessionLogs for package: " + Objects.requireNonNull(originatorIdentity.packageName) + "#" - + sessionId); + + sessionId + + " - " + originatorIdentity.uid + + "|" + originatorIdentity.pid); return new SoundTriggerSessionStub(client, newSoundTriggerHelper(moduleProperties, eventLogger), eventLogger); } @@ -1801,7 +1805,9 @@ public class SoundTriggerService extends SystemService { ServiceEvent.Type.ATTACH, identity.packageName + "#" + sessionId)); var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE, "LocalSoundTriggerEventLogger for package: " + - identity.packageName + "#" + sessionId); + identity.packageName + "#" + sessionId + + " - " + identity.uid + + "|" + identity.pid); return new SessionImpl(newSoundTriggerHelper(underlyingModule, eventLogger, isTrusted), client, eventLogger, identity); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java index 2f2cb594ff3a..55cbf29553f6 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java @@ -22,7 +22,7 @@ import android.os.HwBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; -import android.util.Log; +import android.util.Slog; import java.util.ArrayList; import java.util.Arrays; @@ -62,7 +62,7 @@ class DefaultHalFactory implements HalFactory { android.hardware.soundtrigger3.ISoundTriggerHw.class.getCanonicalName() + "/default"; if (ServiceManager.isDeclared(aidlServiceName)) { - Log.i(TAG, "Connecting to default soundtrigger3.ISoundTriggerHw"); + Slog.i(TAG, "Connecting to default soundtrigger3.ISoundTriggerHw"); return new SoundTriggerHw3Compat(ServiceManager.waitForService(aidlServiceName), () -> { // This property needs to be defined in an init.rc script and @@ -72,7 +72,7 @@ class DefaultHalFactory implements HalFactory { } // Fallback to soundtrigger-V2.x (HIDL). - Log.i(TAG, "Connecting to default soundtrigger-V2.x.ISoundTriggerHw"); + Slog.i(TAG, "Connecting to default soundtrigger-V2.x.ISoundTriggerHw"); ISoundTriggerHw driver = ISoundTriggerHw.getService(true); return SoundTriggerHw2Compat.create(driver, () -> { // This property needs to be defined in an init.rc script and @@ -81,7 +81,7 @@ class DefaultHalFactory implements HalFactory { }, mCaptureStateNotifier); } else if (mockHal == USE_MOCK_HAL_V2) { // Use V2 mock. - Log.i(TAG, "Connecting to mock soundtrigger-V2.x.ISoundTriggerHw"); + Slog.i(TAG, "Connecting to mock soundtrigger-V2.x.ISoundTriggerHw"); HwBinder.setTrebleTestingOverride(true); try { ISoundTriggerHw driver = ISoundTriggerHw.getService("mock", true); @@ -89,7 +89,7 @@ class DefaultHalFactory implements HalFactory { try { driver.debug(null, new ArrayList<>(Arrays.asList("reboot"))); } catch (Exception e) { - Log.e(TAG, "Failed to reboot mock HAL", e); + Slog.e(TAG, "Failed to reboot mock HAL", e); } }, mCaptureStateNotifier); } finally { @@ -100,14 +100,14 @@ class DefaultHalFactory implements HalFactory { final String aidlServiceName = android.hardware.soundtrigger3.ISoundTriggerHw.class.getCanonicalName() + "/mock"; - Log.i(TAG, "Connecting to mock soundtrigger3.ISoundTriggerHw"); + Slog.i(TAG, "Connecting to mock soundtrigger3.ISoundTriggerHw"); return new SoundTriggerHw3Compat(ServiceManager.waitForService(aidlServiceName), () -> { try { ServiceManager.waitForService(aidlServiceName).shellCommand(null, null, null, new String[]{"reboot"}, null, null); } catch (Exception e) { - Log.e(TAG, "Failed to reboot mock HAL", e); + Slog.e(TAG, "Failed to reboot mock HAL", e); } }); } else { diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java index d195fbedcf2f..e3d64d4bf9db 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java @@ -17,7 +17,7 @@ package com.android.server.soundtrigger_middleware; import android.annotation.NonNull; -import android.util.Log; +import android.util.Slog; import java.util.LinkedList; import java.util.List; @@ -94,7 +94,7 @@ class ExternalCaptureStateTracker implements ICaptureStateNotifier { } } } catch (Exception e) { - Log.e(TAG, "Exception caught while setting capture state", e); + Slog.e(TAG, "Exception caught while setting capture state", e); } } @@ -102,7 +102,7 @@ class ExternalCaptureStateTracker implements ICaptureStateNotifier { * Called by native code when the remote service died. */ private void binderDied() { - Log.w(TAG, "Audio policy service died"); + Slog.w(TAG, "Audio policy service died"); mNeedToConnect.release(); } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java index c3e0a3cd0292..0f63347ccef8 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java @@ -27,7 +27,7 @@ import android.media.soundtrigger_middleware.PhraseRecognitionEventSys; import android.media.soundtrigger_middleware.RecognitionEventSys; import android.os.DeadObjectException; import android.os.IBinder; -import android.util.Log; +import android.util.Slog; import java.util.HashMap; import java.util.Map; @@ -227,10 +227,10 @@ public class SoundTriggerHalEnforcer implements ISoundTriggerHal { } if (e.getCause() instanceof DeadObjectException) { // Server is dead, no need to reboot. - Log.e(TAG, "HAL died"); + Slog.e(TAG, "HAL died"); throw new RecoverableException(Status.DEAD_OBJECT); } - Log.e(TAG, "Exception caught from HAL, rebooting HAL"); + Slog.e(TAG, "Exception caught from HAL, rebooting HAL"); reboot(); throw e; } @@ -257,14 +257,14 @@ public class SoundTriggerHalEnforcer implements ISoundTriggerHal { synchronized (mModelStates) { ModelState state = mModelStates.get(model); if (state == null) { - Log.wtfStack(TAG, "Unexpected recognition event for model: " + model); + Slog.wtfStack(TAG, "Unexpected recognition event for model: " + model); reboot(); return; } if (event.recognitionEvent.recognitionStillActive && event.recognitionEvent.status != RecognitionStatus.SUCCESS && event.recognitionEvent.status != RecognitionStatus.FORCED) { - Log.wtfStack(TAG, + Slog.wtfStack(TAG, "recognitionStillActive is only allowed when the recognition status " + "is SUCCESS"); reboot(); @@ -283,14 +283,14 @@ public class SoundTriggerHalEnforcer implements ISoundTriggerHal { synchronized (mModelStates) { ModelState state = mModelStates.get(model); if (state == null) { - Log.wtfStack(TAG, "Unexpected recognition event for model: " + model); + Slog.wtfStack(TAG, "Unexpected recognition event for model: " + model); reboot(); return; } if (event.phraseRecognitionEvent.common.recognitionStillActive && event.phraseRecognitionEvent.common.status != RecognitionStatus.SUCCESS && event.phraseRecognitionEvent.common.status != RecognitionStatus.FORCED) { - Log.wtfStack(TAG, + Slog.wtfStack(TAG, "recognitionStillActive is only allowed when the recognition status " + "is SUCCESS"); reboot(); @@ -309,13 +309,13 @@ public class SoundTriggerHalEnforcer implements ISoundTriggerHal { synchronized (mModelStates) { ModelState state = mModelStates.get(modelHandle); if (state == null) { - Log.wtfStack(TAG, "Unexpected unload event for model: " + modelHandle); + Slog.wtfStack(TAG, "Unexpected unload event for model: " + modelHandle); reboot(); return; } if (state == ModelState.ACTIVE) { - Log.wtfStack(TAG, "Trying to unload an active model: " + modelHandle); + Slog.wtfStack(TAG, "Trying to unload an active model: " + modelHandle); reboot(); return; } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java index 0390f034ab23..5e525e0d194e 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java @@ -23,7 +23,7 @@ import android.media.soundtrigger.Properties; import android.media.soundtrigger.RecognitionConfig; import android.media.soundtrigger.SoundModel; import android.os.IBinder; -import android.util.Log; +import android.util.Slog; import java.util.Objects; @@ -172,7 +172,7 @@ public class SoundTriggerHalWatchdog implements ISoundTriggerHal { Watchdog() { mTask = mTimer.createTask(() -> { - Log.e(TAG, "HAL deadline expired. Rebooting.", mException); + Slog.e(TAG, "HAL deadline expired. Rebooting.", mException); reboot(); }, TIMEOUT_MS); } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java index df2e9b41662b..730e92cb2aee 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java @@ -32,7 +32,7 @@ import android.os.IHwBinder; import android.os.RemoteException; import android.os.SystemClock; import android.system.OsConstants; -import android.util.Log; +import android.util.Slog; import java.io.IOException; import java.util.HashMap; @@ -240,7 +240,7 @@ final class SoundTriggerHw2Compat implements ISoundTriggerHal { try { hidlModel.data.close(); } catch (IOException e) { - Log.e(TAG, "Failed to close file", e); + Slog.e(TAG, "Failed to close file", e); } } } @@ -276,7 +276,7 @@ final class SoundTriggerHw2Compat implements ISoundTriggerHal { try { hidlModel.common.data.close(); } catch (IOException e) { - Log.e(TAG, "Failed to close file", e); + Slog.e(TAG, "Failed to close file", e); } } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java index 3b800de2f30b..5a064da314c6 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerModule; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; -import android.util.Log; +import android.util.Slog; import java.util.ArrayList; import java.util.List; @@ -85,7 +85,7 @@ public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareIntern try { modules.add(new SoundTriggerModule(halFactory, audioSessionProvider)); } catch (Exception e) { - Log.e(TAG, "Failed to add a SoundTriggerModule instance", e); + Slog.e(TAG, "Failed to add a SoundTriggerModule instance", e); } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java index 7ec2d9fd7b23..0b9ed8c20e8e 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java @@ -36,7 +36,7 @@ import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceSpecificException; -import android.util.Log; +import android.util.Slog; import android.util.SparseArray; import com.android.internal.util.Preconditions; @@ -150,7 +150,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware e.getMessage()); } - Log.wtf(TAG, "Unexpected exception", e); + Slog.wtf(TAG, "Unexpected exception", e); throw new ServiceSpecificException(Status.INTERNAL_ERROR, e.getMessage()); } @@ -701,7 +701,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware try { mCallback.onRecognition(modelHandle, event, captureSession); } catch (Exception e) { - Log.w(TAG, "Client callback exception.", e); + Slog.w(TAG, "Client callback exception.", e); } } @@ -719,7 +719,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware try { mCallback.onPhraseRecognition(modelHandle, event, captureSession); } catch (Exception e) { - Log.w(TAG, "Client callback exception.", e); + Slog.w(TAG, "Client callback exception.", e); } } @@ -734,7 +734,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware try { mCallback.onModelUnloaded(modelHandle); } catch (Exception e) { - Log.w(TAG, "Client callback exception.", e); + Slog.w(TAG, "Client callback exception.", e); } } @@ -746,7 +746,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware } catch (RemoteException e) { // Dead client will be handled by binderDied() - no need to handle here. // In any case, client callbacks are considered best effort. - Log.e(TAG, "Client callback exception.", e); + Slog.e(TAG, "Client callback exception.", e); } } @@ -761,7 +761,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware } catch (RemoteException e) { // Dead client will be handled by binderDied() - no need to handle here. // In any case, client callbacks are considered best effort. - Log.e(TAG, "Client callback exception.", e); + Slog.e(TAG, "Client callback exception.", e); } } @@ -795,11 +795,11 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware // Check if state updated unexpectedly to log race conditions. for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) { if (cachedMap.get(entry.getKey()) != entry.getValue().activityState) { - Log.e(TAG, "Unexpected state update in binderDied. Race occurred!"); + Slog.e(TAG, "Unexpected state update in binderDied. Race occurred!"); } } if (mLoadedModels.size() != cachedMap.size()) { - Log.e(TAG, "Unexpected state update in binderDied. Race occurred!"); + Slog.e(TAG, "Unexpected state update in binderDied. Race occurred!"); } try { // Detach diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java index e793f317d41f..45a7fafa90a7 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java @@ -31,7 +31,7 @@ import android.media.soundtrigger_middleware.RecognitionEventSys; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; -import android.util.Log; +import android.util.Slog; import java.util.ArrayList; import java.util.HashMap; @@ -136,7 +136,7 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo @Override public void binderDied() { - Log.w(TAG, "Underlying HAL driver died."); + Slog.w(TAG, "Underlying HAL driver died."); List<ISoundTriggerCallback> callbacks; synchronized (this) { callbacks = new ArrayList<>(mActiveSessions.size()); @@ -270,7 +270,7 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo try { mAudioSessionProvider.releaseSession(audioSession.mSessionHandle); } catch (Exception ee) { - Log.e(TAG, "Failed to release session.", ee); + Slog.e(TAG, "Failed to release session.", ee); } throw e; } @@ -286,7 +286,7 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo checkValid(); Model loadedModel = new Model(); int result = loadedModel.load(model, audioSession); - Log.d(TAG, String.format("loadPhraseModel()->%d", result)); + Slog.d(TAG, String.format("loadPhraseModel()->%d", result)); return result; } catch (Exception e) { // We must do this outside the lock, to avoid possible deadlocks with the remote @@ -294,7 +294,7 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo try { mAudioSessionProvider.releaseSession(audioSession.mSessionHandle); } catch (Exception ee) { - Log.e(TAG, "Failed to release session.", ee); + Slog.e(TAG, "Failed to release session.", ee); } throw e; } diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java index db369756c50e..346622f0f467 100644 --- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java +++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java @@ -61,8 +61,11 @@ public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test { options.setTestMethodName("testCollectAllApexInfo"); // Collect APEX package names from /apex, then pass them as expectation to be verified. + // The package names are collected from the find name with deduplication (NB: we used to + // deduplicate by dropping directory names with '@', but there's a DCLA case where it only + // has one directory with '@'. So we have to keep it and deduplicate the current way). CommandResult result = getDevice().executeShellV2Command( - "ls -d /apex/*/ |grep -v @ |grep -v /apex/sharedlibs |cut -d/ -f3"); + "ls -d /apex/*/ |grep -v /apex/sharedlibs |cut -d/ -f3 |cut -d@ -f1 |sort |uniq"); assertTrue(result.getStatus() == CommandStatus.SUCCESS); String[] packageNames = result.getStdout().split("\n"); for (var i = 0; i < packageNames.length; i++) { diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp index 8faedebb4601..feae3b79520f 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -108,6 +108,7 @@ android_test { ":FlickerTestsAppLaunch-src", ":FlickerTestsQuickswitch-src", ":FlickerTestsRotation-src", + ":FlickerTestsNotification-src", ], } diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml index 1ede943a9fa2..ed63ec0a0e83 100644 --- a/tests/FlickerTests/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AndroidTestTemplate.xml @@ -85,6 +85,8 @@ value="/data/user/0/com.android.server.wm.flicker.quickswitch/files"/> <option name="directory-keys" value="/data/user/0/com.android.server.wm.flicker.rotation/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.notification/files"/> <option name="collect-on-run-ended-only" value="true"/> <option name="clean-up" value="true"/> </metrics_collector> diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt new file mode 100644 index 000000000000..43f4ce9b4f6b --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.activityembedding + +import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.Rect +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test launching a secondary activity over an existing split. By default the new secondary + * activity will stack over the previous secondary activity. + * + * Setup: From Activity A launch a split A|B. + * + * Transitions: Let B start C, expect C to cover B and end up in split A|C. + * + * To run this test: `atest FlickerTests:OpenThirdActivityOverSplitTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenThirdActivityOverSplitTest (flicker: LegacyFlickerTest) : + ActivityEmbeddingTestBase(flicker) { + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + // Launch a split. + testApp.launchViaIntent(wmHelper) + testApp.launchSecondaryActivity(wmHelper) + + startDisplayBounds = + wmHelper.currentState.layerState.physicalDisplayBounds + ?: error("Can't get display bounds") + } + transitions { + testApp.launchThirdActivity(wmHelper) + } + teardown { + tapl.goHome() + testApp.exit(wmHelper) + } + } + + /** Main activity remains visible throughout the transition. */ + @Presubmit + @Test + fun mainActivityWindowAlwaysVisible() { + flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) } + } + + /** Main activity remains visible throughout the transition and takes up half of the screen. */ + @Presubmit + @Test + fun mainActivityLayersAlwaysVisible() { + flicker.assertLayers { + isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + } + + flicker.assertLayersStart { + val display = this.entry.displays.firstOrNull { it.isOn && !it.isVirtual } + ?: error("No non-virtual and on display found") + val mainActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + val secondaryActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .region + mainActivityRegion + .plus(secondaryActivityRegion) + .coversExactly(display.layerStackSpace) + } + + flicker.assertLayersEnd { + val display = this.entry.displays.firstOrNull { it.isOn && !it.isVirtual } + ?: error("No non-virtual and on display found") + val mainActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + val secondaryActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + secondaryActivityRegion.isEmpty() + val thirdActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + mainActivityRegion + .plus(thirdActivityRegion.region) + .coversExactly(display.layerStackSpace) + } + } + + /** Third activity launches during the transition and covers up secondary activity. */ + @Presubmit + @Test + fun thirdActivityWindowLaunchesIntoSplit() { + flicker.assertWm { + isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .isAppWindowInvisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .then() + .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .isAppWindowVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .then() + .isAppWindowVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) // expectation + } + } + + /** Third activity launches during the transition and covers up secondary activity. */ + @Presubmit + @Test + fun thirdActivityLayerLaunchesIntoSplit() { + flicker.assertLayers { + isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .isInvisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .then() + .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .isVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .then() + .isVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + } + } + + /** Assert the background animation layer is never visible during transition. */ + @Presubmit + @Test + fun backgroundLayerNeverVisible() { + val backgroundColorLayer = ComponentNameMatcher("", "Animation Background") + flicker.assertLayers { + isInvisible(backgroundColorLayer) + } + } + + companion object { + /** {@inheritDoc} */ + private var startDisplayBounds = Rect.EMPTY + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index 865d5b4b771d..dbbc771809de 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,7 +17,7 @@ package com.android.server.wm.flicker.close import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt index c1086332a656..566f393efaea 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.close -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory 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 ea9710c633b1..ed930fc8c236 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 @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.close import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt index d65555a97d78..49ed183c2ccf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.close -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt index 351eb1e5d2c3..06beec19cbf0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt @@ -59,6 +59,23 @@ constructor( .waitForAndVerify() } + /** Clicks the button to launch a third activity over a secondary activity. */ + fun launchThirdActivity(wmHelper: WindowManagerStateHelper) { + val launchButton = + uiDevice.wait( + Until.findObject(By.res(getPackage(), "launch_third_activity_button")), + FIND_TIMEOUT + ) + require(launchButton != null) { "Can't find launch third activity button on screen." } + launchButton.click() + wmHelper + .StateSyncBuilder() + .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) + .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_STOPPED) + .withActivityState(THIRD_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) + .waitForAndVerify() + } + /** * Clicks the button to finishes the secondary activity launched through * [launchSecondaryActivity], waits for the main activity to resume. @@ -166,6 +183,9 @@ constructor( val SECONDARY_ACTIVITY_COMPONENT = ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent() + val THIRD_ACTIVITY_COMPONENT = + ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT.toFlickerComponent() + val ALWAYS_EXPAND_ACTIVITY_COMPONENT = ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT.toFlickerComponent() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt index 2563bfb03f61..4fd4a61e4adc 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt index 33302face72a..e39a578fd321 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt @@ -18,7 +18,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt index 45fb453f5df8..6d0b6f48d7c6 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt index 12ee7d0bff78..d2c38076e72d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt @@ -18,7 +18,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt index 1371fd7502d6..3e0958a27aaf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.launch -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt index 46eb2571165a..2a2a3db12513 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt @@ -21,7 +21,7 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory 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 1497e50641eb..eec6bfde8b9f 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 @@ -19,7 +19,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt index 9b6c136f54b3..ab6a1ea36222 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.launch -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt index 4a1bd7e165ae..1bdb6e717b12 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt @@ -18,6 +18,8 @@ package com.android.server.wm.flicker.launch import android.os.SystemClock import android.platform.test.annotations.Postsubmit +import android.tools.common.flicker.subject.layers.LayersTraceSubject +import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.apphelpers.CameraAppHelper import android.tools.device.apphelpers.StandardAppHelper import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -137,8 +139,14 @@ class OpenCameraFromHomeOnDoubleClickPowerButtonTest(flicker: LegacyFlickerTest) @Postsubmit @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + flicker.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry( + LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + + listOf(CAMERA_BACKGROUND) + ) + } + } @Postsubmit @Test @@ -161,5 +169,12 @@ class OpenCameraFromHomeOnDoubleClickPowerButtonTest(flicker: LegacyFlickerTest) @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams() = LegacyFlickerTestFactory.nonRotationTests() + + private val CAMERA_BACKGROUND = + ComponentNameMatcher( + "Background for SurfaceView" + + "[com.google.android.GoogleCamera/" + + "com.google.android.apps.camera.legacy.app.activity.main.CameraActivity]" + ) } } diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 64302831202e..7a2e74bdb8e7 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -200,6 +200,13 @@ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" android:exported="false"/> <activity + android:name=".ActivityEmbeddingThirdActivity" + android:label="ActivityEmbedding Third" + android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" + android:exported="false"/> + <activity android:name=".ActivityEmbeddingAlwaysExpandActivity" android:label="ActivityEmbedding AlwaysExpand" android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml index 239aba59f4a7..67314463161d 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml @@ -27,4 +27,12 @@ android:layout_height="48dp" android:text="Finish" /> + <Button + android:id="@+id/launch_third_activity_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_centerHorizontal="true" + android:onClick="launchThirdActivity" + android:text="Launch a third activity" /> + </LinearLayout>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java index 6e78750cdeee..dc21027bc99c 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.testapp; import android.app.Activity; +import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.view.View; @@ -40,4 +41,9 @@ public class ActivityEmbeddingSecondaryActivity extends Activity { } }); } + + public void launchThirdActivity(View view) { + startActivity(new Intent().setComponent( + ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT)); + } } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java new file mode 100644 index 000000000000..3bd72818d894 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.graphics.Color; +import android.os.Bundle; + +/** + * Activity to be used also as a secondary activity to split with + * {@link ActivityEmbeddingMainActivity}. + */ +public class ActivityEmbeddingThirdActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_embedding_base_layout); + findViewById(R.id.root_activity_layout).setBackgroundColor(Color.RED); + } +} 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 52106189840d..d84ac427f027 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 @@ -99,6 +99,12 @@ public class ActivityOptions { FLICKER_APP_PACKAGE + ".ActivityEmbeddingSecondaryActivity"); } + public static class ThirdActivity { + public static final String LABEL = "ActivityEmbeddingThirdActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ActivityEmbeddingThirdActivity"); + } + public static class AlwaysExpandActivity { public static final String LABEL = "ActivityEmbeddingAlwaysExpandActivity"; public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, |